diff --git a/README.md b/README.md index e78d0f5d..e0da28ef 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ let someEntity = this.$api.get('/some/endpoint')
  • ...
  • ``` +**Known limitations:** The current implementation has so far only be tested to generate static HTML on server side (nuxt config `injectScripts` set to `false`) without client side SPA. Serialization of Vuex store data & hydration on client side will probably not work without errors. + # Available options ### apiName diff --git a/src/LoadingStoreValue.ts b/src/LoadingStoreValue.ts index 9a1cda8a..6992f6be 100644 --- a/src/LoadingStoreValue.ts +++ b/src/LoadingStoreValue.ts @@ -48,7 +48,7 @@ class LoadingStoreValue implements Resource { // This is necessary so that Vue's reactivity system understands to treat this LoadingStoreValue // like a normal object. - if (['then', 'toJSON', Symbol.toStringTag, 'state', 'getters', '$options', '_isVue', '__file', 'render', 'constructor'].includes(prop as string)) { + if (['then', Symbol.toStringTag, 'state', 'getters', '$options', '_isVue', '__file', 'render', 'constructor'].includes(prop as string)) { return undefined } @@ -99,6 +99,10 @@ class LoadingStoreValue implements Resource { public $href (relation: string, templateParams = {}): Promise { return this._meta.load.then(resource => resource.$href(relation, templateParams)) } + + public toJSON (): string { + return '{}' + } } export default LoadingStoreValue diff --git a/src/StoreValue.ts b/src/StoreValue.ts index 6517b635..4d555610 100644 --- a/src/StoreValue.ts +++ b/src/StoreValue.ts @@ -97,6 +97,16 @@ class StoreValue implements Resource { $href (relation: string, templateParams = {}): Promise { return this.apiActions.href(this, relation, templateParams) } + + /** + * Serialize object to JSON + * this avoid warnings in Nuxt "Cannot stringify arbitrary non-POJOs" + */ + toJSON (): string { + // for the lack of any better alternative, return store data as JSON + // alternatively: could also return '{}', as the data cannot be used directly, anyway + return JSON.stringify(this._storeData) + } } export default StoreValue diff --git a/src/index.ts b/src/index.ts index e2b13237..c9254417 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ import { ExternalConfig } from './interfaces/Config' import { Store } from 'vuex/types' import { AxiosInstance, AxiosError } from 'axios' import Resource from './interfaces/Resource' -import StoreData, { Link } from './interfaces/StoreData' +import StoreData, { Link, SerializablePromise } from './interfaces/StoreData' import ApiActions from './interfaces/ApiActions' import EmbeddedCollectionClass from './EmbeddedCollection' import EmbeddedCollection, { EmbeddedCollectionMeta } from './interfaces/EmbeddedCollection' @@ -407,7 +407,9 @@ function HalJsonVuex (store: Store>, axios: AxiosInstance, * @param loadStoreData */ function setLoadPromiseOnStore (uri: string, loadStoreData: Promise | null = null) { - store.state[opts.apiName][uri]._meta.load = loadStoreData || Promise.resolve(store.state[opts.apiName][uri]) + const promise: SerializablePromise = loadStoreData || Promise.resolve(store.state[opts.apiName][uri]) + promise.toJSON = () => '{}' // avoid warning in Nuxt when serializing the complete Vuex store ("Cannot stringify arbitrary non-POJOs Promise") + store.state[opts.apiName][uri]._meta.load = promise } /** diff --git a/src/interfaces/StoreData.ts b/src/interfaces/StoreData.ts index 630adc36..d9f39916 100644 --- a/src/interfaces/StoreData.ts +++ b/src/interfaces/StoreData.ts @@ -6,6 +6,10 @@ type TemplatedLink = Link & { templated: string } +type SerializablePromise = Promise & { + toJSON?: () => string +} + type StoreDataMeta = { _meta: { self: string @@ -18,19 +22,19 @@ type StoreDataMeta = { type StoreDataEntity = StoreDataMeta & { items: never, _meta: { - load: Promise + load: SerializablePromise } } type StoreDataCollection = StoreDataMeta & { items: Array, _meta: { - load: Promise + load: SerializablePromise } } type StoreData = StoreDataEntity | StoreDataCollection -export { StoreData, Link, TemplatedLink, StoreDataEntity, StoreDataCollection } +export { StoreData, Link, TemplatedLink, StoreDataEntity, StoreDataCollection, SerializablePromise } export default StoreData diff --git a/tests/store.spec.js b/tests/store.spec.js index 1537e20d..ef72ef3a 100644 --- a/tests/store.spec.js +++ b/tests/store.spec.js @@ -18,6 +18,9 @@ import multipleReferencesToUser from './resources/multiple-references-to-user' import templatedLink from './resources/templated-link' import root from './resources/root' +import LoadingStoreValue from '../src/LoadingStoreValue' +import StoreValue from '../src/StoreValue' + async function letNetworkRequestFinish () { await new Promise(resolve => { setTimeout(() => resolve()) @@ -70,6 +73,24 @@ describe('API store', () => { expect(vm.$store.state.api).toMatchObject(root.storeState) }) + it('can serialize StoreValue object', async done => { + // given + axiosMock.onGet('http://localhost/').reply(200, root.serverResponse) + + // when + const loadingObject = vm.api.get() + + // then (loading) + expect(loadingObject).toBeInstanceOf(LoadingStoreValue) + expect(loadingObject.toJSON()).toEqual('{}') + + // then (loaded) + const loadedObject = await loadingObject._meta.load + expect(loadedObject).toBeInstanceOf(StoreValue) + expect(loadedObject.toJSON()).toEqual('{"this":"is","the":"root","_meta":{"self":"","loading":false,"reloading":false,"load":"{}"}}') + done() + }) + it('imports embedded single entity', async () => { // given axiosMock.onGet('http://localhost/camps/1').reply(200, embeddedSingleEntity.serverResponse)