feat: 🎸 First release

Basically everything in react-native-embedded-api... but rewritten
This commit is contained in:
Johan Öbrink 2021-02-06 21:06:49 +01:00
parent d623705563
commit d37f3db3e8
9 changed files with 168 additions and 101 deletions

View File

@ -1,3 +1,9 @@
module.exports = {
extends: ['airbnb-typescript'],
parserOptions: {
project: `./tsconfig.json`
},
rules: {
'@typescript-eslint/semi': ['error', 'never']
}
}

27
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Release
on:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install dependencies
run: yarn install --immutable --silent --non-interactive 2> >(grep -v warning 1>&2)
- name: Build
run: yarn build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release

20
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,20 @@
# This workflow will do a clean install of node dependencies and run tests
name: Test
on:
pull_request:
branches: [main]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js and run tests
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- run: yarn install --immutable --silent --non-interactive 2> >(grep -v warning 1>&2)
- run: yarn lint
- run: yarn test

View File

@ -1,9 +1,10 @@
import { ApiCall, EntityAction, EntityName, ExtraActionProps } from './types'
import {
EntityAction, EntityName, ExtraActionProps,
} from './types'
export const loadAction = <T>(entity: EntityName, extra: ExtraActionProps<T>): EntityAction<T> => {
return {
entity,
extra,
type: 'GET_FROM_API',
}
}
// eslint-disable-next-line import/prefer-default-export
export const loadAction = <T>(entity: EntityName, extra: ExtraActionProps<T>): EntityAction<T> => ({
entity,
extra,
type: 'GET_FROM_API',
})

View File

@ -1,13 +1,23 @@
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { Api, CalendarItem, Child, Classmate, MenuItem, NewsItem, Notification, ScheduleItem, User } from '@skolplattformen/embedded-api'
import {
Api,
CalendarItem,
Child,
Classmate,
MenuItem,
NewsItem,
Notification,
ScheduleItem,
User,
} from '@skolplattformen/embedded-api'
import {
ApiCall,
EntityHookResult,
EntityMap,
EntityName,
EntityStoreRootState,
ExtraActionProps
ExtraActionProps,
} from './types'
import { useApi } from './context'
import { loadAction } from './actions'
@ -57,7 +67,9 @@ const hook = <T>(
const listener = () => {
const newState = select(store.getState() as EntityStoreRootState)
if (newState.status !== state.status || newState.data !== state.data || newState.error !== state.error) {
if (newState.status !== state.status
|| newState.data !== state.data
|| newState.error !== state.error) {
setState(newState)
}
}
@ -65,7 +77,7 @@ const hook = <T>(
return {
...state,
reload: () => load(true)
reload: () => load(true),
}
}
@ -73,7 +85,7 @@ export const useChildList = () => hook<Child[]>(
'CHILDREN',
'children',
[],
(store) => store.children,
(s) => s.children,
(api) => () => api.getChildren(),
)
@ -81,7 +93,7 @@ export const useCalendar = (child: Child) => hook<CalendarItem[]>(
'CALENDAR',
`calendar_${child.id}`,
[],
(store) => store.calendar,
(s) => s.calendar,
(api) => () => api.getCalendar(child),
)
@ -89,7 +101,7 @@ export const useClassmates = (child: Child) => hook<Classmate[]>(
'CLASSMATES',
`classmates_${child.id}`,
[],
(store) => store.classmates,
(s) => s.classmates,
(api) => () => api.getClassmates(child),
)
@ -97,7 +109,7 @@ export const useMenu = (child: Child) => hook<MenuItem[]>(
'MENU',
`menu_${child.id}`,
[],
(store) => store.menu,
(s) => s.menu,
(api) => () => api.getMenu(child),
)
@ -105,7 +117,7 @@ export const useNews = (child: Child) => hook<NewsItem[]>(
'NEWS',
`news_${child.id}`,
[],
(store) => store.news,
(s) => s.news,
(api) => () => api.getNews(child),
)
@ -113,7 +125,7 @@ export const useNotifications = (child: Child) => hook<Notification[]>(
'NOTIFICATIONS',
`notifications_${child.id}`,
[],
(store) => store.notifications,
(s) => s.notifications,
(api) => () => api.getNotifications(child),
)
@ -121,7 +133,7 @@ export const useSchedule = (child: Child, from: string, to: string) => hook<Sche
'SCHEDULE',
`schedule_${child.id}_${from}_${to}`,
[],
(store) => store.schedule,
(s) => s.schedule,
(api) => () => api.getSchedule(child, from, to),
)
@ -129,6 +141,6 @@ export const useUser = () => hook<User>(
'USER',
'user',
{},
(store) => store.user,
(s) => s.user,
(api) => () => api.getUser(),
)

View File

@ -1,81 +1,81 @@
/* eslint-disable default-case */
import { Middleware } from 'redux'
import { EntityAction, EntityStoreRootState } from './types'
export const apiMiddleware: Middleware<{}, EntityStoreRootState> =
(storeApi) =>
(next) =>
(action: EntityAction<any>) => {
try {
switch (action.type) {
case 'GET_FROM_API': {
// Call api
const apiCall = action.extra?.apiCall
if (apiCall) {
apiCall()
.then((res: any) => {
const resultAction: EntityAction<any> = {
...action,
type: 'RESULT_FROM_API',
data: res
}
storeApi.dispatch(resultAction)
if (action.extra?.saveToCache && res) {
const cacheAction: EntityAction<any> = {
...resultAction,
type: 'STORE_IN_CACHE'
}
storeApi.dispatch(cacheAction)
}
})
type IMiddleware = Middleware<{}, EntityStoreRootState>
export const apiMiddleware: IMiddleware = (storeApi) => (next) => (action: EntityAction<any>) => {
try {
switch (action.type) {
case 'GET_FROM_API': {
// Call api
const apiCall = action.extra?.apiCall
if (apiCall) {
apiCall()
.then((res: any) => {
const resultAction: EntityAction<any> = {
...action,
type: 'RESULT_FROM_API',
data: res,
}
storeApi.dispatch(resultAction)
// Retrieve from cache
if (action.extra?.getFromCache) {
if (action.extra?.saveToCache && res) {
const cacheAction: EntityAction<any> = {
...action,
type: 'GET_FROM_CACHE'
...resultAction,
type: 'STORE_IN_CACHE',
}
storeApi.dispatch(cacheAction)
}
}
}
} catch (err) {
console.error(err)
})
}
return next(action)
}
export const cacheMiddleware: Middleware<{}, EntityStoreRootState> =
(storeApi) =>
(next) =>
(action: EntityAction<any>) => {
try {
switch (action.type) {
case 'GET_FROM_CACHE': {
const getFromCache = action.extra?.getFromCache
if (getFromCache) {
getFromCache().then((res: string | null) => {
if (res) {
const cacheResultAction: EntityAction<any> = {
...action,
type: 'RESULT_FROM_CACHE',
data: JSON.parse(res)
}
storeApi.dispatch(cacheResultAction)
}
})
}
}
case 'STORE_IN_CACHE': {
const saveToCache = action.extra?.saveToCache
if (saveToCache && action.data) {
saveToCache(JSON.stringify(action.data))
}
}
// Retrieve from cache
if (action.extra?.getFromCache) {
const cacheAction: EntityAction<any> = {
...action,
type: 'GET_FROM_CACHE',
}
} catch (err) {
console.error(err)
storeApi.dispatch(cacheAction)
}
return next(action)
}
}
} catch (err) {
// eslint-disable-next-line no-console
console.error(err)
}
return next(action)
}
export const cacheMiddleware: IMiddleware = (storeApi) => (next) => (action: EntityAction<any>) => {
try {
switch (action.type) {
case 'GET_FROM_CACHE': {
const getFromCache = action.extra?.getFromCache
if (getFromCache) {
getFromCache().then((res: string | null) => {
if (res) {
const cacheResultAction: EntityAction<any> = {
...action,
type: 'RESULT_FROM_CACHE',
data: JSON.parse(res),
}
storeApi.dispatch(cacheResultAction)
}
})
}
break
}
case 'STORE_IN_CACHE': {
const saveToCache = action.extra?.saveToCache
if (saveToCache && action.data) {
saveToCache(JSON.stringify(action.data))
}
break
}
}
} catch (err) {
// eslint-disable-next-line no-console
console.error(err)
}
return next(action)
}

View File

@ -12,8 +12,7 @@ import { EntityName, EntityReducer, EntityState } from './types'
const createReducer = <T>(entity: EntityName): EntityReducer<T> => {
const reducer: EntityReducer<T> = (state = {}, action) => {
if (action.entity !== entity || !action.extra)
return state
if (action.entity !== entity || !action.extra) return state
const key = action.extra?.key
const node = state[key] || {
@ -36,12 +35,14 @@ const createReducer = <T>(entity: EntityName): EntityReducer<T> => {
data: action.data || node.data,
status: 'loaded',
}
break
}
case 'RESULT_FROM_CACHE': {
newNode = {
...node,
data: action.data || node.data,
}
break
}
default: {
newNode = { ...node }

View File

@ -10,7 +10,6 @@ import {
schedule,
user,
} from './reducers'
import { EntityAction } from './types'
const appReducer = combineReducers({
calendar,
@ -22,8 +21,9 @@ const appReducer = combineReducers({
schedule,
user,
})
const rootReducer = (state: unknown, action: EntityAction<any>) => {
const rootReducer: typeof appReducer = (state, action) => {
if (action.type === 'CLEAR') {
// eslint-disable-next-line no-param-reassign
state = undefined
}
return appReducer(state, action)

View File

@ -7,7 +7,7 @@ import {
MenuItem,
NewsItem,
Notification,
ScheduleItem
ScheduleItem,
} from '@skolplattformen/embedded-api'
import { Action, Reducer } from 'redux'
@ -37,14 +37,14 @@ export interface ExtraActionProps<T> {
}
export type EntityActionType = 'GET_FROM_API' | 'RESULT_FROM_API' | 'GET_FROM_CACHE' | 'RESULT_FROM_CACHE' | 'STORE_IN_CACHE' | 'CLEAR'
export type EntityName = 'USER'
| 'CHILDREN'
| 'CALENDAR'
| 'CLASSMATES'
| 'MENU'
| 'NEWS'
| 'NOTIFICATIONS'
| 'SCHEDULE'
| 'ALL'
| 'CHILDREN'
| 'CALENDAR'
| 'CLASSMATES'
| 'MENU'
| 'NEWS'
| 'NOTIFICATIONS'
| 'SCHEDULE'
| 'ALL'
export interface EntityAction<T> extends Action<EntityActionType> {
entity: EntityName
data?: T