diff --git a/packages/objectloader2/src/operations/databases/__snapshots__/indexedDatabase.spec.ts.snap b/packages/objectloader2/src/operations/databases/__snapshots__/indexedDatabase.spec.ts.snap index 1eb27e248..5b06c8d62 100644 --- a/packages/objectloader2/src/operations/databases/__snapshots__/indexedDatabase.spec.ts.snap +++ b/packages/objectloader2/src/operations/databases/__snapshots__/indexedDatabase.spec.ts.snap @@ -1,31 +1,29 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`database cache > write single item to queue use getItem 1`] = ` -{ - "base": { - "id": "id", - "speckle_type": "type", +exports[`IndexedDatabase > should add and get an item 1`] = ` +[ + { + "baseId": "id1", + "item": { + "foo": "bar", + }, }, - "baseId": "id", -} +] `; -exports[`database cache > write two items to queue use getItem 1`] = ` -{ - "base": { - "id": "id", - "speckle_type": "type", +exports[`IndexedDatabase > should add and get multiple items 1`] = ` +[ + { + "baseId": "id1", + "item": { + "foo": "bar", + }, }, - "baseId": "id1", -} -`; - -exports[`database cache > write two items to queue use getItem 2`] = ` -{ - "base": { - "id": "id", - "speckle_type": "type", + { + "baseId": "id2", + "item": { + "foo": "bar", + }, }, - "baseId": "id2", -} +] `; diff --git a/packages/objectloader2/src/operations/databases/indexedDatabase.spec.ts b/packages/objectloader2/src/operations/databases/indexedDatabase.spec.ts index 390862b5c..80eed79e8 100644 --- a/packages/objectloader2/src/operations/databases/indexedDatabase.spec.ts +++ b/packages/objectloader2/src/operations/databases/indexedDatabase.spec.ts @@ -1,37 +1,33 @@ -import { describe, expect, test } from 'vitest' -import IndexedDatabase from './indexedDatabase.js' +import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { IDBFactory, IDBKeyRange } from 'fake-indexeddb' +import IndexedDatabase, { IndexedDatabaseOptions } from './indexedDatabase.js' import { Item } from '../../types/types.js' -describe('database cache', () => { - test('write single item to queue use getItem', async () => { - const i: Item = { baseId: 'id', base: { id: 'id', speckle_type: 'type' } } - const database = new IndexedDatabase({ - indexedDB: new IDBFactory(), - keyRange: IDBKeyRange - }) - await database.add(i) - await database.disposeAsync() +// Mock Item +const defaultItem = (id: string): Item => ({ baseId: id, item: { foo: 'bar' } }) - const x = await database.getItem({ id: 'id' }) - expect(x).toMatchSnapshot() +describe('IndexedDatabase', () => { + let db: IndexedDatabase + let options: IndexedDatabaseOptions + + beforeEach(() => { + options = { indexedDB: new IDBFactory(), keyRange: IDBKeyRange } + db = new IndexedDatabase(options) }) - test('write two items to queue use getItem', async () => { - const i1: Item = { baseId: 'id1', base: { id: 'id', speckle_type: 'type' } } - const i2: Item = { baseId: 'id2', base: { id: 'id', speckle_type: 'type' } } - const database = new IndexedDatabase({ - indexedDB: new IDBFactory(), - keyRange: IDBKeyRange - }) - await database.add(i1) - await database.add(i2) - await database.disposeAsync() + afterEach(async () => { + await db.disposeAsync() + }) - const x1 = await database.getItem({ id: i1.baseId }) - expect(x1).toMatchSnapshot() + it('should add and get multiple items', async () => { + const items = [defaultItem('id1'), defaultItem('id2')] + await db.cacheSaveBatch({ batch: items }) + const result = await db.getAll(['id1', 'id2']) + expect(result).toMatchSnapshot() + expect(result).toEqual(items) + }) - const x2 = await database.getItem({ id: i2.baseId }) - expect(x2).toMatchSnapshot() + it('should dispose without error', async () => { + await expect(db.disposeAsync()).resolves.not.toThrow() }) }) diff --git a/packages/objectloader2/src/operations/databases/indexedDatabase.ts b/packages/objectloader2/src/operations/databases/indexedDatabase.ts index 03d0d9d4c..8d545659b 100644 --- a/packages/objectloader2/src/operations/databases/indexedDatabase.ts +++ b/packages/objectloader2/src/operations/databases/indexedDatabase.ts @@ -5,7 +5,7 @@ import { isSafari } from '@speckle/shared' import { Dexie, DexieOptions, Table } from 'dexie' import { Database } from '../interfaces.js' -class ObjectStore extends Dexie { +export class ObjectStore extends Dexie { static #databaseName: string = 'speckle-cache' objects!: Table // Table type: @@ -83,30 +83,6 @@ export default class IndexedDatabase implements Database { this.#cacheDB = await this.#openDatabase() } - //this is for testing only - in the real world we will not use this - async add(item: Item): Promise { - await this.#setupCacheDb() - await this.#cacheDB!.transaction('rw', this.#cacheDB!.objects, async () => { - return await this.#cacheDB?.objects.add(item) - }) - } - - async getItem(params: { id: string }): Promise { - const { id } = params - await this.#setupCacheDb() - //might not be in the real DB yet, so check the write queue first - if (this.#writeQueue) { - const item = this.#writeQueue.get(id) - if (item) { - return item - } - } - - return this.#cacheDB!.transaction('r', this.#cacheDB!.objects, async () => { - return await this.#cacheDB?.objects.get(id) - }) - } - async cacheSaveBatch(params: { batch: Item[] }): Promise { await this.#setupCacheDb() const { batch } = params diff --git a/packages/objectloader2/src/operations/databases/memoryDatabase.spec.ts b/packages/objectloader2/src/operations/databases/memoryDatabase.spec.ts new file mode 100644 index 000000000..cf224848c --- /dev/null +++ b/packages/objectloader2/src/operations/databases/memoryDatabase.spec.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { MemoryDatabase } from './memoryDatabase.js' +import { Base, Item } from '../../types/types.js' + +const makeItem = (id: string, foo = 'bar'): Item => ({ + baseId: id, + base: { foo } as unknown as Base +}) + +describe('MemoryDatabase', () => { + let db: MemoryDatabase + + beforeEach(() => { + db = new MemoryDatabase() + }) + + it('should return undefined for missing keys', async () => { + const result = await db.getAll(['missing']) + expect(result).toEqual([undefined]) + }) + + it('should add and retrieve a single item', async () => { + const item = makeItem('id1') + await db.cacheSaveBatch({ batch: [item] }) + const result = await db.getAll(['id1']) + expect(result).toEqual([item]) + }) + + it('should add and retrieve multiple items', async () => { + const items = [makeItem('id1'), makeItem('id2', 'baz')] + await db.cacheSaveBatch({ batch: items }) + const result = await db.getAll(['id1', 'id2']) + expect(result).toEqual(items) + }) + + it('should overwrite items with the same key', async () => { + const item1 = makeItem('id1', 'foo') + const item2 = makeItem('id1', 'bar') + await db.cacheSaveBatch({ batch: [item1] }) + await db.cacheSaveBatch({ batch: [item2] }) + const result = await db.getAll(['id1']) + expect(result).toEqual([item2]) + }) + + it('disposeAsync should resolve', async () => { + await expect(db.disposeAsync()).resolves.not.toThrow() + }) +}) diff --git a/packages/objectloader2/src/operations/databases/memoryDatabase.ts b/packages/objectloader2/src/operations/databases/memoryDatabase.ts index 6e5d99e10..e657dcac4 100644 --- a/packages/objectloader2/src/operations/databases/memoryDatabase.ts +++ b/packages/objectloader2/src/operations/databases/memoryDatabase.ts @@ -29,14 +29,6 @@ export class MemoryDatabase implements Database { return Promise.resolve() } - getItem(params: { id: string }): Promise { - const item = this.items.get(params.id) - if (item) { - return Promise.resolve({ baseId: params.id, base: item }) - } - return Promise.resolve(undefined) - } - disposeAsync(): Promise { return Promise.resolve() } diff --git a/packages/objectloader2/src/operations/downloaders/__snapshots__/memoryDownloader.spec.ts.snap b/packages/objectloader2/src/operations/downloaders/__snapshots__/memoryDownloader.spec.ts.snap new file mode 100644 index 000000000..7463beeac --- /dev/null +++ b/packages/objectloader2/src/operations/downloaders/__snapshots__/memoryDownloader.spec.ts.snap @@ -0,0 +1,12 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`MemoryDownloader > should add found item to results queue 1`] = ` +Map { + "id1" => { + "foo": "foo", + }, + "id2" => { + "foo": "bar", + }, +} +`; diff --git a/packages/objectloader2/src/operations/downloaders/memoryDownloader.spec.ts b/packages/objectloader2/src/operations/downloaders/memoryDownloader.spec.ts new file mode 100644 index 000000000..c87e78e61 --- /dev/null +++ b/packages/objectloader2/src/operations/downloaders/memoryDownloader.spec.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { MemoryDownloader } from './memoryDownloader.js' +import { Base, Item } from '../../types/types.js' +import Queue from '../../helpers/queue.js' +import BufferQueue from '../../helpers/bufferQueue.js' + +const makeBase = (foo: string): Base => ({ foo } as unknown as Base) + +describe('MemoryDownloader', () => { + let items: Map + let downloader: MemoryDownloader + let results: Queue + + beforeEach(() => { + items = new Map([ + ['id1', makeBase('foo')], + ['id2', makeBase('bar')] + ]) + downloader = new MemoryDownloader('id1', items) + results = new BufferQueue() + }) + + it('should download the root item', async () => { + const item = await downloader.downloadSingle() + expect(item).toEqual({ baseId: 'id1', base: { foo: 'foo' } }) + }) + + it('should throw if root item is missing', async () => { + const missingDownloader = new MemoryDownloader('missing', items) + await expect(missingDownloader.downloadSingle()).rejects.toThrow( + 'Method not implemented.' + ) + }) + + it('should add found item to results queue', () => { + downloader.initializePool({ results, total: 2 }) + downloader.add('id2') + expect(items).toMatchSnapshot() + }) + + it('should throw if added item is missing', () => { + downloader.initializePool({ results, total: 2 }) + expect(() => downloader.add('missing')).toThrow() + }) + + it('disposeAsync should resolve', async () => { + await expect(downloader.disposeAsync()).resolves.not.toThrow() + }) +}) diff --git a/packages/objectloader2/src/operations/downloaders/memoryDownloader.ts b/packages/objectloader2/src/operations/downloaders/memoryDownloader.ts index 383805279..73a077a84 100644 --- a/packages/objectloader2/src/operations/downloaders/memoryDownloader.ts +++ b/packages/objectloader2/src/operations/downloaders/memoryDownloader.ts @@ -23,7 +23,7 @@ export class MemoryDownloader implements Downloader { if (root) { return Promise.resolve({ baseId: this.#rootId, base: root }) } - throw new Error('Method not implemented.') + return Promise.reject(new Error('Method not implemented.')) } disposeAsync(): Promise { return Promise.resolve() diff --git a/packages/objectloader2/src/operations/interfaces.ts b/packages/objectloader2/src/operations/interfaces.ts index 1be903feb..768662254 100644 --- a/packages/objectloader2/src/operations/interfaces.ts +++ b/packages/objectloader2/src/operations/interfaces.ts @@ -13,7 +13,6 @@ export interface Downloader extends Queue { export interface Database { getAll(keys: string[]): Promise<(Item | undefined)[]> - getItem(params: { id: string }): Promise cacheSaveBatch(params: { batch: Item[] }): Promise disposeAsync(): Promise } diff --git a/packages/objectloader2/src/operations/objectLoader2.ts b/packages/objectloader2/src/operations/objectLoader2.ts index 95957b543..67a83d894 100644 --- a/packages/objectloader2/src/operations/objectLoader2.ts +++ b/packages/objectloader2/src/operations/objectLoader2.ts @@ -65,7 +65,7 @@ export class ObjectLoader2 { async getRootObject(): Promise { if (!this.#root) { - this.#root = await this.#database.getItem({ id: this.#rootId }) + this.#root = (await this.#database.getAll([this.#rootId]))[0] if (!this.#root) { this.#root = await this.#downloader.downloadSingle() } diff --git a/packages/objectloader2/src/test/e2e.spec.ts b/packages/objectloader2/src/test/e2e.spec.ts index c3a3ffc75..07399958b 100644 --- a/packages/objectloader2/src/test/e2e.spec.ts +++ b/packages/objectloader2/src/test/e2e.spec.ts @@ -14,8 +14,10 @@ describe('e2e', () => { serverUrl: 'https://app.speckle.systems', streamId: 'da9e320dad', objectId: '31d10c0cea569a1e26809658ed27e281', - indexedDB: new IDBFactory(), - keyRange: IDBKeyRange + options: { + indexedDB: new IDBFactory(), + keyRange: IDBKeyRange + } }) const getObjectPromise = loader.getObject({