@openstax/ts-utils 1.1.10 → 1.1.12
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.
- package/dist/guards.d.ts +1 -0
- package/dist/guards.js +3 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +12 -1
- package/dist/profile.d.ts +59 -0
- package/dist/profile.js +199 -0
- package/dist/routing.d.ts +7 -1
- package/dist/routing.js +13 -9
- package/dist/services/authProvider/browser.js +9 -3
- package/dist/services/authProvider/decryption.js +5 -5
- package/dist/services/authProvider/index.d.ts +8 -3
- package/dist/services/authProvider/subrequest.js +6 -6
- package/dist/services/versionedDocumentStore/dynamodb.d.ts +4 -1
- package/dist/services/versionedDocumentStore/dynamodb.js +9 -9
- package/dist/services/versionedDocumentStore/file-system.d.ts +4 -1
- package/dist/services/versionedDocumentStore/file-system.js +9 -9
- package/dist/tsconfig.withoutspecs.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/guards.d.ts
CHANGED
package/dist/guards.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ifDefined = exports.isPlainObject = exports.isDefined = void 0;
|
|
3
|
+
exports.ifDefined = exports.isPlainObject = exports.isNumber = exports.isDefined = void 0;
|
|
4
4
|
/*
|
|
5
5
|
* checks if a thing is defined. this is often easy to do with a simple if statement, but in certain
|
|
6
6
|
* situations the guard is required and its nice to have one pre-defined.
|
|
@@ -16,6 +16,8 @@ exports.ifDefined = exports.isPlainObject = exports.isDefined = void 0;
|
|
|
16
16
|
*/
|
|
17
17
|
const isDefined = (x) => x !== undefined;
|
|
18
18
|
exports.isDefined = isDefined;
|
|
19
|
+
const isNumber = (x) => typeof x === 'number';
|
|
20
|
+
exports.isNumber = isNumber;
|
|
19
21
|
/*
|
|
20
22
|
* a guard for plain old javascript objects that are not based on some other prototype,
|
|
21
23
|
* for example making them safe to JSON stringify and stuff
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { UnionToIntersection } from './types';
|
|
2
2
|
export declare const getKeyValue: <K extends string>(key: K) => <O extends { [key in K]?: any; }>(obj: O) => O[K];
|
|
3
|
-
export declare const getKeyValueOr: <K extends string, V>(key: K, defaultValue: V) => <O extends { [
|
|
3
|
+
export declare const getKeyValueOr: <K extends string, V, Od extends { [key in K]?: any; } = { [key_1 in K]?: any; }>(key: K, defaultValue: V) => <O extends Od extends undefined ? { [key_2 in K]?: any; } : Od>(obj: O) => V | NonNullable<O[K]>;
|
|
4
4
|
export declare const putKeyValue: <K extends string>(key: K) => <O extends { [key in K]?: any; }>(obj: O, value: O[K]) => O;
|
|
5
|
+
declare type FlowFn<A, R> = (...args: [A]) => R;
|
|
6
|
+
declare type AnyFlowFn = FlowFn<any, any>;
|
|
7
|
+
declare type FlowFnResult<F, A> = F extends FlowFn<A, infer R> ? R : never;
|
|
8
|
+
declare type FlowResult<C, A> = C extends [infer F1, ...infer Cr] ? F1 extends AnyFlowFn ? Cr extends never[] ? FlowFnResult<F1, A> : FlowResult<Cr, FlowFnResult<F1, A>> : never : never;
|
|
9
|
+
declare type FlowChainArg<C> = C extends [infer F1, ...infer _] ? F1 extends FlowFn<infer A, any> ? A : never : never;
|
|
10
|
+
export declare const flow: <C extends AnyFlowFn[]>(...chain: C) => (param: FlowChainArg<C>) => FlowResult<C, FlowChainArg<C>>;
|
|
5
11
|
export declare const fnIf: <T1, T2>(condition: boolean, trueValue: T1, falseValue: T2) => T1 | T2;
|
|
6
12
|
export declare const mapFind: <I, R>(array: I[], mapper: (item: I) => R, predicate?: (result: R) => boolean) => R | undefined;
|
|
7
13
|
declare type HashValue = string | number | boolean | null | HashCompoundValue;
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.tuple = exports.merge = exports.getCommonProperties = exports.roundToPrecision = exports.memoize = exports.partitionSequence = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
6
|
+
exports.tuple = exports.merge = exports.getCommonProperties = exports.roundToPrecision = exports.memoize = exports.partitionSequence = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.flow = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
7
7
|
const crypto_1 = require("crypto");
|
|
8
8
|
const deep_equal_1 = __importDefault(require("deep-equal"));
|
|
9
9
|
const guards_1 = require("./guards");
|
|
@@ -26,6 +26,17 @@ const getKeyValueOr = (key, defaultValue) => (obj) => obj[key] || defaultValue;
|
|
|
26
26
|
exports.getKeyValueOr = getKeyValueOr;
|
|
27
27
|
const putKeyValue = (key) => (obj, value) => ({ ...obj, [key]: value });
|
|
28
28
|
exports.putKeyValue = putKeyValue;
|
|
29
|
+
/*
|
|
30
|
+
* this is like lodash/flow but it uses a recursive type instead of hard-coding parameters
|
|
31
|
+
*/
|
|
32
|
+
const flow = (...chain) => (param) => {
|
|
33
|
+
let result = param;
|
|
34
|
+
for (const fn of chain) {
|
|
35
|
+
result = fn(result);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
exports.flow = flow;
|
|
29
40
|
/*
|
|
30
41
|
* a shameful helper to avoid needing to test code coverage of branches
|
|
31
42
|
*/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { GenericFetch } from './fetch';
|
|
2
|
+
import { ApiResponse, HttpHeaders } from './routing';
|
|
3
|
+
export interface Call {
|
|
4
|
+
start: number;
|
|
5
|
+
end?: number;
|
|
6
|
+
children: Array<Profile | ProfileData>;
|
|
7
|
+
}
|
|
8
|
+
export declare type ProfileFunction = <Fn extends (...args: any[]) => any>(trackName: string, fn: (p: Track) => Fn) => Fn;
|
|
9
|
+
export interface Track {
|
|
10
|
+
track: ProfileFunction;
|
|
11
|
+
trackFetch: <F extends GenericFetch>(fetch: F) => F;
|
|
12
|
+
setTracing: (tracing: boolean | Promise<boolean>) => void;
|
|
13
|
+
getTracing: () => Promise<undefined | boolean>;
|
|
14
|
+
data: () => Call;
|
|
15
|
+
report: () => CallReport;
|
|
16
|
+
adopt: (child: Profile) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface ProfileData {
|
|
19
|
+
name: string;
|
|
20
|
+
calls: Call[];
|
|
21
|
+
}
|
|
22
|
+
export interface Profile extends ProfileData {
|
|
23
|
+
report: () => ProfileReport;
|
|
24
|
+
start: () => Track & {
|
|
25
|
+
end: () => void;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface CallReport {
|
|
29
|
+
time: number | string;
|
|
30
|
+
children: ProfileReport[];
|
|
31
|
+
coverage: 'unknown' | {
|
|
32
|
+
accountedTime: number;
|
|
33
|
+
unaccountedTime: number;
|
|
34
|
+
accountedPercent: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
interface ProfileReport {
|
|
38
|
+
name: string;
|
|
39
|
+
time: 'unknown' | {
|
|
40
|
+
total: number;
|
|
41
|
+
min: number;
|
|
42
|
+
max: number;
|
|
43
|
+
avg: number;
|
|
44
|
+
};
|
|
45
|
+
calls: CallReport[];
|
|
46
|
+
}
|
|
47
|
+
export declare const createProfile: (name: string, options?: {
|
|
48
|
+
trace?: boolean | Promise<boolean>;
|
|
49
|
+
}) => Profile;
|
|
50
|
+
export declare const makeProfileApiResponseMiddleware: <Ri extends {
|
|
51
|
+
headers: HttpHeaders;
|
|
52
|
+
}>(showReport: (request: Ri) => string | boolean) => () => (responsePromise: Promise<ApiResponse<number, any>> | undefined, { request, profile }: {
|
|
53
|
+
request: Ri;
|
|
54
|
+
profile: Track;
|
|
55
|
+
}) => Promise<ApiResponse<number, any>> | undefined;
|
|
56
|
+
export declare const makeHtmlReport: (report: CallReport) => string;
|
|
57
|
+
export declare const makeHtmlProfileReport: (report: ProfileReport) => string;
|
|
58
|
+
export declare const makeHtmlCallReport: (report: CallReport, index: number) => string;
|
|
59
|
+
export {};
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeHtmlCallReport = exports.makeHtmlProfileReport = exports.makeHtmlReport = exports.makeProfileApiResponseMiddleware = exports.createProfile = void 0;
|
|
4
|
+
const guards_1 = require("./guards");
|
|
5
|
+
const routing_1 = require("./routing");
|
|
6
|
+
const _1 = require(".");
|
|
7
|
+
const callReport = (call) => {
|
|
8
|
+
const time = call.start && call.end
|
|
9
|
+
? call.end - call.start
|
|
10
|
+
: 'unknown';
|
|
11
|
+
const children = call.children
|
|
12
|
+
.filter(profile => profile.calls.length > 0)
|
|
13
|
+
.map(profileReport);
|
|
14
|
+
const accountedTime = children.reduce((result, profile) => typeof profile.time === 'string' ? result : result + profile.time.total, 0);
|
|
15
|
+
const coverage = typeof time === 'number' && children.length > 0 ? {
|
|
16
|
+
accountedTime,
|
|
17
|
+
unaccountedTime: time - accountedTime,
|
|
18
|
+
accountedPercent: (0, _1.roundToPrecision)(accountedTime / time, -2),
|
|
19
|
+
} : 'unknown';
|
|
20
|
+
return { time, children, coverage };
|
|
21
|
+
};
|
|
22
|
+
const profileReport = (profile) => {
|
|
23
|
+
const calls = profile.calls.map(callReport);
|
|
24
|
+
const times = calls
|
|
25
|
+
.map(call => call.time)
|
|
26
|
+
.filter(guards_1.isNumber);
|
|
27
|
+
const time = times.length > 0 ? {
|
|
28
|
+
total: times.reduce((result, item) => result + item, 0),
|
|
29
|
+
min: Math.min(...times),
|
|
30
|
+
max: Math.max(...times),
|
|
31
|
+
avg: Math.round(times.reduce((result, item) => result + item, 0) / times.length),
|
|
32
|
+
} : 'unknown';
|
|
33
|
+
return {
|
|
34
|
+
name: profile.name, calls, time
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const createProfile = (name, options = {}) => {
|
|
38
|
+
const profile = {
|
|
39
|
+
name,
|
|
40
|
+
calls: [],
|
|
41
|
+
report: () => profileReport(profile),
|
|
42
|
+
start: () => {
|
|
43
|
+
const call = { start: Date.now(), children: [] };
|
|
44
|
+
profile.calls.push(call);
|
|
45
|
+
return {
|
|
46
|
+
end: () => { call.end = Date.now(); },
|
|
47
|
+
report: () => callReport({ end: Date.now(), ...call }),
|
|
48
|
+
data: () => ({ end: Date.now(), ...call }),
|
|
49
|
+
adopt: (childProfile) => { call.children.push(childProfile); },
|
|
50
|
+
setTracing: (tracing) => { options.trace = tracing; },
|
|
51
|
+
getTracing: async () => options.trace,
|
|
52
|
+
trackFetch: (fetch) => {
|
|
53
|
+
const trackProfile = (0, exports.createProfile)('fetch', options);
|
|
54
|
+
call.children.push(trackProfile);
|
|
55
|
+
return (async (url, config) => {
|
|
56
|
+
const { end, ...track } = trackProfile.start();
|
|
57
|
+
const fetchConfig = await options.trace
|
|
58
|
+
? (0, _1.merge)(config || {}, {
|
|
59
|
+
headers: { 'x-application-profile': 'trace' }
|
|
60
|
+
})
|
|
61
|
+
: config;
|
|
62
|
+
return fetch(url, fetchConfig).then(response => {
|
|
63
|
+
const fetchProfileJson = response.headers.get('x-application-profile');
|
|
64
|
+
const fetchProfile = fetchProfileJson ? JSON.parse(fetchProfileJson) : undefined;
|
|
65
|
+
end();
|
|
66
|
+
if (fetchProfile) {
|
|
67
|
+
const subrequestProfile = (0, exports.createProfile)(url, options);
|
|
68
|
+
subrequestProfile.calls.push(fetchProfile);
|
|
69
|
+
track.adopt(subrequestProfile);
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
track: (trackName, fn) => {
|
|
76
|
+
const trackProfile = (0, exports.createProfile)(trackName, options);
|
|
77
|
+
call.children.push(trackProfile);
|
|
78
|
+
return ((...args) => {
|
|
79
|
+
const { end, ...track } = trackProfile.start();
|
|
80
|
+
const result = fn(track)(...args);
|
|
81
|
+
if (result instanceof Promise) {
|
|
82
|
+
return result.finally(() => { end(); });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
end();
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
return profile;
|
|
94
|
+
};
|
|
95
|
+
exports.createProfile = createProfile;
|
|
96
|
+
const makeProfileApiResponseMiddleware = (showReport) => () => (responsePromise, { request, profile }) => {
|
|
97
|
+
if (responsePromise) {
|
|
98
|
+
return responsePromise.then(async (response) => {
|
|
99
|
+
const reportFormat = showReport(request);
|
|
100
|
+
const enabled = await profile.getTracing();
|
|
101
|
+
// profiling is disabled, return original response
|
|
102
|
+
if (!enabled) {
|
|
103
|
+
return response;
|
|
104
|
+
}
|
|
105
|
+
// there is no report format, return original response but
|
|
106
|
+
// add sub-request trace response if necessary
|
|
107
|
+
if (reportFormat === false) {
|
|
108
|
+
if ((0, routing_1.getHeader)(request.headers, 'x-application-profile')) {
|
|
109
|
+
return {
|
|
110
|
+
...response,
|
|
111
|
+
headers: {
|
|
112
|
+
...response.headers,
|
|
113
|
+
'x-application-profile': JSON.stringify(profile.data())
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return response;
|
|
118
|
+
// override response with html report
|
|
119
|
+
}
|
|
120
|
+
else if (reportFormat === 'html') {
|
|
121
|
+
return (0, routing_1.apiHtmlResponse)(200, (0, exports.makeHtmlReport)(profile.report()));
|
|
122
|
+
}
|
|
123
|
+
// override response with json report
|
|
124
|
+
return (0, routing_1.apiJsonResponse)(200, profile.report());
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return responsePromise;
|
|
128
|
+
};
|
|
129
|
+
exports.makeProfileApiResponseMiddleware = makeProfileApiResponseMiddleware;
|
|
130
|
+
const makeHtmlReport = (report) => `
|
|
131
|
+
<head>
|
|
132
|
+
<style>
|
|
133
|
+
table {
|
|
134
|
+
border-collapse: collapse;
|
|
135
|
+
}
|
|
136
|
+
table td,th {
|
|
137
|
+
border: 1px solid #ccc;
|
|
138
|
+
padding: 10px;
|
|
139
|
+
}
|
|
140
|
+
summary {
|
|
141
|
+
padding: 10px 0;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
144
|
+
</head>
|
|
145
|
+
<body>
|
|
146
|
+
${(0, exports.makeHtmlCallReport)(report, 0)}
|
|
147
|
+
</body>
|
|
148
|
+
`;
|
|
149
|
+
exports.makeHtmlReport = makeHtmlReport;
|
|
150
|
+
const makeHtmlProfileReport = (report) => `
|
|
151
|
+
<details>
|
|
152
|
+
<summary>Profile: ${report.name}</summary>
|
|
153
|
+
<table>
|
|
154
|
+
<thead>
|
|
155
|
+
<tr>
|
|
156
|
+
<th>call #</th>
|
|
157
|
+
<th>children</th>
|
|
158
|
+
<th>time</th>
|
|
159
|
+
<th>time coverage (accounted/unaccounted/percent)</th>
|
|
160
|
+
</tr>
|
|
161
|
+
</thead>
|
|
162
|
+
<tbody>
|
|
163
|
+
${report.calls.map((call, i) => `<tr>
|
|
164
|
+
<td>${i + 1}</td>
|
|
165
|
+
<td>${call.children.length}</td>
|
|
166
|
+
<td>${call.time}</td>
|
|
167
|
+
<td>${typeof call.coverage === 'string' ? call.coverage : `${call.coverage.accountedTime}/${call.coverage.unaccountedTime}/${call.coverage.accountedPercent}`}</td>
|
|
168
|
+
</tr>`)}
|
|
169
|
+
</tbody>
|
|
170
|
+
</table>
|
|
171
|
+
<div style="padding-left: 20px; border-left: 1px solid #ccc;">
|
|
172
|
+
${report.calls.map(exports.makeHtmlCallReport).join('')}
|
|
173
|
+
</div>
|
|
174
|
+
</details>
|
|
175
|
+
`;
|
|
176
|
+
exports.makeHtmlProfileReport = makeHtmlProfileReport;
|
|
177
|
+
const makeHtmlCallReport = (report, index) => `
|
|
178
|
+
<h4>call # ${index + 1} children<h4>
|
|
179
|
+
<table>
|
|
180
|
+
<thead>
|
|
181
|
+
<tr>
|
|
182
|
+
<th>name</th>
|
|
183
|
+
<th>calls</th>
|
|
184
|
+
<th>time (total (min/max/avg))</th>
|
|
185
|
+
</tr>
|
|
186
|
+
</thead>
|
|
187
|
+
<tbody>
|
|
188
|
+
${report.children.map(profile => `<tr>
|
|
189
|
+
<td>${profile.name}</td>
|
|
190
|
+
<td>${profile.calls.length}</td>
|
|
191
|
+
<td>${typeof profile.time === 'string' ? profile.time : `${profile.time.total} (${profile.time.min}/${profile.time.max}/${profile.time.avg})`}</td>
|
|
192
|
+
</tr>`).join('')}
|
|
193
|
+
</tbody>
|
|
194
|
+
</table>
|
|
195
|
+
<div style="margin-left: 20px">
|
|
196
|
+
${report.children.map(exports.makeHtmlProfileReport).join('')}
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
exports.makeHtmlCallReport = makeHtmlCallReport;
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Track } from './profile';
|
|
1
2
|
export declare type QueryParams = Record<string, string | undefined>;
|
|
2
3
|
export declare type RouteParams = {
|
|
3
4
|
[key: string]: string;
|
|
@@ -17,6 +18,7 @@ export declare type PayloadForRoute<R> = RequestServicesForRoute<R> extends {
|
|
|
17
18
|
} ? RequestServicesForRoute<R>['payload'] : undefined;
|
|
18
19
|
declare type RequestServiceProvider<Sa, Sr, Ri> = (app: Sa) => <R>(middleware: {
|
|
19
20
|
request: Ri;
|
|
21
|
+
profile: Track;
|
|
20
22
|
}, match: RouteMatchRecord<R>) => Sr;
|
|
21
23
|
declare type RouteHandler<P, Sr, Ro> = (params: P, request: Sr) => Ro;
|
|
22
24
|
declare type Route<N extends string, P extends RouteParams | undefined, Sa, Sr, Ri, Ro> = (Sr extends undefined ? {
|
|
@@ -51,7 +53,10 @@ declare type RequestPathExtractor<Ri> = (request: Ri) => string;
|
|
|
51
53
|
declare type RequestRouteMatcher<Ri, R> = (request: Ri, route: R) => boolean;
|
|
52
54
|
declare type RequestResponder<Sa, Ri, Ro> = {
|
|
53
55
|
(services: Sa): (request: Ri) => Ro | undefined;
|
|
54
|
-
<RoF>(services: Sa, responseMiddleware: (app: Sa) => (response: Ro | undefined, request:
|
|
56
|
+
<RoF>(services: Sa, responseMiddleware: (app: Sa) => (response: Ro | undefined, request: {
|
|
57
|
+
request: Ri;
|
|
58
|
+
profile: Track;
|
|
59
|
+
}) => RoF): (request: Ri) => RoF;
|
|
55
60
|
};
|
|
56
61
|
export declare const makeGetRequestResponder: <Sa, Ru, Ri, Ro>() => ({ routes, pathExtractor, routeMatcher, errorHandler }: {
|
|
57
62
|
routes: () => AnySpecificRoute<Ru, Sa, Ri, Ro>[];
|
|
@@ -77,6 +82,7 @@ export declare type ApiResponse<S extends number, T> = {
|
|
|
77
82
|
};
|
|
78
83
|
export declare const apiJsonResponse: <S extends number, T extends JsonCompatibleStruct>(statusCode: S, data: T, headers?: HttpHeaders | undefined) => ApiResponse<S, T>;
|
|
79
84
|
export declare const apiTextResponse: <S extends number>(statusCode: S, data: string, headers?: HttpHeaders | undefined) => ApiResponse<S, string>;
|
|
85
|
+
export declare const apiHtmlResponse: <S extends number>(statusCode: S, data: string, headers?: HttpHeaders | undefined) => ApiResponse<S, string>;
|
|
80
86
|
export declare enum METHOD {
|
|
81
87
|
GET = "GET",
|
|
82
88
|
HEAD = "HEAD",
|
package/dist/routing.js
CHANGED
|
@@ -26,12 +26,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.requestPayloadProvider = exports.unsafePayloadValidator = exports.getRequestBody = exports.getHeader = exports.METHOD = exports.apiTextResponse = exports.apiJsonResponse = exports.makeGetRequestResponder = exports.renderAnyRouteUrl = exports.makeRenderRouteUrl = exports.makeCreateRoute = void 0;
|
|
29
|
+
exports.requestPayloadProvider = exports.unsafePayloadValidator = exports.getRequestBody = exports.getHeader = exports.METHOD = exports.apiHtmlResponse = exports.apiTextResponse = exports.apiJsonResponse = exports.makeGetRequestResponder = exports.renderAnyRouteUrl = exports.makeRenderRouteUrl = exports.makeCreateRoute = void 0;
|
|
30
30
|
const pathToRegexp = __importStar(require("path-to-regexp"));
|
|
31
31
|
const query_string_1 = __importDefault(require("query-string"));
|
|
32
32
|
const assertions_1 = require("./assertions");
|
|
33
33
|
const errors_1 = require("./errors");
|
|
34
34
|
const guards_1 = require("./guards");
|
|
35
|
+
const profile_1 = require("./profile");
|
|
35
36
|
const _1 = require(".");
|
|
36
37
|
/*
|
|
37
38
|
* route definition helper. the only required params of the route are the name, path, and handler. other params
|
|
@@ -92,11 +93,11 @@ exports.renderAnyRouteUrl = (0, exports.makeRenderRouteUrl)();
|
|
|
92
93
|
const bindRoute = (services, appBinder, pathExtractor, matcher) => (route) => {
|
|
93
94
|
const getParamsFromPath = pathToRegexp.match(route.path, { decode: decodeURIComponent });
|
|
94
95
|
const boundServiceProvider = route.requestServiceProvider && appBinder(services, route.requestServiceProvider);
|
|
95
|
-
return (request) => {
|
|
96
|
+
return (request, profile) => {
|
|
96
97
|
const path = pathExtractor(request);
|
|
97
98
|
const match = getParamsFromPath(path);
|
|
98
99
|
if ((!matcher || matcher(request, route)) && match) {
|
|
99
|
-
return () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request }, { route, params: match.params }) : undefined);
|
|
100
|
+
return profile.track(route.name, routeProfile => () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, profile: routeProfile }, { route, params: match.params }) : undefined));
|
|
100
101
|
}
|
|
101
102
|
};
|
|
102
103
|
};
|
|
@@ -127,20 +128,21 @@ const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatcher, er
|
|
|
127
128
|
// *note* this opaque promise guard is less generic than i hoped so
|
|
128
129
|
// i'm leaving it here instead of the guards file.
|
|
129
130
|
//
|
|
130
|
-
// its less than ideal
|
|
131
|
+
// its less than ideal because it enforces that the handlers return
|
|
131
132
|
// the same type as the parent promise, usually a handler can be either a
|
|
132
133
|
// promise or a non-promise value and the promise figures it out, but those
|
|
133
134
|
// types are getting complicated quickly here.
|
|
134
135
|
const isPromise = (thing) => thing instanceof Promise;
|
|
135
136
|
return (request) => {
|
|
137
|
+
const { end, ...profile } = (0, profile_1.createProfile)(new Date().toISOString()).start();
|
|
136
138
|
try {
|
|
137
|
-
const executor = (0, _1.mapFind)(boundRoutes, (route) => route(request));
|
|
139
|
+
const executor = (0, _1.mapFind)(boundRoutes, (route) => route(request, profile));
|
|
138
140
|
if (executor) {
|
|
139
141
|
const result = boundResponseMiddleware ?
|
|
140
|
-
boundResponseMiddleware(executor(), request) : executor();
|
|
142
|
+
boundResponseMiddleware(executor(), { request, profile }) : executor();
|
|
141
143
|
if (isPromise(result) && errorHandler) {
|
|
142
144
|
const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
|
|
143
|
-
boundResponseMiddleware(errorHandler(e), request) : errorHandler(e);
|
|
145
|
+
boundResponseMiddleware(errorHandler(e), { request, profile }) : errorHandler(e);
|
|
144
146
|
return result.catch(errorHandlerWithMiddleware);
|
|
145
147
|
}
|
|
146
148
|
else {
|
|
@@ -148,12 +150,12 @@ const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatcher, er
|
|
|
148
150
|
}
|
|
149
151
|
}
|
|
150
152
|
else if (boundResponseMiddleware) {
|
|
151
|
-
return boundResponseMiddleware(undefined, request);
|
|
153
|
+
return boundResponseMiddleware(undefined, { request, profile });
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
catch (e) {
|
|
155
157
|
if (errorHandler && e instanceof Error) {
|
|
156
|
-
return boundResponseMiddleware ? boundResponseMiddleware(errorHandler(e), request) : errorHandler(e);
|
|
158
|
+
return boundResponseMiddleware ? boundResponseMiddleware(errorHandler(e), { request, profile }) : errorHandler(e);
|
|
157
159
|
}
|
|
158
160
|
throw e;
|
|
159
161
|
}
|
|
@@ -165,6 +167,8 @@ const apiJsonResponse = (statusCode, data, headers) => ({ statusCode, data, body
|
|
|
165
167
|
exports.apiJsonResponse = apiJsonResponse;
|
|
166
168
|
const apiTextResponse = (statusCode, data, headers) => ({ statusCode, data, body: data, headers: { ...headers, 'content-type': 'text/plain' } });
|
|
167
169
|
exports.apiTextResponse = apiTextResponse;
|
|
170
|
+
const apiHtmlResponse = (statusCode, data, headers) => ({ statusCode, data, body: data, headers: { ...headers, 'content-type': 'text/html' } });
|
|
171
|
+
exports.apiHtmlResponse = apiHtmlResponse;
|
|
168
172
|
var METHOD;
|
|
169
173
|
(function (METHOD) {
|
|
170
174
|
METHOD["GET"] = "GET";
|
|
@@ -71,9 +71,15 @@ const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
|
|
|
71
71
|
* requests user identity from accounts api using given token or cookie
|
|
72
72
|
*/
|
|
73
73
|
const getFetchUser = async () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
const response = await window.fetch((await accountsUrl).replace(/\/+$/, '') + '/accounts/api/user', getAuthorizedFetchConfigFromData(userData));
|
|
75
|
+
if (response.status === 200) {
|
|
76
|
+
return { ...userData, user: await response.json() };
|
|
77
|
+
}
|
|
78
|
+
if (response.status === 403) {
|
|
79
|
+
return { ...userData, user: undefined };
|
|
80
|
+
}
|
|
81
|
+
const message = await response.text();
|
|
82
|
+
throw new Error(`Error response from Accounts ${response.status}: ${message}`);
|
|
77
83
|
};
|
|
78
84
|
const getUserData = (0, __1.once)(async () => {
|
|
79
85
|
userData = authQuery === embeddedQueryValue
|
|
@@ -23,16 +23,16 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
-
return (request) => {
|
|
26
|
+
return ({ request, profile }) => {
|
|
27
27
|
let user;
|
|
28
|
-
const getAuthorizedFetchConfig = async () => {
|
|
28
|
+
const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
|
|
29
29
|
const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
|
|
30
30
|
if (!token) {
|
|
31
31
|
return {};
|
|
32
32
|
}
|
|
33
33
|
return { headers };
|
|
34
|
-
};
|
|
35
|
-
const loadUser = async () => {
|
|
34
|
+
});
|
|
35
|
+
const loadUser = profile.track('loadUser', () => async () => {
|
|
36
36
|
const [token] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
|
|
37
37
|
if (!token) {
|
|
38
38
|
return undefined;
|
|
@@ -49,7 +49,7 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
51
51
|
return jwt.sub;
|
|
52
|
-
};
|
|
52
|
+
});
|
|
53
53
|
return {
|
|
54
54
|
getAuthorizedFetchConfig,
|
|
55
55
|
getUser: async () => {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { FetchConfig } from '../../fetch';
|
|
2
|
+
import { Track } from '../../profile';
|
|
2
3
|
import { HttpHeaders } from '../../routing';
|
|
3
4
|
export interface User {
|
|
5
|
+
id: number;
|
|
4
6
|
name: string;
|
|
5
7
|
first_name: string;
|
|
6
8
|
last_name: string;
|
|
7
9
|
full_name: string;
|
|
8
10
|
uuid: string;
|
|
9
11
|
faculty_status: string;
|
|
10
|
-
is_administrator:
|
|
11
|
-
is_not_gdpr_location:
|
|
12
|
+
is_administrator: boolean;
|
|
13
|
+
is_not_gdpr_location: boolean;
|
|
12
14
|
contact_infos: Array<{
|
|
13
15
|
type: string;
|
|
14
16
|
value: string;
|
|
@@ -27,7 +29,10 @@ export declare type CookieAuthProviderRequest = {
|
|
|
27
29
|
headers: HttpHeaders;
|
|
28
30
|
cookies?: string[];
|
|
29
31
|
};
|
|
30
|
-
export declare type CookieAuthProvider = (
|
|
32
|
+
export declare type CookieAuthProvider = (inputs: {
|
|
33
|
+
request: CookieAuthProviderRequest;
|
|
34
|
+
profile: Track;
|
|
35
|
+
}) => AuthProvider;
|
|
31
36
|
export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
|
|
32
37
|
export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
|
|
33
38
|
export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest, cookieName: string) => [string, {
|
|
@@ -8,23 +8,23 @@ const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
8
8
|
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'subrequest')];
|
|
9
9
|
const cookieName = (0, config_1.resolveConfigValue)(config.cookieName);
|
|
10
10
|
const accountsUrl = (0, config_1.resolveConfigValue)(config.accountsUrl);
|
|
11
|
-
return (request) => {
|
|
11
|
+
return ({ request, profile }) => {
|
|
12
12
|
let user;
|
|
13
|
-
const getAuthorizedFetchConfig = async () => {
|
|
13
|
+
const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
|
|
14
14
|
const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
|
|
15
15
|
if (!token) {
|
|
16
16
|
return {};
|
|
17
17
|
}
|
|
18
18
|
return { headers };
|
|
19
|
-
};
|
|
20
|
-
const loadUser = async () => {
|
|
19
|
+
});
|
|
20
|
+
const loadUser = profile.track('loadUser', p => async () => {
|
|
21
21
|
const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
|
|
22
22
|
if (!token) {
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
|
-
return initializer.fetch(await accountsUrl, { headers })
|
|
25
|
+
return p.trackFetch(initializer.fetch)(await accountsUrl, { headers })
|
|
26
26
|
.then(response => response.json());
|
|
27
|
-
};
|
|
27
|
+
});
|
|
28
28
|
return {
|
|
29
29
|
getAuthorizedFetchConfig,
|
|
30
30
|
getUser: async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
+
import { Track } from '../../profile';
|
|
2
3
|
import { TDocument, VersionedDocumentAuthor } from '.';
|
|
3
4
|
declare type DynamoConfig = {
|
|
4
5
|
tableName: string;
|
|
@@ -8,7 +9,9 @@ interface Initializer<C> {
|
|
|
8
9
|
}
|
|
9
10
|
export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
|
|
10
11
|
tableName: import("../../config").ConfigValueProvider<string>;
|
|
11
|
-
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(
|
|
12
|
+
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({ profile }: {
|
|
13
|
+
profile: Track;
|
|
14
|
+
}, hashKey: K, getAuthor: A) => {
|
|
12
15
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
13
16
|
getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
|
|
14
17
|
items: T[];
|
|
@@ -51,11 +51,11 @@ const decodeDynamoAttribute = (value) => {
|
|
|
51
51
|
};
|
|
52
52
|
const decodeDynamoDocument = (document) => Object.fromEntries(Object.entries(document).map(([key, value]) => ([key, decodeDynamoAttribute(value)])));
|
|
53
53
|
// i'm not really excited about getAuthor being required, but ts is getting confused about the type when unspecified
|
|
54
|
-
const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => (hashKey, getAuthor) => {
|
|
54
|
+
const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => ({ profile }, hashKey, getAuthor) => {
|
|
55
55
|
const options = (0, guards_1.ifDefined)(initializer, {});
|
|
56
56
|
const tableName = (0, config_1.resolveConfigValue)(configProvider[(0, guards_1.ifDefined)(options.configSpace, 'dynamodb')].tableName);
|
|
57
57
|
return {
|
|
58
|
-
loadAllDocumentsTheBadWay: async () => {
|
|
58
|
+
loadAllDocumentsTheBadWay: profile.track('versionedDocumentStore.loadAllDocumentsTheBadWay', () => async () => {
|
|
59
59
|
const loadAllResults = async (ExclusiveStartKey) => {
|
|
60
60
|
var _a;
|
|
61
61
|
const cmd = new client_dynamodb_1.ScanCommand({ TableName: await tableName, ExclusiveStartKey });
|
|
@@ -74,8 +74,8 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
|
|
|
74
74
|
return result;
|
|
75
75
|
}, new Map()));
|
|
76
76
|
return Array.from(allResults.values());
|
|
77
|
-
},
|
|
78
|
-
getVersions: async (id, startVersion) => {
|
|
77
|
+
}),
|
|
78
|
+
getVersions: profile.track('versionedDocumentStore.getVersions', () => async (id, startVersion) => {
|
|
79
79
|
const cmd = new client_dynamodb_1.QueryCommand({
|
|
80
80
|
TableName: await tableName,
|
|
81
81
|
KeyConditionExpression: '#hk = :hkv',
|
|
@@ -104,8 +104,8 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
|
|
|
104
104
|
nextPageToken: result.LastEvaluatedKey ? decodeDynamoDocument(result.LastEvaluatedKey).timestamp : undefined
|
|
105
105
|
};
|
|
106
106
|
});
|
|
107
|
-
},
|
|
108
|
-
getItem: async (id, timestamp) => {
|
|
107
|
+
}),
|
|
108
|
+
getItem: profile.track('versionedDocumentStore.getItem', () => async (id, timestamp) => {
|
|
109
109
|
let keyConditionExpression = '#hk = :hkv';
|
|
110
110
|
const expressionAttributeNames = {
|
|
111
111
|
'#hk': hashKey.toString()
|
|
@@ -130,8 +130,8 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
|
|
|
130
130
|
var _a;
|
|
131
131
|
return (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)[0];
|
|
132
132
|
});
|
|
133
|
-
},
|
|
134
|
-
putItem: async (item, ...authorArgs) => {
|
|
133
|
+
}),
|
|
134
|
+
putItem: profile.track('versionedDocumentStore.putItem', () => async (item, ...authorArgs) => {
|
|
135
135
|
// this getAuthor type is terrible
|
|
136
136
|
const author = getAuthor ? await getAuthor(...authorArgs) : authorArgs[0];
|
|
137
137
|
const document = {
|
|
@@ -145,7 +145,7 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
|
|
|
145
145
|
});
|
|
146
146
|
return dynamodb().send(cmd)
|
|
147
147
|
.then(() => document);
|
|
148
|
-
},
|
|
148
|
+
}),
|
|
149
149
|
};
|
|
150
150
|
};
|
|
151
151
|
exports.dynamoVersionedDocumentStore = dynamoVersionedDocumentStore;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
+
import { Track } from '../../profile';
|
|
2
3
|
import { TDocument, VersionedDocumentAuthor } from '.';
|
|
3
4
|
declare type Config = {
|
|
4
5
|
tableName: string;
|
|
@@ -10,7 +11,9 @@ interface Initializer<C> {
|
|
|
10
11
|
}
|
|
11
12
|
export declare const fileSystemVersionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
|
|
12
13
|
tableName: import("../../config").ConfigValueProvider<string>;
|
|
13
|
-
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(
|
|
14
|
+
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({ profile }: {
|
|
15
|
+
profile: Track;
|
|
16
|
+
}, hashKey: K, getAuthor: A) => {
|
|
14
17
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
15
18
|
getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
|
|
16
19
|
items: T[];
|