diff --git a/packages/@orbit/indexeddb/src/cache.ts b/packages/@orbit/indexeddb/src/cache.ts index 107c69bab..92e2fb7a9 100644 --- a/packages/@orbit/indexeddb/src/cache.ts +++ b/packages/@orbit/indexeddb/src/cache.ts @@ -165,6 +165,9 @@ export default class IndexedDBCache extends AsyncRecordCache { objectStore.createIndex('recordIdentity', 'recordIdentity', { unique: false }); + objectStore.createIndex('relatedIdentity', 'relatedIdentity', { + unique: false + }); } /** @@ -464,7 +467,20 @@ export default class IndexedDBCache extends AsyncRecordCache { const keyRange = Orbit.globals.IDBKeyRange.only( serializeRecordIdentity(recordIdentity) ); - const request = objectStore.index('recordIdentity').openCursor(keyRange); + + let index; + try { + index = objectStore.index('relatedIdentity'); + } catch (e) { + console.error( + `[@orbit/indexeddb] The 'relatedIdentity' index is missing from the ${INVERSE_RELS} object store in IndexedDB. ` + + 'Please add this index using a DB migration as described in https://github.com/orbitjs/orbit/pull/825' + ); + resolve([]); + return; + } + + const request = index.openCursor(keyRange); request.onerror = function(/* event */) { // console.error('error - getRecords', request.error); diff --git a/packages/@orbit/indexeddb/src/source.ts b/packages/@orbit/indexeddb/src/source.ts index c20292df8..273c47cc1 100644 --- a/packages/@orbit/indexeddb/src/source.ts +++ b/packages/@orbit/indexeddb/src/source.ts @@ -66,7 +66,9 @@ export default class IndexedDBSource extends Source super(settings); - let cacheSettings: IndexedDBCacheSettings = settings.cacheSettings || {}; + let cacheSettings: IndexedDBCacheSettings = settings.cacheSettings || { + schema: settings.schema + }; cacheSettings.schema = settings.schema; cacheSettings.keyMap = settings.keyMap; cacheSettings.queryBuilder = diff --git a/packages/@orbit/indexeddb/test/cache-test.ts b/packages/@orbit/indexeddb/test/cache-test.ts index 69476a13a..4b53b8b07 100644 --- a/packages/@orbit/indexeddb/test/cache-test.ts +++ b/packages/@orbit/indexeddb/test/cache-test.ts @@ -130,12 +130,15 @@ module('Cache', function(hooks) { assert.deepEqual(await cache.getRecordsAsync([jupiter, io, europa]), []); }); - test('sets/gets inverse relationships', async function(assert) { + test('sets/gets inverse relationships for a single record', async function(assert) { const jupiter = { type: 'planet', id: 'jupiter' }; const io = { type: 'moon', id: 'io' }; const europa = { type: 'moon', id: 'europa' }; const callisto = { type: 'moon', id: 'callisto' }; + const earth = { type: 'planet', id: 'earth' }; + const earthMoon = { type: 'moon', id: 'earthMoon' }; + await cache.openDB(); assert.deepEqual( @@ -145,25 +148,33 @@ module('Cache', function(hooks) { ); await cache.addInverseRelationshipsAsync([ - { record: jupiter, relationship: 'moons', relatedRecord: io }, - { record: jupiter, relationship: 'moons', relatedRecord: europa }, - { record: jupiter, relationship: 'moons', relatedRecord: callisto } + { record: callisto, relationship: 'planet', relatedRecord: jupiter }, + { record: earthMoon, relationship: 'planet', relatedRecord: earth }, + { record: europa, relationship: 'planet', relatedRecord: jupiter }, + { record: io, relationship: 'planet', relatedRecord: jupiter } ]); assert.deepEqual( await cache.getInverseRelationshipsAsync(jupiter), [ - { record: jupiter, relationship: 'moons', relatedRecord: callisto }, - { record: jupiter, relationship: 'moons', relatedRecord: europa }, - { record: jupiter, relationship: 'moons', relatedRecord: io } + { record: callisto, relationship: 'planet', relatedRecord: jupiter }, + { record: europa, relationship: 'planet', relatedRecord: jupiter }, + { record: io, relationship: 'planet', relatedRecord: jupiter } ], 'inverse relationships have been added' ); + assert.deepEqual( + await cache.getInverseRelationshipsAsync(earth), + [{ record: earthMoon, relationship: 'planet', relatedRecord: earth }], + 'inverse relationships have been added' + ); + await cache.removeInverseRelationshipsAsync([ - { record: jupiter, relationship: 'moons', relatedRecord: io }, - { record: jupiter, relationship: 'moons', relatedRecord: europa }, - { record: jupiter, relationship: 'moons', relatedRecord: callisto } + { record: callisto, relationship: 'planet', relatedRecord: jupiter }, + { record: earthMoon, relationship: 'planet', relatedRecord: earth }, + { record: europa, relationship: 'planet', relatedRecord: jupiter }, + { record: io, relationship: 'planet', relatedRecord: jupiter } ]); assert.deepEqual( @@ -171,6 +182,12 @@ module('Cache', function(hooks) { [], 'inverse relationships have been removed' ); + + assert.deepEqual( + await cache.getInverseRelationshipsAsync(earth), + [], + 'inverse relationships have been removed' + ); }); test('#patch - addRecord', async function(assert) { @@ -997,4 +1014,130 @@ module('Cache', function(hooks) { 'key has been mapped' ); }); + + test('#patch tracks refs and clears them from hasOne relationships when a referenced record is removed', async function(assert) { + const jupiter: Record = { + type: 'planet', + id: 'p1', + attributes: { name: 'Jupiter' }, + relationships: { moons: { data: undefined } } + }; + const io: Record = { + type: 'moon', + id: 'm1', + attributes: { name: 'Io' }, + relationships: { planet: { data: { type: 'planet', id: 'p1' } } } + }; + const europa: Record = { + type: 'moon', + id: 'm2', + attributes: { name: 'Europa' }, + relationships: { planet: { data: { type: 'planet', id: 'p1' } } } + }; + + await cache.patch(t => [ + t.addRecord(jupiter), + t.addRecord(io), + t.addRecord(europa) + ]); + + assert.deepEqual( + ((await cache.getRecordAsync({ type: 'moon', id: 'm1' })) as Record) + .relationships.planet.data, + { type: 'planet', id: 'p1' }, + 'Jupiter has been assigned to Io' + ); + assert.deepEqual( + ((await cache.getRecordAsync({ type: 'moon', id: 'm2' })) as Record) + .relationships.planet.data, + { type: 'planet', id: 'p1' }, + 'Jupiter has been assigned to Europa' + ); + + await cache.patch(t => t.removeRecord(jupiter)); + + assert.equal( + await cache.getRecordAsync({ type: 'planet', id: 'p1' }), + undefined, + 'Jupiter is GONE' + ); + + assert.equal( + ((await cache.getRecordAsync({ type: 'moon', id: 'm1' })) as Record) + .relationships.planet.data, + undefined, + 'Jupiter has been cleared from Io' + ); + assert.equal( + ((await cache.getRecordAsync({ type: 'moon', id: 'm2' })) as Record) + .relationships.planet.data, + undefined, + 'Jupiter has been cleared from Europa' + ); + }); + + test('#patch tracks refs and clears them from hasMany relationships when a referenced record is removed', async function(assert) { + const io: Record = { + type: 'moon', + id: 'm1', + attributes: { name: 'Io' }, + relationships: { planet: { data: null } } + }; + const europa: Record = { + type: 'moon', + id: 'm2', + attributes: { name: 'Europa' }, + relationships: { planet: { data: null } } + }; + const jupiter: Record = { + type: 'planet', + id: 'p1', + attributes: { name: 'Jupiter' }, + relationships: { + moons: { + data: [{ type: 'moon', id: 'm1' }, { type: 'moon', id: 'm2' }] + } + } + }; + + await cache.patch(t => [ + t.addRecord(io), + t.addRecord(europa), + t.addRecord(jupiter) + ]); + + assert.deepEqual( + ((await cache.getRecordAsync({ type: 'planet', id: 'p1' })) as Record) + .relationships.moons.data, + [{ type: 'moon', id: 'm1' }, { type: 'moon', id: 'm2' }], + 'Jupiter has been assigned to Io and Europa' + ); + assert.deepEqual( + await cache.getRelatedRecordsAsync(jupiter, 'moons'), + [{ type: 'moon', id: 'm1' }, { type: 'moon', id: 'm2' }], + 'Jupiter has been assigned to Io and Europa' + ); + + await cache.patch(t => t.removeRecord(io)); + + assert.equal( + await cache.getRecordAsync({ type: 'moon', id: 'm1' }), + null, + 'Io is GONE' + ); + + await cache.patch(t => t.removeRecord(europa)); + + assert.equal( + await cache.getRecordAsync({ type: 'moon', id: 'm2' }), + null, + 'Europa is GONE' + ); + + assert.deepEqual( + await cache.getRelatedRecordsAsync({ type: 'planet', id: 'p1' }, 'moons'), + [], + 'moons have been cleared from Jupiter' + ); + }); });