b69ff951 by Adam Heath

WIP

1 parent 2c7e9de3
1 {
2 "name": "astro-redux",
3 "type": "module",
4 "exports": {
5 ".": "./src/index.mjs",
6 "./client": "./src/client.mjs",
7 "./slices": "./src/slices.mjs",
8 "./react": "./src/react.jsx"
9 }
10 }
1 ---
2
3 const { locals: { store, session } } = Astro
4
5 const getPreloadedState = async () => {
6 //await getAllAsync(store.dispatch)
7 return new Promise((resolve, reject) => {
8 setTimeout(() => {
9 resolve(store.getState())
10 }, 0)
11 })
12 }
13
14 ---
15 <script define:vars={{preloadedState: await getPreloadedState()}}>
16 window.__PRELOADED_STATE__ = preloadedState
17 console.log('window', window)
18 </script>
1 import { configureStore } from '@reduxjs/toolkit'
2 import { setupListeners } from '@reduxjs/toolkit/query'
3
4 export function getBrowserStore() {
5 const {
6 astroStore: store
7 } = window
8
9 return store
10 }
11
12 export async function configureAstroStore(userStoreConfig) {
13 const {
14 __PRELOADED_STATE__: preloadedState,
15 } = window
16 console.log('preloadedState', preloadedState)
17 if (preloadedState) {
18 const store = configureStore({
19 ...userStoreConfig,
20 preloadedState,
21 })
22 await setupListeners(store.dispatch)
23 //await resetApiState(store.dispatch)
24 console.log('store', store)
25
26 window.astroStore = store
27 }
28 }
1 import NodeCache from 'node-cache'
2 import { configureStore } from '@reduxjs/toolkit'
3
4 export const makeLeadingCase = (s) => s.substring(0, 1).toUpperCase() + s.substring(1)
5
6 export const fixUnsubscribe = async (store, action) => {
7 const promise = store.dispatch(action)
8 try {
9 return await promise
10 } finally {
11 if (promise.unsubscribe) promise.unsubscribe()
12 }
13 }
14
15 export const DefaultEndpointOptions = Symbol.for('DefaultEndpointOptions')
16
17 export const createApiWrappers = (apiSlice, options = {}) => {
18 const { util: { upsertQueryData } } = apiSlice
19 const { prefix, endpoints: endpointOptions = {} } = options
20 const { [ DefaultEndpointOptions ]: defaultEndpointOptions = {} } = endpointOptions
21 return Object.entries(apiSlice.endpoints).reduce((result, [ endpointName, endpoint ]) => {
22 const { initiate } = endpoint
23 const methodName = prefix ? `${prefix}${makeLeadingCase(endpointName)}` : endpointName
24 const { [ endpointName ]: { forceRefetch = false } = defaultEndpointOptions } = endpointOptions
25 result.endpoints[ methodName ] = (store, arg) => {
26 return fixUnsubscribe(store, initiate(arg, { forceRefetch, subscribe: true }))
27 }
28 result.upserts[ methodName ] = (store, arg, data) => {
29 return fixUnsubscribe(store, upsertQueryData(endpointName, arg, data))
30 }
31
32 return result
33 }, { endpoints: {}, upserts: {} })
34 }
35
36 export const createAstroApiWrappers = (apiWrappers) => Object.entries(apiWrappers).reduce((result, [ apiType, methods ]) => {
37 result[ apiType ] = Object.entries(methods).reduce((methods, [ methodName, method ]) => {
38 methods[ methodName ] = (Astro, ...args) => method(Astro.locals.store, ...args)
39 return methods
40 }, {})
41 return result
42 }, {})
43
44 export const sessionStoreCache = new NodeCache({
45 stdTTL: 600, // in seconds
46 useClones: false,
47 })
48
49 export const createGetSessionStore = (storeConfig) => (session) => {
50 const getStore = (sessionId) => {
51 const currentStore = sessionStoreCache.get(sessionId)
52 if (currentStore) return currentStore
53 const newStore = configureStore(storeConfig)
54 sessionStoreCache.set(sessionId, newStore)
55 return newStore
56 }
57 const { id: sessionId } = session
58 return getStore(sessionId)
59 }
60
61 export { default as AstroReduxProvider } from './Provider.astro'
1 import React from 'react'
2 import { Provider } from 'react-redux'
3 import { getBrowserStore } from './client.mjs'
4
5 export const ReduxAstroProvider = (Component) => (props) => {
6 const {
7 ASTRO_STORE: possibleStore = {},
8 ...rest
9 } = props
10 const store = possibleStore.dispatch ? possibleStore : getBrowserStore()
11
12 return (
13 <Provider store={store}><Component {...rest}/></Provider>
14 )
15 }
1 import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
2
3 export const loginPrepareHeaders = (headers, context) => {
4 const { getState } = context
5 const state = getState()
6 const { data: { access_token } = {} } = loginApiSlice.endpoints.getToken.select()(state)
7 if (access_token) {
8 headers.set('authorization', `Bearer ${access_token}`)
9 }
10 return headers
11 }
12
13 export const loginApiSlice = createApi({
14 reducerPath: 'login',
15 tagTypes: ['Token'],
16 keepUnusedDataFor: 60,
17 baseQuery: fetchBaseQuery({
18 baseUrl: '/login',
19 }),
20 refetchOnReconnect: true,
21 endpoints: builder => ({
22 getToken: builder.query({
23 query: () => ({
24 url: '/token?refresh',
25 method: 'GET',
26 }),
27 providesTags(result, error, arg) {
28 const { expires_at, access_token } = result
29 return ['Token']
30 },
31 }),
32 }),
33 })
34
35 export const { useGetTokenQuery } = loginApiSlice
36 export const invalidateToken = (dispatch) => dispatch(loginApiSlice.util.invalidateTags(['Token']))
37