Skip to content

Commit 0908644

Browse files
committed
remove EmbeddedCollection as special case
1 parent 215bf99 commit 0908644

File tree

9 files changed

+49
-150
lines changed

9 files changed

+49
-150
lines changed

src/EmbeddedCollection.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/HasItems.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type GConstructor<T> = new (...args: any[]) => T;
1818
type HasStoreData = GConstructor<{ _storeData: { items: Array<Link> } }>;
1919

2020
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
21-
function HasItems<TBase extends HasStoreData> (Base: TBase, apiActions: ApiActions, config: InternalConfig, reloadUri?: string, reloadProperty?: string) {
21+
function HasItems<TBase extends HasStoreData> (Base: TBase, apiActions: ApiActions, config: InternalConfig) {
2222
/**
2323
* Filter out items that are marked as deleting (eager removal)
2424
*/
@@ -75,8 +75,8 @@ function HasItems<TBase extends HasStoreData> (Base: TBase, apiActions: ApiActio
7575
}
7676

7777
// eager loading of 'fetchAllUri' (e.g. parent for embedded collections)
78-
if (config.avoidNPlusOneRequests && reloadUri) {
79-
return apiActions.reload({ _meta: { reload: { uri: reloadUri || '', property: reloadProperty || '' } } }) as Promise<Collection> // we know that reload resolves to a type Collection
78+
if (config.avoidNPlusOneRequests) {
79+
return apiActions.reload(this as unknown as Collection) as Promise<Collection> // we know that reload resolves to a type Collection
8080

8181
// no eager loading: replace each reference (Link) with a StoreValue (Resource)
8282
} else {

src/StoreValue.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import urltemplate from 'url-template'
22
import { isTemplatedLink, isVirtualLink, isEntityReference } from './halHelpers'
3-
import EmbeddedCollection from './EmbeddedCollection'
43
import Resource from './interfaces/Resource'
54
import ApiActions from './interfaces/ApiActions'
6-
import { StoreData, StoreDataEntity } from './interfaces/StoreData'
7-
import Collection from './interfaces/Collection'
5+
import { StoreData } from './interfaces/StoreData'
86
import StoreValueCreator from './StoreValueCreator'
97
import { InternalConfig } from './interfaces/Config'
10-
import HasItems from './HasItems'
118

129
/**
1310
* Represents an actual StoreValue, by wrapping the given Vuex store storeData. The storeData must not be loading.
@@ -43,18 +40,7 @@ class StoreValue implements Resource {
4340

4441
// storeData[key] is a virtual link (=embedded collection)
4542
if (isVirtualLink(value)) {
46-
// build complete Collection class = EmbeddedCollection + HasItems mixin
47-
const EmbeddedCollectionClass = HasItems(EmbeddedCollection, this.apiActions, this.config, storeData._meta.self, key)
48-
49-
const loadCollection = storeData._meta.loading && storeData._meta.load
50-
? (storeData._meta.load as Promise<StoreDataEntity>).then(() => {
51-
const collection = this.apiActions.get(value.href) as Collection
52-
return new EmbeddedCollectionClass(collection, storeData._meta.self, key, this.apiActions)
53-
})
54-
: null
55-
56-
const collection = this.apiActions.get(value.href) as Collection
57-
this[key] = () => new EmbeddedCollectionClass(collection, storeData._meta.self, key, this.apiActions, loadCollection)
43+
this[key] = () => this.apiActions.get(value.href)
5844

5945
// storeData[key] is a reference only (contains only href; no data)
6046
} else if (isEntityReference(value)) {
@@ -84,18 +70,30 @@ class StoreValue implements Resource {
8470
}
8571

8672
$reload (): Promise<Resource> {
87-
return this.apiActions.reload(this._meta.self)
73+
return this.apiActions.reload(this)
8874
}
8975

9076
$post (data: unknown): Promise<Resource | null> {
77+
if (this.isVirtual()) {
78+
throw new Error('$post is not implemented for virtual resources')
79+
}
80+
9181
return this.apiActions.post(this._meta.self, data)
9282
}
9383

9484
$patch (data: unknown): Promise<Resource> {
85+
if (this.isVirtual()) {
86+
throw new Error('$patch is not implemented for virtual resources')
87+
}
88+
9589
return this.apiActions.patch(this._meta.self, data)
9690
}
9791

9892
$del (): Promise<string | void> {
93+
if (this.isVirtual()) {
94+
throw new Error('$del is not implemented for virtual resources')
95+
}
96+
9997
return this.apiActions.del(this._meta.self)
10098
}
10199

@@ -112,6 +110,14 @@ class StoreValue implements Resource {
112110
// alternatively: could also return '{}', as the data cannot be used directly, anyway
113111
return JSON.stringify(this._storeData)
114112
}
113+
114+
/**
115+
* returns true, if resource is only virtual generated an not an actual resource on the API
116+
*/
117+
private isVirtual (): boolean {
118+
const meta = this._storeData._meta
119+
return 'virtual' in meta && meta.virtual
120+
}
115121
}
116122

117123
export default StoreValue

src/index.ts

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ import { ExternalConfig } from './interfaces/Config'
1010
import { Store } from 'vuex/types'
1111
import { AxiosInstance, AxiosError } from 'axios'
1212
import Resource from './interfaces/Resource'
13-
import StoreData, { VirtualStoreData, Link, SerializablePromise } from './interfaces/StoreData'
13+
import StoreData, { Link, SerializablePromise } from './interfaces/StoreData'
1414
import ApiActions from './interfaces/ApiActions'
15-
import EmbeddedCollectionClass from './EmbeddedCollection'
16-
import EmbeddedCollection, { EmbeddedCollectionMeta } from './interfaces/EmbeddedCollection'
1715

1816
/**
1917
* Defines the API store methods available in all Vue components. The methods can be called as follows:
@@ -133,15 +131,11 @@ function HalJsonVuex (store: Store<Record<string, State>>, axios: AxiosInstance,
133131
* @returns Promise Resolves when the GET request has completed and the updated entity is available
134132
* in the Vuex store.
135133
*/
136-
async function reload (uriOrEntity: string | Resource | StoreData | VirtualStoreData | EmbeddedCollectionMeta): Promise<Resource | EmbeddedCollection> {
137-
if (isEmbeddedCollection(uriOrEntity)) {
134+
async function reload (uriOrEntity: string | Resource): Promise<Resource> {
135+
if (uriOrEntity instanceof StoreValue && 'virtual' in uriOrEntity._storeData._meta && uriOrEntity._storeData._meta.virtual) {
138136
// For embedded collections which had to reload the parent entity, unwrap the embedded collection after loading has finished
139-
return reload(uriOrEntity._meta.reload.uri).then(parent => parent[uriOrEntity._meta.reload.property]() as EmbeddedCollection)
140-
}
141-
142-
if (isVirtualStoreData(uriOrEntity) && uriOrEntity._meta.virtual) {
143-
// For virtual resources which had to reload the owningResource, unwrap the relation after loading has finished
144-
return reload(uriOrEntity._meta.owningResource).then(owner => owner[uriOrEntity._meta.owningRelation]() as Resource)
137+
const { owningResource, owningRelation } = uriOrEntity._storeData._meta
138+
return reload(owningResource).then(owner => owner[owningRelation]())
145139
}
146140

147141
const uri = normalizeEntityUri(uriOrEntity, axios.defaults.baseURL)
@@ -165,33 +159,14 @@ function HalJsonVuex (store: Store<Record<string, State>>, axios: AxiosInstance,
165159
return loadPromise.then(storeData => storeValueCreator.wrap(storeData))
166160
}
167161

168-
/**
169-
* Type guard for EmbeddedCollectionMeta
170-
* @param uriOrEntity
171-
*/
172-
function isVirtualStoreData (uriOrEntity: string | Resource | EmbeddedCollectionMeta | StoreData | VirtualStoreData | null): uriOrEntity is VirtualStoreData {
173-
if (uriOrEntity === null) return false
174-
175-
if (typeof uriOrEntity === 'string') return false
176-
177-
// found an object that looks like an EmbeddedCollectionMeta
178-
return 'virtual' in uriOrEntity._meta
179-
}
180-
181-
/**
182-
* Type guard for EmbeddedCollectionMeta
183-
* @param uriOrEntity
184-
*/
185-
function isEmbeddedCollection (uriOrEntity: string | Resource | EmbeddedCollectionMeta | StoreData | VirtualStoreData | null): uriOrEntity is EmbeddedCollectionMeta {
186-
if (uriOrEntity === null) return false
187-
188-
if (typeof uriOrEntity === 'string') return false
162+
async function reloadResourceFromStoreData (storeData: StoreData) {
163+
if ('virtual' in storeData._meta && storeData._meta.virtual) {
164+
const { owningResource, owningRelation } = storeData._meta
189165

190-
// found an actual EmbeddedCollection instance
191-
if (uriOrEntity instanceof EmbeddedCollectionClass) return true
166+
return reload(owningResource).then(owner => owner[owningRelation]())
167+
}
192168

193-
// found an object that looks like an EmbeddedCollectionMeta
194-
return 'reload' in uriOrEntity._meta
169+
return reload(storeData._meta.self)
195170
}
196171

197172
/**
@@ -392,7 +367,7 @@ function HalJsonVuex (store: Store<Record<string, State>>, axios: AxiosInstance,
392367
.filter(outdatedEntity => !outdatedEntity._meta.deleting)
393368

394369
// reload outdated entities...
395-
.map(outdatedEntity => reload(outdatedEntity).catch(() => {
370+
.map(outdatedEntity => reloadResourceFromStoreData(outdatedEntity).catch(() => {
396371
// ...but ignore any errors (such as 404 errors during reloading)
397372
// handleAxiosError will take care of recursively deleting cascade-deleted entities
398373
}))

src/interfaces/ApiActions.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import EmbeddedCollection, { EmbeddedCollectionMeta } from './EmbeddedCollection'
21
import Resource from './Resource'
32
import StoreData from './StoreData'
43

54
interface ApiActions {
6-
get: (uriOrEntity: string | Resource | StoreData, forceReload?: boolean) => Resource
7-
reload: (uriOrEntity: string | Resource | StoreData | EmbeddedCollectionMeta) => Promise<Resource | EmbeddedCollection>
5+
get: (uriOrEntity: string | Resource | StoreData) => Resource
6+
reload: (uriOrEntity: string | Resource) => Promise<Resource>
87
post: (uriOrEntity: string | Resource, data: unknown) => Promise<Resource | null>
98
patch: (uriOrEntity: string | Resource, data: unknown) => Promise<Resource>
109
del: (uriOrEntity: string | Resource) => Promise<string | void>

src/interfaces/EmbeddedCollection.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/interfaces/StoreData.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@ type StoreDataMeta = {
2323
}
2424
}
2525

26-
type VirtualStoreDataMeta = {
26+
type VirtualStoreDataMeta = StoreDataMeta & {
2727
_meta: {
2828
virtual: boolean
2929
owningResource: string
3030
owningRelation: string
3131
}
3232
}
3333

34-
type StoreDataEntity = StoreDataMeta & {
34+
type StoreDataEntity = (StoreDataMeta | VirtualStoreDataMeta) & {
3535
items: never,
3636
_meta: {
3737
load: SerializablePromise<StoreDataEntity>
3838
}
3939
}
4040

41-
type StoreDataCollection = StoreDataMeta & {
41+
type StoreDataCollection = (StoreDataMeta | VirtualStoreDataMeta) & {
4242
items: Array<Link>,
4343
_meta: {
4444
load: SerializablePromise<StoreDataCollection>
@@ -47,8 +47,6 @@ type StoreDataCollection = StoreDataMeta & {
4747

4848
type StoreData = StoreDataEntity | StoreDataCollection
4949

50-
type VirtualStoreData = StoreData & VirtualStoreDataMeta
51-
52-
export { StoreData, VirtualStoreData, Link, VirtualLink, TemplatedLink, StoreDataEntity, StoreDataCollection, SerializablePromise }
50+
export { StoreData, Link, VirtualLink, TemplatedLink, StoreDataEntity, StoreDataCollection, SerializablePromise }
5351

5452
export default StoreData

tests/apiOperations.spec.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import templatedLink from './resources/templated-link'
1313
import rootWithLink from './resources/root-with-link'
1414
import StoreValue from '../src/StoreValue'
1515
import LoadingStoreValue from '../src/LoadingStoreValue'
16-
import EmbeddedCollection from '../src/EmbeddedCollection'
1716

1817
async function letNetworkRequestFinish () {
1918
await new Promise(resolve => {
@@ -123,7 +122,7 @@ describe('Using dollar methods', () => {
123122
await letNetworkRequestFinish()
124123
const camp = vm.api.get('/camps/1')
125124
expect(camp).toBeInstanceOf(StoreValue)
126-
expect(camp.periods()).toBeInstanceOf(EmbeddedCollection)
125+
expect(camp.periods()).toBeInstanceOf(StoreValue)
127126

128127
// when
129128
const load = camp.periods().$reload()
@@ -133,7 +132,7 @@ describe('Using dollar methods', () => {
133132
const periods = await load
134133

135134
expect(periods.items.length).toEqual(2)
136-
expect(periods).toBeInstanceOf(EmbeddedCollection)
135+
expect(periods).toBeInstanceOf(StoreValue)
137136

138137
done()
139138
})
@@ -543,7 +542,7 @@ describe('Using dollar methods', () => {
543542
vm.api.get('/users/1').lastReadBook().chapters()
544543
await letNetworkRequestFinish()
545544
const lastReadBookChapters = vm.api.get('/users/1').lastReadBook().chapters()
546-
expect(lastReadBookChapters).toBeInstanceOf(EmbeddedCollection)
545+
expect(lastReadBookChapters).toBeInstanceOf(StoreValue)
547546

548547
// when
549548
const load = lastReadBookChapters.$loadItems()

tests/store.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ describe('API store', () => {
791791
vm.api.reload(embeddedCollection)
792792

793793
// then
794-
expect(embeddedCollection._meta.self).toBeUndefined()
794+
expect(embeddedCollection._meta.self).toBe('http://localhost/camps/1#activityTypes')
795795
await letNetworkRequestFinish()
796796
expect(vm.$store.state.api['/camps/1#activityTypes'].items).toMatchObject(campData.storeState)
797797
})
@@ -839,7 +839,7 @@ describe('API store', () => {
839839
vm.api.reload(embeddedCollection)
840840

841841
// then
842-
expect(embeddedCollection._meta.self).toBeUndefined()
842+
expect(embeddedCollection._meta.self).toBe('http://localhost/camps/1#activityTypes')
843843
await letNetworkRequestFinish()
844844
expect(vm.$store.state.api['/camps/1#activityTypes'].items).toMatchObject(activityTypeStoreState)
845845
})

0 commit comments

Comments
 (0)