Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit bfdb9e8

Browse files
sorted everything except the reducer
1 parent 21403e7 commit bfdb9e8

File tree

13 files changed

+154
-78
lines changed

13 files changed

+154
-78
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"devDependencies": {
1616
"@types/classnames": "^2.2.6",
1717
"@types/history": "^4.7.2",
18+
"@types/json-schema": "^7.0.1",
1819
"@types/jss": "^9.5.7",
1920
"@types/prettier": "^1.13.2",
2021
"@types/react": "^16.4.18",
@@ -36,6 +37,7 @@
3637
"classnames": "2.2.6",
3738
"history": "4.7.2",
3839
"inflector-js": "1.0.1",
40+
"json-schema": "0.2.3",
3941
"jss": "9.4.0",
4042
"react": "16.5.2",
4143
"react-dom": "16.5.2",

src/stream-store/actions.js

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/stream-store/actions.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Action, Location } from 'history';
2+
import { ReplaySubject } from 'rxjs';
3+
import { createAction } from '../reactive';
4+
import { HttpRequest, HttpResponse } from '../types';
5+
import { history, http } from '../utils';
6+
7+
type Actions = {
8+
[P in keyof typeof http]: {
9+
request: ReplaySubject<HttpRequest>;
10+
response: ReplaySubject<HttpResponse>;
11+
}
12+
};
13+
14+
const verbs = Object.keys(http);
15+
16+
const actions: Actions = verbs.reduce(
17+
(akk, verb: keyof typeof http) => ({
18+
...akk,
19+
[verb]: {
20+
request: createAction(),
21+
response: createAction(),
22+
},
23+
}),
24+
// tslint:disable-next-line:no-object-literal-type-assertion
25+
{} as Actions,
26+
);
27+
28+
verbs.forEach(verb =>
29+
actions[verb].request
30+
.flatMap(request => http[verb](request))
31+
.subscribe(response => actions[verb].response.next(response)),
32+
);
33+
34+
actions.get.response.subscribe(({ url }) => history.push(url));
35+
36+
const getUrl = (location: Location) =>
37+
`${location.pathname}${location.search}${location.hash}`;
38+
39+
history.listen(
40+
(location: Location, action: Action): void => {
41+
if (action !== 'POP') {
42+
return;
43+
}
44+
actions.get.request.next({
45+
headers: {},
46+
link: { href: getUrl(location) },
47+
});
48+
},
49+
);
50+
51+
export default actions;
Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,46 @@
1+
import { JSONSchema7 } from 'json-schema';
12
import { Observable as obs } from 'rxjs';
3+
import { HalResource } from '../types';
24
import { resolveLinks } from '../utils';
35
import actions from './actions';
46

57
const mediaType$ = actions.get.response.map(
68
({ headers }) => headers['content-type'].split(';')[0],
79
);
810

9-
const body$ = actions.get.response.map(({ body }) => body);
11+
const body$ = actions.get.response.map(({ body }) => body as HalResource);
1012

1113
const url$ = actions.get.response.map(({ url }) => url);
1214

1315
const links$ = body$
1416
.zip(url$)
1517
.map(([{ _links }, url]) => resolveLinks(url, _links || {}));
1618

17-
const forms$ = body$.map(({ _embedded }) =>
18-
Object.keys(_embedded || {})
19-
.filter(
20-
rel =>
21-
_embedded[rel].$schema &&
22-
_embedded[rel].$schema.endsWith('schema#'),
23-
)
24-
.reduce((akk, rel) => ({ ...akk, [rel]: _embedded[rel] }), {}),
19+
const isJsonSchema = (schema: JSONSchema7 & HalResource) =>
20+
schema.$schema && schema.$schema.endsWith('schema#');
21+
22+
const forms$ = body$.map(({ _embedded }) => _embedded || {}).map(embedded =>
23+
Object.keys(embedded)
24+
.filter(rel => isJsonSchema(embedded[rel]))
25+
.reduce(
26+
(akk, rel) => ({
27+
...akk,
28+
[rel]: embedded[rel],
29+
}),
30+
// tslint:disable-next-line:no-object-literal-type-assertion
31+
{} as JSONSchema7,
32+
),
2533
);
2634

2735
const verbs = Object.keys(actions);
2836

29-
const requests$ = obs.merge(...verbs.map(verb => actions[verb].request));
37+
const requests$ = obs.merge(
38+
...verbs.map((verb: keyof typeof actions) => actions[verb].request),
39+
);
3040

31-
const responses$ = obs.merge(...verbs.map(verb => actions[verb].response));
41+
const responses$ = obs.merge(
42+
...verbs.map((verb: keyof typeof actions) => actions[verb].response),
43+
);
3244

3345
const delayedRequests$ = requests$.delay(1000);
3446

@@ -48,10 +60,10 @@ const loading$ = requests$
4860
);
4961

5062
export default {
51-
links$,
52-
forms$,
5363
body$,
54-
url$,
64+
forms$,
65+
links$,
5566
loading$,
5667
mediaType$,
68+
url$,
5769
};

src/types/index.d.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface HalLinks {
1515
export interface HalResource {
1616
readonly _links?: HalLinks;
1717
readonly _embedded?: EmbeddedResources;
18+
[key: string]: any;
1819
}
1920

2021
export interface EmbeddedResources {
@@ -29,10 +30,12 @@ export interface NavigatableProps {
2930
}
3031

3132
export interface HttpResponse {
32-
body: { [key: string]: any } | string | undefined;
33+
body: object | undefined | string;
34+
headers: { [key: string]: string };
35+
ok: boolean;
3336
status: number;
3437
statusText: string;
35-
ok: boolean;
38+
url: string;
3639
}
3740

3841
export interface HttpProblemDetailsResponse extends HttpResponse {
@@ -42,3 +45,9 @@ export interface HttpProblemDetailsResponse extends HttpResponse {
4245
type: string;
4346
};
4447
}
48+
49+
export interface HttpRequest {
50+
body?: object;
51+
link: HalLink;
52+
headers: { [key: string]: string | undefined };
53+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { parse } from 'uri-js';
33

44
const history = createBrowserHistory();
55

6-
const getPathAndQuery = url => {
6+
const getPathAndQuery = (url: string): string => {
77
const { path, query } = parse(url);
88

9-
return query ? `${path}?${query}` : path;
9+
return query ? `${path}?${query}` : path || '';
1010
};
1111

12-
const push = url => {
12+
const push = (url: string) => {
1313
const pathAndQuery = getPathAndQuery(url);
1414
if (pathAndQuery !== getPathAndQuery(window.location.href)) {
1515
history.push(pathAndQuery);
Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1+
import { HttpRequest, HttpResponse } from '../types';
12
import mediaTypes from './mediaTypes';
23

3-
const isJson = mediaType =>
4+
declare global {
5+
interface Headers {
6+
entries: () => Iterable<string>;
7+
}
8+
}
9+
10+
const isJson = (mediaType: string | undefined) =>
411
mediaType === mediaTypes.json || (mediaType || '').endsWith('+json');
512

6-
const getBody = async (response, headers) => {
13+
const getBody = async (
14+
response: Response,
15+
headers: { [key: string]: string },
16+
) => {
717
const body = await response.text();
818
if (!isJson(headers['content-type'])) {
919
return body;
@@ -15,16 +25,19 @@ const getBody = async (response, headers) => {
1525
}
1626
};
1727

18-
const getHeaders = headers =>
28+
const getHeaders = (headers: Headers) =>
1929
[...headers.entries()].reduce(
20-
(acc, [key, value]) => ({
21-
...acc,
22-
[key]: value,
23-
}),
30+
(acc, [key, value]) =>
31+
value
32+
? {
33+
...acc,
34+
[key]: value,
35+
}
36+
: acc,
2437
{},
2538
);
2639

27-
const mapResponse = async response => {
40+
const mapResponse = async (response: Response): Promise<HttpResponse> => {
2841
const { ok, status, statusText, url } = response;
2942

3043
const headers = getHeaders(response.headers);
@@ -39,26 +52,34 @@ const mapResponse = async response => {
3952
};
4053
};
4154

42-
const get = ({ link, headers = {} }) =>
55+
const get = ({ link, headers = {} }: HttpRequest) =>
4356
fetch(link.href, {
4457
headers: new Headers({
4558
accept: link.type || mediaTypes.hal,
4659
...headers,
4760
}),
4861
}).then(mapResponse);
4962

50-
const post = ({ link, body, headers = {} }) =>
63+
interface HttpRequestWithBody extends HttpRequest {
64+
body: object;
65+
}
66+
67+
const post = <TRequest extends object, TResponse extends object>({
68+
link,
69+
body,
70+
headers = {},
71+
}: HttpRequestWithBody) =>
5172
fetch(link.href, {
73+
body: JSON.stringify(body),
5274
headers: new Headers({
53-
'content-type': mediaTypes.json,
5475
accept: link.type || mediaTypes.hal,
76+
'content-type': mediaTypes.json,
5577
...headers,
5678
}),
5779
method: 'post',
58-
body: JSON.stringify(body),
5980
}).then(mapResponse);
6081

61-
const _delete = ({ link, headers = {} }) =>
82+
const _delete = ({ link, headers = {} }: HttpRequest) =>
6283
fetch(link.href, {
6384
headers: new Headers({
6485
accept: link.type || mediaTypes.hal,
@@ -67,8 +88,16 @@ const _delete = ({ link, headers = {} }) =>
6788
method: 'delete',
6889
}).then(mapResponse);
6990

70-
export default {
91+
interface Http {
92+
get: (request: HttpRequest) => Promise<HttpResponse>;
93+
post: (request: HttpRequestWithBody) => Promise<HttpResponse>;
94+
delete: (request: HttpRequest) => Promise<HttpResponse>;
95+
}
96+
97+
const http: Http = {
98+
delete: _delete,
7199
get,
72100
post,
73-
delete: _delete,
74101
};
102+
103+
export default http;
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import { resolve } from 'uri-js';
2+
import { HalLinks } from '../types';
23

34
const toArray = maybeArray =>
45
!!maybeArray && Array.isArray(maybeArray) ? maybeArray : [maybeArray];
56

6-
export const resolveLinks = (url, links) =>
7+
export const resolveLinks = (url: string, links: HalLinks): HalLinks =>
78
Object.keys(links).reduce(
89
(akk, rel) => ({
910
...akk,
1011
[rel]: toArray(links[rel]).map(({ href, ...link }) => ({
1112
...link,
12-
rel,
1313
href: resolve(url, href || './', { tolerant: true }),
14+
rel,
1415
})),
1516
}),
16-
{},
17+
// tslint:disable-next-line:no-object-literal-type-assertion
18+
{} as HalLinks,
1719
);
1820

1921
export { default as history } from './history';

0 commit comments

Comments
 (0)