@skele/classic
Version:
Skele is an architectural framework that assists with building data-driven apps with React or React Native.
325 lines (277 loc) • 7.5 kB
JavaScript
import * as R from 'ramda'
import invariant from 'invariant'
import { fromJS } from 'immutable'
import uuid from 'uuid'
import { data, log } from '@skele/core'
import { time, timeSync } from '../impl/util'
import * as readActions from './actions'
import * as propNames from '../propNames'
import { isOK, isResponse } from './http'
const { canonical, flow, kindOf } = data
const { info, error } = log
export const fallback = '@@skele/defaultRead'
const updateKind = R.curry((update, element) =>
element.update(
'kind',
R.pipe(
canonical,
update
)
)
)
const setReadId = R.curry((id, el) =>
el.setIn([propNames.metadata, 'readId'], id)
)
const getReadId = el => el.getIn([propNames.metadata, 'readId'])
const setMeta = R.curry((meta, el) => el.set(propNames.metadata, meta))
const setRefreshingAttr = R.curry((value, el) =>
el.setIn([propNames.metadata, 'refreshing'], value)
)
const setRefreshMeta = R.curry((meta, el) =>
el
.setIn([propNames.metadata, 'failedRefresh'], meta)
.deleteIn([propNames.metadata, 'refreshing'])
)
export function setLoading(element, action) {
const { readId } = action
return flow(
element,
updateKind(k => k.set(0, '__loading')),
setReadId(readId)
)
}
export function setRefreshing(element, action) {
let { readId, refreshing } = action
if (refreshing == null) refreshing = true
return flow(
element,
setReadId(readId),
setRefreshingAttr(refreshing)
)
}
export function setRefreshMetadata(element, action) {
let { metadata, readId } = action
return flow(
element,
R.unless(
el => el == null || readId !== getReadId(el),
setRefreshMeta(metadata)
)
)
}
export function applyRead(element, action) {
const { readId, readValue } = action
if (element == null || readId !== getReadId(element)) return element
return flow(
readValue,
setReadId(readId)
)
}
export function fail(element, action) {
const { readId, response } = action
if (element == null || readId !== getReadId(element)) return element
return flow(
element,
updateKind(k => k.set(0, '__error')),
setMeta(fromJS(response.meta))
)
}
export async function performRead(context, action) {
const {
registry,
enrichment,
enhancement,
transformation,
} = context.subsystems.read.context
const kernel = context
const { uri, ...opts } = R.omit(['type', propNames.actionMeta], action)
const reader = registry.get(uri) || registry.get(fallback)
if (reader != null) {
let enhanceContext = {
config: kernel.config,
subsystems: kernel.subsystems,
subsystemSequence: kernel.subsystemSequence,
elementZipper: kernel.elementZipper,
uri,
opts,
}
const [readResponse, readIndependentEnhancements] = await time(
`TIME-reader-plus-enhancement-(${uri})`,
Promise.all.bind(Promise)
)([
time(`TIME-reader-(${uri})`, reader)(
uri,
opts,
R.pick(['config', 'subsystems', 'subsystemSequence'], context)
),
time(
`TIME-enhancement-run-independent-(${uri})`,
enhancement.runEnhancers
)(null, enhanceContext, enhancement.readIndependentEnhancers()),
])
if (!isResponse(readResponse)) {
throw new Error(
`The read fn acting on ${uri} has returned an invalid response: ${readResponse}`
)
}
if (isOK(readResponse)) {
const readValue = fromJS(readResponse.value).merge({
[propNames.metadata]: fromJS(
readResponse.meta || defaultMeta(uri)
).merge({ request: R.omit([propNames.actionMeta], action) }),
})
enhanceContext = {
...enhanceContext,
readValue,
}
const readDependentEnhancements = await time(
`TIME-enhancement-run-read-dependent-(${uri})`,
enhancement.runEnhancers
)(
readValue,
enhanceContext,
enhancement.readDependentEnhancers(kindOf(readValue))
)
const enhancedResponse = timeSync(
`TIME-enhancement-aply-(${uri})`,
enhancement.applyEnhancements
)(readValue, enhanceContext, [
...readIndependentEnhancements,
...readDependentEnhancements,
])
const enrichContext = {
...enhanceContext,
readValue: enhancedResponse,
}
const enrichedResponse = await time(
`TIME-enrichment-(${uri})`,
enrichment
)(enhancedResponse, enrichContext)
const transformContext = {
...enrichContext,
readValue: enrichedResponse,
}
const transformedResponse = timeSync(
`TIME-transformation-(${uri})`,
transformation
)(enrichedResponse, transformContext)
return { ...readResponse, value: transformedResponse }
} else {
return readResponse
}
} else {
return {
meta: {
url: uri,
uri,
status: 999,
message: `There's no reader defined for ${uri}. Did you forget to register a fallback reader?`,
},
}
}
}
function defaultMeta(uri) {
return {
status: 200,
uri,
url: uri,
}
}
export async function read(context, action) {
const { dispatch } = context
const readId = uuid()
dispatch({ ...action, readId, type: readActions.types.setLoading })
try {
const readResponse = await time(
`TIME-performRead-(${action.uri})`,
performRead
)(context, action)
if (isOK(readResponse)) {
dispatch({
...action,
type: readActions.types.apply,
readId,
response: readResponse,
readValue: readResponse.value,
})
} else {
dispatch({
...action,
readId,
type: readActions.types.fail,
response: readResponse,
})
}
} catch (e) {
dispatch({
...action,
readId,
type: readActions.types.fail,
response: {
meta: {
status: 999,
message: e.toString(),
error: e,
},
},
})
}
}
export async function readRefresh(context, action) {
const { dispatch } = context
const element = context.query()
const readId = uuid()
let readAction
if (action.uri != null) {
readAction = action
} else {
readAction = element.getIn([propNames.metadata, 'request']).toJS()
}
invariant(
readAction != null,
'The element you are refreshing must have been loaded via a read, or you must provide an uri yourself.'
)
readAction.revalidate = flow(
action,
R.prop('revalidate'),
R.defaultTo(true)
)
context.dispatch({
...action,
readId,
type: readActions.types.setRefreshing,
})
try {
const response = await performRead(context, readAction)
if (isOK(response)) {
dispatch({
...action,
readId,
type: readActions.types.apply,
response,
readValue: response.value,
})
} else {
info(`Unsuccessful refreshing (read) ${readAction.uri} `, response)
dispatch({
...action,
readId,
metadata: fromJS(response.meta),
type: readActions.types.setRefreshMetadata,
})
}
} catch (e) {
error(`Error while refreshing (read) ${readAction.uri} `, e)
dispatch({
...action,
readId,
metadata: fromJS({
status: 999,
message: e.toString(),
error: e,
}),
type: readActions.types.setRefreshMetadata,
})
}
}