@openstax/ts-utils 1.1.11 → 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 CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare const isDefined: <X>(x: X) => x is Exclude<X, undefined>;
2
+ export declare const isNumber: (x: any) => x is number;
2
3
  export declare const isPlainObject: (thing: any) => thing is {
3
4
  [key: string]: any;
4
5
  };
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 { [key in K]?: any; }>(obj: O) => V | NonNullable<O[K]>;
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 {};
@@ -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: Ri) => RoF): (request: Ri) => RoF;
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 than because it enforces that the handlers return
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";
@@ -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: string;
11
- is_not_gdpr_location: string;
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 = (request: CookieAuthProviderRequest) => AuthProvider;
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>(hashKey: K, getAuthor: A) => {
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>(hashKey: K, getAuthor: A) => {
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[];