@pihanga2/core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/card.d.ts +22 -0
  2. package/dist/card.d.ts.map +1 -0
  3. package/dist/card.js +493 -0
  4. package/dist/card.js.map +1 -0
  5. package/dist/index.d.ts +50 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +88 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/logger.d.ts +5 -0
  10. package/dist/logger.d.ts.map +1 -0
  11. package/dist/logger.js +12 -0
  12. package/dist/logger.js.map +1 -0
  13. package/dist/reducer.d.ts +5 -0
  14. package/dist/reducer.d.ts.map +1 -0
  15. package/dist/reducer.js +77 -0
  16. package/dist/reducer.js.map +1 -0
  17. package/dist/redux.d.ts +31 -0
  18. package/dist/redux.d.ts.map +1 -0
  19. package/dist/redux.js +50 -0
  20. package/dist/redux.js.map +1 -0
  21. package/dist/rest/delete.d.ts +4 -0
  22. package/dist/rest/delete.d.ts.map +1 -0
  23. package/dist/rest/delete.js +23 -0
  24. package/dist/rest/delete.js.map +1 -0
  25. package/dist/rest/enums.d.ts +6 -0
  26. package/dist/rest/enums.d.ts.map +1 -0
  27. package/dist/rest/enums.js +9 -0
  28. package/dist/rest/enums.js.map +1 -0
  29. package/dist/rest/get.d.ts +10 -0
  30. package/dist/rest/get.d.ts.map +1 -0
  31. package/dist/rest/get.js +31 -0
  32. package/dist/rest/get.js.map +1 -0
  33. package/dist/rest/index.d.ts +6 -0
  34. package/dist/rest/index.d.ts.map +1 -0
  35. package/dist/rest/index.js +6 -0
  36. package/dist/rest/index.js.map +1 -0
  37. package/dist/rest/postPutPatch.d.ts +6 -0
  38. package/dist/rest/postPutPatch.d.ts.map +1 -0
  39. package/dist/rest/postPutPatch.js +72 -0
  40. package/dist/rest/postPutPatch.js.map +1 -0
  41. package/dist/rest/types.d.ts +93 -0
  42. package/dist/rest/types.d.ts.map +1 -0
  43. package/dist/rest/types.js +38 -0
  44. package/dist/rest/types.js.map +1 -0
  45. package/dist/rest/utils.d.ts +8 -0
  46. package/dist/rest/utils.d.ts.map +1 -0
  47. package/dist/rest/utils.js +212 -0
  48. package/dist/rest/utils.js.map +1 -0
  49. package/dist/router.d.ts +25 -0
  50. package/dist/router.d.ts.map +1 -0
  51. package/dist/router.js +136 -0
  52. package/dist/router.js.map +1 -0
  53. package/dist/store.d.ts +1 -0
  54. package/dist/store.d.ts.map +1 -0
  55. package/dist/store.js +18 -0
  56. package/dist/store.js.map +1 -0
  57. package/dist/types.d.ts +94 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +2 -0
  60. package/dist/types.js.map +1 -0
  61. package/package.json +71 -0
  62. package/src/card.tsx +628 -0
  63. package/src/index.tsx +193 -0
  64. package/src/logger.ts +13 -0
  65. package/src/reducer.ts +127 -0
  66. package/src/redux.ts +64 -0
  67. package/src/rest/delete.ts +41 -0
  68. package/src/rest/enums.ts +8 -0
  69. package/src/rest/get.ts +50 -0
  70. package/src/rest/index.ts +7 -0
  71. package/src/rest/postPutPatch.ts +118 -0
  72. package/src/rest/types.ts +135 -0
  73. package/src/rest/utils.ts +265 -0
  74. package/src/router.ts +171 -0
  75. package/src/store.ts +19 -0
  76. package/src/types.ts +146 -0
@@ -0,0 +1,135 @@
1
+ import { RestContentType } from ".."
2
+ import { registerActions } from "../redux"
3
+ import { DispatchF, ReduxAction, ReduxState } from "../types"
4
+
5
+ export const Domain = "pi/rest"
6
+ export const ACTION_TYPES = registerActions(Domain, [
7
+ // "GET_SUBMITTED",
8
+ // "GET_RESULT",
9
+ // "GET_ERROR",
10
+ // "GET_INTERNAL_ERROR",
11
+ // "GET_PERIODIC_TICK",
12
+ "POST_SUBMITTED",
13
+ "POST_RESULT",
14
+ "POST_ERROR",
15
+ "POST_INTERNAL_ERROR",
16
+ "PUT_SUBMITTED",
17
+ "PUT_RESULT",
18
+ "PUT_ERROR",
19
+ "PUT_INTERNAL_ERROR",
20
+ "PATCH_SUBMITTED",
21
+ "PATCH_RESULT",
22
+ "PATCH_ERROR",
23
+ "PATCH_INTERNAL_ERROR",
24
+ "DELETE_SUBMITTED",
25
+ "DELETE_RESULT",
26
+ "DELETE_ERROR",
27
+ "DELETE_INTERNAL_ERROR",
28
+ "UNAUTHORISED_ERROR",
29
+ "PERMISSION_DENIED_ERROR",
30
+ "NOT_FOUND_ERROR",
31
+ "ERROR",
32
+ "CONTEXT_ERROR"
33
+ ])
34
+
35
+ export type Bindings = { [key: string]: string | number }
36
+ export type PoPuPaRequest = {
37
+ body: any
38
+ contentType?: string // if not set and body is 'object' then we send it as jsonconst h = {}
39
+ bindings?: Bindings
40
+ }
41
+
42
+ export type RegisterGenericProps<
43
+ S extends ReduxState,
44
+ A extends ReduxAction,
45
+ R,
46
+ C = any,
47
+ > = {
48
+ name: string
49
+ origin?: string | ((action: A, state: S, context: C) => string | URL) // if defined, will be prepended to 'url' (URL(window.location.href).origin)
50
+ url: string
51
+ trigger: string
52
+ context?: (action: A, state: S) => Promise<C> | null
53
+ guard?: (action: A, state: S, dispatcher: DispatchF, context: C) => boolean
54
+ headers?: (action: A, state: S, context: C) => { [key: string]: string }
55
+ reply: (
56
+ state: S,
57
+ reply: R,
58
+ dispatcher: DispatchF,
59
+ result: ResultAction<A>,
60
+ ) => S
61
+ error?: (
62
+ state: S,
63
+ error: ErrorAction<A>,
64
+ requestAction: A,
65
+ dispatch: DispatchF,
66
+ ) => S
67
+ }
68
+
69
+ export type PiRegisterGetProps<
70
+ S extends ReduxState,
71
+ A extends ReduxAction,
72
+ R,
73
+ C = any,
74
+ > = RegisterGenericProps<S, A, R, C> & {
75
+ request?: (action: A, state: S) => Bindings
76
+ }
77
+
78
+ export type PiRegisterPoPuPaProps<
79
+ S extends ReduxState,
80
+ A extends ReduxAction,
81
+ R,
82
+ C = any,
83
+ > = RegisterGenericProps<S, A, R, C> & {
84
+ request: (action: A, state: S) => PoPuPaRequest
85
+ }
86
+
87
+ export type PiRegisterDeleteProps<
88
+ S extends ReduxState,
89
+ A extends ReduxAction,
90
+ R,
91
+ C = any,
92
+ > = RegisterGenericProps<S, A, R, C> & {
93
+ request?: (action: A, state: S) => Bindings
94
+ }
95
+
96
+
97
+ export type HttpResponse = {
98
+ statusCode: number
99
+ headers: { [k: string]: any }
100
+ content: any
101
+ contentType: RestContentType
102
+ mimeType: string
103
+ size: number
104
+ }
105
+
106
+ export type SubmitAction = ReduxAction & {
107
+ requestID: string
108
+ url: string
109
+ bindings: Bindings
110
+ }
111
+
112
+ export enum ErrorKind {
113
+ Unauthorised = "Unauthorised",
114
+ PermissionDenied = "PermissionDenied",
115
+ NotFound = "NotFound",
116
+ Other = "Other",
117
+ }
118
+
119
+ export type ResultAction<R> = ReduxAction & {
120
+ queryID: string
121
+ url: string
122
+ request: R
123
+ } & HttpResponse
124
+
125
+ export type ErrorAction<R> = ReduxAction & {
126
+ requestID: string
127
+ error: ErrorKind
128
+ url: string
129
+ request: R
130
+ } & HttpResponse
131
+
132
+ export type ContextErrorAction = ReduxAction & {
133
+ error: string
134
+ pendingAction: ReduxAction
135
+ }
@@ -0,0 +1,265 @@
1
+ import { current } from "immer"
2
+ import { DispatchF, PiReducer, ReduxAction, ReduxState } from "../types"
3
+ import {
4
+ ACTION_TYPES,
5
+ Bindings,
6
+ ContextErrorAction,
7
+ ErrorAction,
8
+ ErrorKind,
9
+ HttpResponse,
10
+ RegisterGenericProps,
11
+ ResultAction,
12
+ } from "./types"
13
+ import { RestContentType } from "./enums"
14
+
15
+ export function parseResponse(
16
+ response: Response,
17
+ ): Promise<[any, RestContentType, string, Response]> {
18
+ const mimeType = response.headers.get("content-type")
19
+ if (mimeType) {
20
+ switch (mimeType) {
21
+ case "application/json":
22
+ return response.json().then((j) => [j, RestContentType.Object, mimeType, response])
23
+ case "application/jose":
24
+ return response.text().then((t) => [t, RestContentType.Text, mimeType, response])
25
+ default:
26
+ if (mimeType.startsWith("text")) {
27
+ return response.text().then((t) => [t, RestContentType.Text, mimeType, response])
28
+ }
29
+ }
30
+ }
31
+ return response.blob().then((t) => [t, RestContentType.Blob, mimeType || "unknown", response])
32
+ }
33
+
34
+ export function createErrorAction<R>(
35
+ type: string,
36
+ resp: HttpResponse,
37
+ name: string,
38
+ url: URL,
39
+ request: R,
40
+ ): ErrorAction<R> {
41
+ let error: ErrorKind
42
+ const status = resp.statusCode
43
+ if (status === 401) {
44
+ error = ErrorKind.Unauthorised
45
+ } else if (status === 403) {
46
+ error = ErrorKind.PermissionDenied
47
+ } else if (status === 404) {
48
+ error = ErrorKind.NotFound
49
+ } else {
50
+ error = ErrorKind.Other
51
+ }
52
+ return {
53
+ type,
54
+ requestID: name,
55
+ error,
56
+ url: url.toString(),
57
+ request,
58
+ ...resp,
59
+ }
60
+ }
61
+
62
+ export type RequestF<S extends ReduxState, A extends ReduxAction> = (
63
+ state: S,
64
+ action: A,
65
+ ) => [RequestInit, Bindings]
66
+
67
+ export function registerCommon<S extends ReduxState, A extends ReduxAction, R, C = any>(
68
+ reducer: PiReducer,
69
+ props: RegisterGenericProps<S, A, R, C>,
70
+ requestF: RequestF<S, A>,
71
+ submitType: string,
72
+ resultType: string,
73
+ errorType: string,
74
+ intErrorType: string,
75
+ ) {
76
+ const {
77
+ name,
78
+ origin = window.location.href,
79
+ url,
80
+ trigger,
81
+ context,
82
+ guard,
83
+ headers,
84
+ reply,
85
+ error,
86
+ } = props
87
+
88
+ if (!name) {
89
+ throw Error('Missing "name"')
90
+ }
91
+ if (!url) {
92
+ throw Error('Missing "url"')
93
+ }
94
+ if (!trigger) {
95
+ throw Error('Missing "trigger"')
96
+ }
97
+ if (!reply) {
98
+ throw Error('Missing "reply"')
99
+ }
100
+
101
+ reducer.register<S, A>(
102
+ trigger,
103
+ (state: S, action: A, dispatch: DispatchF) => {
104
+ const ctxtP = context ? context(action, state) : null
105
+ if (ctxtP) {
106
+ // const s = current(state) // need
107
+ ctxtP
108
+ .then((ctxt) => handleEvent(null as unknown as S, action, dispatch, ctxt))
109
+ .catch((err) => {
110
+ const a: ContextErrorAction = {
111
+ type: ACTION_TYPES.CONTEXT_ERROR,
112
+ error: err.toString(),
113
+ pendingAction: action
114
+ }
115
+ dispatch(a)
116
+ })
117
+ } else {
118
+ // we should remove state as argument
119
+ handleEvent(null as unknown as S, action, dispatch, {} as any)
120
+ }
121
+ return state
122
+ })
123
+
124
+ function handleEvent(state: S, action: A, dispatch: DispatchF, ctxt: C): S {
125
+ if (guard) {
126
+ if (!guard(action, state, dispatch, ctxt)) {
127
+ return state
128
+ }
129
+ }
130
+ let bindings: Bindings = {}
131
+ let request: RequestInit
132
+ let url2: URL
133
+ try {
134
+ ;[request, bindings] = requestF(state, action)
135
+ if (headers) {
136
+ const h = headers(action, state, ctxt)
137
+ request.headers = request.headers ? { ...request.headers, ...h } : h
138
+ }
139
+ let o: string
140
+ if (typeof origin === "function") {
141
+ const ox = origin(action, state, ctxt)
142
+ o = ox instanceof URL ? ox.toString() : ox
143
+ } else {
144
+ o = origin
145
+ }
146
+ url2 = buildURL(url, o, bindings)
147
+ } catch (e: any) {
148
+ dispatch({
149
+ type: intErrorType,
150
+ error: e?.message,
151
+ call: name,
152
+ action,
153
+ bindings,
154
+ })
155
+ return state
156
+ }
157
+
158
+ dispatch({
159
+ type: submitType,
160
+ requestID: name,
161
+ url: url2.toString(),
162
+ bindings,
163
+ })
164
+ _fetch(url2, request)
165
+ .then((resp) => {
166
+ if (resp.statusCode < 300) {
167
+ const a: ResultAction<A> = {
168
+ type: resultType,
169
+ queryID: name,
170
+ ...resp,
171
+ url: url2.toString(),
172
+ request: action,
173
+ }
174
+ dispatch(a)
175
+ } else {
176
+ const a = createErrorAction(errorType, resp, name, url2, action)
177
+ dispatch(a)
178
+ }
179
+ })
180
+ .catch((error) => console.log("_fetch", error))
181
+ return state
182
+ }
183
+
184
+ reducer.register<S, ResultAction<A>>(resultType, (state, ra, dispatch) => {
185
+ return reply(state, ra.content, dispatch, ra)
186
+ })
187
+
188
+ if (error) {
189
+ reducer.register<S, ErrorAction<A>>(
190
+ errorType,
191
+ (state, action, dispatch) => {
192
+ return error(state, action, action.request, dispatch)
193
+ },
194
+ )
195
+ }
196
+ }
197
+
198
+ function buildURL(url: string, origin: string, bindings: Bindings): URL {
199
+ const u = new URL(url, origin)
200
+ u.pathname = u.pathname
201
+ .split("/")
202
+ .map((p) => {
203
+ return resolveBinding(p, bindings, encodeURIComponent)
204
+ })
205
+ .join("/")
206
+ if (u.search !== "") {
207
+ const params = new URLSearchParams()
208
+ u.searchParams.forEach((v, k) => {
209
+ const v2 = resolveBinding(v, bindings)
210
+ if (v2) {
211
+ params.set(k, v2)
212
+ }
213
+ })
214
+ u.search = "?" + params.toString()
215
+ }
216
+ return u
217
+ }
218
+
219
+ function resolveBinding(
220
+ value: string,
221
+ bindings: Bindings,
222
+ encoder: ((v: string) => string) | undefined = undefined,
223
+ ): string | undefined {
224
+ const first = value[0]
225
+ if (first === ":" || first === "?") {
226
+ const k = value.slice(1)
227
+ let v = bindings[k]
228
+ if (v === undefined) {
229
+ if (first === "?") {
230
+ return undefined
231
+ } else {
232
+ throw new Error(`Missing binding '${k}'.`)
233
+ }
234
+ }
235
+ if (typeof v !== "string") {
236
+ v = `${v}`
237
+ }
238
+ return encoder ? encoder(v) : v
239
+ } else {
240
+ return value
241
+ }
242
+ }
243
+
244
+ function _fetch(url: URL, request: RequestInit): Promise<HttpResponse> {
245
+ return fetch(url, request)
246
+ .then(parseResponse)
247
+ .then(([content, contentType, mimeType, response]) => {
248
+ return {
249
+ statusCode: response.status,
250
+ content,
251
+ contentType,
252
+ mimeType,
253
+ size: getSize(response),
254
+ headers: response.headers,
255
+ }
256
+ })
257
+ }
258
+
259
+ function getSize(response: Response): number {
260
+ const s = response.headers.get("Content-Length")
261
+ if (!s) {
262
+ return -1
263
+ }
264
+ return Number(s)
265
+ }
package/src/router.ts ADDED
@@ -0,0 +1,171 @@
1
+ import { Update, createBrowserHistory, Location } from 'history';
2
+ import { getLogger } from './logger';
3
+ import { registerActions } from './redux';
4
+ import { DispatchF, PathQuery, PiReducer, ReduceF, ReduxAction, ReduxState, Route } from './types';
5
+ import { PiRegister } from '.';
6
+
7
+ const logger = getLogger("router")
8
+ export const browserHistory = createBrowserHistory();
9
+
10
+ // type Route = {
11
+ // path: string[]
12
+ // query: PathQuery
13
+ // url: string
14
+ // fromBrowser?: boolean
15
+ // }
16
+
17
+ export const ACTION_TYPES = registerActions('pi/router', ['show_page', 'navigate_to_page']);
18
+
19
+ export type ShowPageEvent = {
20
+ path: string[]
21
+ query?: PathQuery
22
+ fromBrowser?: boolean
23
+ }
24
+ export const onShowPage = createOnAction<ShowPageEvent>(ACTION_TYPES.SHOW_PAGE)
25
+
26
+ export const showPage = (dispatch: DispatchF, path: string[], query?: PathQuery) => {
27
+ dispatch(createShowPageAction(path, query))
28
+ }
29
+
30
+ export const createShowPageAction = (path: string[], query?: PathQuery): ReduxAction & ShowPageEvent => ({
31
+ type: ACTION_TYPES.SHOW_PAGE,
32
+ path, query, fromBrowser: false
33
+ })
34
+
35
+ export type NavigateToPageEvent = {
36
+ url: string
37
+ fromBrowser: boolean
38
+ }
39
+ export const onNavigateToPage = createOnAction<NavigateToPageEvent>(ACTION_TYPES.NAVIGATE_TO_PAGE)
40
+
41
+ export const ON_INIT_ACTION = "pi/start"
42
+ export const onInit = createOnAction<{}>(ON_INIT_ACTION)
43
+
44
+ export function currentRoute(pathPrefix = ''): Route {
45
+ const f = route_functions(pathPrefix)
46
+ const r2 = f.url2route(window.location.href)
47
+ const r = f.location2route(browserHistory.location)
48
+ return r
49
+
50
+ }
51
+
52
+ export function init(reducer: PiReducer, pathPrefix = ''): Route {
53
+ let workingURL: string
54
+ const f = route_functions(pathPrefix)
55
+
56
+ browserHistory.listen(({ action, location }: Update) => {
57
+ // location is an object like window.location
58
+ const { url } = f.location2route(location)
59
+ if (workingURL !== url) {
60
+ logger.info('browser history:', url, action)
61
+ setTimeout(() => navigateToPage(url, action === 'POP'))
62
+ }
63
+ });
64
+
65
+ function browserPath(): Route {
66
+ return f.location2route(browserHistory.location)
67
+ }
68
+
69
+ function navigateToPage(url: string, fromBrowser = false) {
70
+ reducer.dispatch({
71
+ type: ACTION_TYPES.NAVIGATE_TO_PAGE,
72
+ url,
73
+ fromBrowser,
74
+ });
75
+ }
76
+
77
+ reducer.register<ReduxState, ReduxAction & NavigateToPageEvent>(
78
+ ACTION_TYPES.NAVIGATE_TO_PAGE,
79
+ (state, { url, fromBrowser }, dispatch) => {
80
+ const r = f.url2route(url)
81
+ r.fromBrowser = fromBrowser
82
+ if (workingURL && state.route?.url === r.url) {
83
+ return state;
84
+ }
85
+ dispatch({
86
+ type: ACTION_TYPES.SHOW_PAGE,
87
+ ...r,
88
+ });
89
+ return state
90
+ });
91
+
92
+ reducer.register<ReduxState, ReduxAction & ShowPageEvent>(
93
+ ACTION_TYPES.SHOW_PAGE,
94
+ (state, { path, query = {}, fromBrowser = false }) => {
95
+ const route = f.pathl2route(path, query)
96
+ workingURL = route.url
97
+ if (!fromBrowser) {
98
+ const hp = browserPath()
99
+ if (route.url !== hp.url) {
100
+ browserHistory.push(route.url);
101
+ }
102
+ }
103
+ state.route = {
104
+ ...route,
105
+ fromBrowser,
106
+ }
107
+ return state
108
+ });
109
+
110
+ reducer.register('@@INIT', (state) => {
111
+ const url = browserPath().url
112
+ logger.info(`Request navigation to '${url}'`);
113
+ setTimeout(() => navigateToPage(url, true));
114
+ return state;
115
+ });
116
+ return f.location2route(browserHistory.location)
117
+ };
118
+
119
+ function route_functions(pathPrefix = '') {
120
+ const location2route = (location: Location): Route => {
121
+ const [p, s] = [location.pathname, location.search]
122
+ const url = s ? `${p}${s}` : p
123
+ return url2route(url)
124
+ }
125
+
126
+ const url2route = (url: string): Route => {
127
+ const [pn, search] = url.split("?")
128
+
129
+ const path = pn.substring(pathPrefix.length).split('/').filter(s => s !== '');
130
+ const query = {} as PathQuery
131
+ const s = search
132
+ if (s && s.length > 0) {
133
+ s.split('&').forEach((el) => {
134
+ const [k, v] = el.split('=');
135
+ query[decodeURI(k)] = v ? decodeURI(v) : true;
136
+ });
137
+ }
138
+ return { url, path, query }
139
+ }
140
+
141
+ const pathl2route = (path: string[], query: PathQuery): Route => {
142
+ let url = `${pathPrefix}/${path.join('/')}`
143
+ if (query) {
144
+ const qa = Object.entries(query);
145
+ if (qa.length > 0) {
146
+ const s = qa.map(([k, v]) => {
147
+ const n = encodeURI(k)
148
+ if (typeof v === 'boolean') {
149
+ return n
150
+ } else if (typeof v === 'number') {
151
+ return `${n}=${v}`
152
+ } else {
153
+ return `${n}=${encodeURI(v)}`
154
+ }
155
+ }).join('&')
156
+ url = `${url}?${s}`
157
+ }
158
+ }
159
+ return { url, path, query }
160
+ }
161
+ return { location2route, url2route, pathl2route }
162
+ }
163
+
164
+ function createOnAction<E>(actionType: string): <S extends ReduxState>(
165
+ register: PiRegister,
166
+ f: ReduceF<S, ReduxAction & E>,
167
+ ) => void {
168
+ return (register, f) => {
169
+ register.reducer.register(actionType, f)
170
+ }
171
+ }
package/src/store.ts ADDED
@@ -0,0 +1,19 @@
1
+ // import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
2
+ // import counterReducer from "../UNUSED/features/counter/counterSlice"
3
+
4
+ // export type Store = any
5
+
6
+ // export const store: Store = configureStore({
7
+ // reducer: {
8
+ // counter: counterReducer,
9
+ // },
10
+ // })
11
+
12
+ // export type AppDispatch = typeof store.dispatch
13
+ // export type RootState = ReturnType<typeof store.getState>
14
+ // export type AppThunk<ReturnType = void> = ThunkAction<
15
+ // ReturnType,
16
+ // RootState,
17
+ // unknown,
18
+ // Action<string>
19
+ // >