@openstax/ts-utils 1.1.30 → 1.1.32
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/cjs/index.d.ts +4 -29
- package/dist/cjs/index.js +17 -228
- package/dist/cjs/misc/hashValue.d.ts +6 -0
- package/dist/cjs/misc/hashValue.js +18 -0
- package/dist/cjs/misc/helpers.d.ts +17 -0
- package/dist/cjs/misc/helpers.js +129 -0
- package/dist/cjs/misc/merge.d.ts +3 -0
- package/dist/cjs/misc/merge.js +38 -0
- package/dist/cjs/misc/partitionSequence.d.ts +4 -0
- package/dist/cjs/misc/partitionSequence.js +55 -0
- package/dist/cjs/routing/helpers.d.ts +12 -0
- package/dist/cjs/routing/helpers.js +72 -0
- package/dist/cjs/routing/index.d.ts +96 -0
- package/dist/cjs/routing/index.js +182 -0
- package/dist/cjs/services/authProvider/decryption.js +4 -4
- package/dist/cjs/tsconfig.withoutspecs.cjs.tsbuildinfo +1 -1
- package/dist/esm/index.d.ts +4 -29
- package/dist/esm/index.js +4 -210
- package/dist/esm/misc/hashValue.d.ts +6 -0
- package/dist/esm/misc/hashValue.js +14 -0
- package/dist/esm/misc/helpers.d.ts +17 -0
- package/dist/esm/misc/helpers.js +115 -0
- package/dist/esm/misc/merge.d.ts +3 -0
- package/dist/esm/misc/merge.js +33 -0
- package/dist/esm/misc/partitionSequence.d.ts +4 -0
- package/dist/esm/misc/partitionSequence.js +48 -0
- package/dist/esm/routing/helpers.d.ts +12 -0
- package/dist/esm/routing/helpers.js +65 -0
- package/dist/esm/routing/index.d.ts +96 -0
- package/dist/esm/routing/index.js +144 -0
- package/dist/esm/services/authProvider/decryption.js +1 -1
- package/dist/esm/tsconfig.withoutspecs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Track } from '../profile';
|
|
2
|
+
export declare type QueryParams = Record<string, string | undefined | string[] | null>;
|
|
3
|
+
export declare type RouteParams = {
|
|
4
|
+
[key: string]: string;
|
|
5
|
+
};
|
|
6
|
+
export declare type AnyRoute<R> = R extends Route<infer N, infer P, infer Sa, infer Sr, infer Ri, infer Ro> ? Route<N, P, Sa, Sr, Ri, Ro> : never;
|
|
7
|
+
export declare type AnySpecificRoute<R, Sa, Ri, Ro> = R extends Route<infer N, infer P, Sa, infer Sr, Ri, Ro> & infer E ? Route<N, P, Sa, Sr, Ri, Ro> & E : never;
|
|
8
|
+
export declare type OutputForRoute<R> = R extends Route<any, any, any, any, any, infer Ro> ? Ro : never;
|
|
9
|
+
export declare type ParamsForRoute<R> = R extends Route<any, infer P, any, any, any, any> ? P : never;
|
|
10
|
+
export declare type RequestServicesForRoute<R> = R extends Route<any, any, any, infer Sr, any, any> ? Sr : never;
|
|
11
|
+
export declare type ParamsForRouteOrEmpty<R> = ParamsForRoute<R> extends undefined ? {} : Exclude<ParamsForRoute<R>, undefined>;
|
|
12
|
+
export declare type RouteMatchRecord<R> = R extends AnyRoute<R> ? {
|
|
13
|
+
route: R;
|
|
14
|
+
params: ParamsForRoute<R>;
|
|
15
|
+
} : never;
|
|
16
|
+
export declare type PayloadForRoute<R> = RequestServicesForRoute<R> extends {
|
|
17
|
+
payload: any;
|
|
18
|
+
} ? RequestServicesForRoute<R>['payload'] : undefined;
|
|
19
|
+
declare type RequestServiceProvider<Sa, Sr, Ri> = (app: Sa) => <R>(middleware: {
|
|
20
|
+
request: Ri;
|
|
21
|
+
profile: Track;
|
|
22
|
+
}, match: RouteMatchRecord<R>) => Sr;
|
|
23
|
+
declare type RouteHandler<P, Sr, Ro> = (params: P, request: Sr) => Ro;
|
|
24
|
+
declare type Route<N extends string, P extends RouteParams | undefined, Sa, Sr, Ri, Ro> = (Sr extends undefined ? {
|
|
25
|
+
requestServiceProvider?: RequestServiceProvider<Sa, Sr, Ri> | undefined;
|
|
26
|
+
} : {
|
|
27
|
+
requestServiceProvider: RequestServiceProvider<Sa, Sr, Ri>;
|
|
28
|
+
}) & {
|
|
29
|
+
name: N;
|
|
30
|
+
path: string;
|
|
31
|
+
handler: (params: P, request: Sr) => Ro;
|
|
32
|
+
};
|
|
33
|
+
declare type CreateRouteConfig<Sa, Sr, Ri, N extends string, Ex> = (Sr extends undefined ? {
|
|
34
|
+
requestServiceProvider?: RequestServiceProvider<Sa, Sr, Ri> | undefined;
|
|
35
|
+
} : {
|
|
36
|
+
requestServiceProvider: RequestServiceProvider<Sa, Sr, Ri>;
|
|
37
|
+
}) & {
|
|
38
|
+
name: N;
|
|
39
|
+
path: string;
|
|
40
|
+
} & Ex;
|
|
41
|
+
export interface CreateRoute<Sa, Ri, Ex> {
|
|
42
|
+
<N extends string, Ro, Sr extends unknown | undefined = undefined, P extends RouteParams | undefined = undefined>(config: CreateRouteConfig<Sa, Sr, Ri, N, Ex> & {
|
|
43
|
+
handler: RouteHandler<P, Sr, Ro>;
|
|
44
|
+
}): Route<N, P, Sa, Sr, Ri, Ro> & Ex;
|
|
45
|
+
<N extends string, Ro, Sr extends unknown | undefined, P extends RouteParams | undefined = undefined>(config: CreateRouteConfig<Sa, Sr, Ri, N, Ex>, handler: RouteHandler<P, Sr, Ro>): Route<N, P, Sa, Sr, Ri, Ro> & Ex;
|
|
46
|
+
}
|
|
47
|
+
export declare const makeCreateRoute: <Sa, Ri, Ex = {}>() => CreateRoute<Sa, Ri, Ex>;
|
|
48
|
+
export declare const makeRenderRouteUrl: <Ru extends {
|
|
49
|
+
path: string;
|
|
50
|
+
}>() => <R extends Ru>(route: R, params: ParamsForRoute<R>, query?: QueryParams) => string;
|
|
51
|
+
export declare const renderAnyRouteUrl: <R extends any>(route: R, params: ParamsForRoute<R>, query?: QueryParams) => string;
|
|
52
|
+
declare type RequestPathExtractor<Ri> = (request: Ri) => string;
|
|
53
|
+
declare type RequestRouteMatcher<Ri, R> = (request: Ri, route: R) => boolean;
|
|
54
|
+
declare type RequestResponder<Sa, Ri, Ro> = {
|
|
55
|
+
(services: Sa): (request: Ri) => Ro | undefined;
|
|
56
|
+
<RoF>(services: Sa, responseMiddleware: (app: Sa) => (response: Ro | undefined, request: {
|
|
57
|
+
request: Ri;
|
|
58
|
+
profile: Track;
|
|
59
|
+
}) => RoF): (request: Ri) => RoF;
|
|
60
|
+
};
|
|
61
|
+
export declare const makeGetRequestResponder: <Sa, Ru, Ri, Ro>() => ({ routes, pathExtractor, routeMatcher, errorHandler }: {
|
|
62
|
+
routes: () => AnySpecificRoute<Ru, Sa, Ri, Ro>[];
|
|
63
|
+
pathExtractor: RequestPathExtractor<Ri>;
|
|
64
|
+
routeMatcher?: RequestRouteMatcher<Ri, AnySpecificRoute<Ru, Sa, Ri, Ro>> | undefined;
|
|
65
|
+
errorHandler?: ((e: Error) => Ro) | undefined;
|
|
66
|
+
}) => RequestResponder<Sa, Ri, Ro>;
|
|
67
|
+
export declare type HttpHeaders = {
|
|
68
|
+
[key: string]: string | undefined | string[];
|
|
69
|
+
};
|
|
70
|
+
export declare type JsonCompatibleValue = string | number | null | undefined | boolean;
|
|
71
|
+
export declare type JsonCompatibleArray = Array<JsonCompatibleValue | JsonCompatibleStruct | JsonCompatibleStruct>;
|
|
72
|
+
export declare type JsonCompatibleStruct = {
|
|
73
|
+
[key: string]: JsonCompatibleStruct | JsonCompatibleValue | JsonCompatibleArray;
|
|
74
|
+
};
|
|
75
|
+
export declare type ApiResponse<S extends number, T> = {
|
|
76
|
+
isBase64Encoded?: boolean;
|
|
77
|
+
statusCode: S;
|
|
78
|
+
data: T;
|
|
79
|
+
body: string;
|
|
80
|
+
headers?: {
|
|
81
|
+
[key: string]: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
export declare const apiJsonResponse: <S extends number, T extends JsonCompatibleStruct>(statusCode: S, data: T, headers?: HttpHeaders | undefined) => ApiResponse<S, T>;
|
|
85
|
+
export declare const apiTextResponse: <S extends number>(statusCode: S, data: string, headers?: HttpHeaders | undefined) => ApiResponse<S, string>;
|
|
86
|
+
export declare const apiHtmlResponse: <S extends number>(statusCode: S, data: string, headers?: HttpHeaders | undefined) => ApiResponse<S, string>;
|
|
87
|
+
export declare enum METHOD {
|
|
88
|
+
GET = "GET",
|
|
89
|
+
HEAD = "HEAD",
|
|
90
|
+
POST = "POST",
|
|
91
|
+
PUT = "PUT",
|
|
92
|
+
PATCH = "PATCH",
|
|
93
|
+
DELETE = "DELETE",
|
|
94
|
+
OPTIONS = "OPTIONS"
|
|
95
|
+
}
|
|
96
|
+
export * from './helpers';
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as pathToRegexp from 'path-to-regexp';
|
|
2
|
+
import queryString from 'query-string';
|
|
3
|
+
import { mapFind, memoize } from '../misc/helpers';
|
|
4
|
+
import { createProfile } from '../profile';
|
|
5
|
+
/*
|
|
6
|
+
* route definition helper. the only required params of the route are the name, path, and handler. other params
|
|
7
|
+
* can be added to the type and then later used in the routeMatcher. when defining the `createRoute` method, only
|
|
8
|
+
* the request input format is defined, the result format is derived from the routes.
|
|
9
|
+
*
|
|
10
|
+
* eg:
|
|
11
|
+
* export const createRoute = makeCreateRoute<AppServices, ApiRouteRequest, {
|
|
12
|
+
* method: METHOD;
|
|
13
|
+
* }>();
|
|
14
|
+
*
|
|
15
|
+
* eg when defining requestServiceProvider in line, the types have a hard time, it helps to put in another argument:
|
|
16
|
+
* export const exampleRoute = createRoute({name: 'exampleRoute', method: METHOD.GET, path: '/api/example/:key',
|
|
17
|
+
* requestServiceProvider: requestServiceProvider({
|
|
18
|
+
* cookieAuthMiddleware,
|
|
19
|
+
* documentStoreMiddleware,
|
|
20
|
+
* }},
|
|
21
|
+
* async(params: {key: string}, services) => {
|
|
22
|
+
* const result = await services.myDocumentStore.getItem(params.key);
|
|
23
|
+
*
|
|
24
|
+
* if (!result) {
|
|
25
|
+
* throw new NotFoundError('requested item not found');
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* return apiJsonResponse(200, result);
|
|
29
|
+
* }
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* eg when using a pre-existing provider variable the types work better:
|
|
33
|
+
* export const exampleRoute = createRoute({name: 'exampleRoute', method: METHOD.GET, path: '/api/example/:key',
|
|
34
|
+
* requestServiceProvider,
|
|
35
|
+
* handler: async(params: {key: string}, services) => {
|
|
36
|
+
* const result = await services.myDocumentStore.getItem(params.key);
|
|
37
|
+
*
|
|
38
|
+
* if (!result) {
|
|
39
|
+
* throw new NotFoundError('requested item not found');
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* return apiJsonResponse(200, result);
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
*/
|
|
46
|
+
export const makeCreateRoute = () => (...args) => {
|
|
47
|
+
return (args.length === 1
|
|
48
|
+
? args[0]
|
|
49
|
+
: { ...args[0], handler: args[1] });
|
|
50
|
+
};
|
|
51
|
+
/* begin reverse routing utils */
|
|
52
|
+
export const makeRenderRouteUrl = () => (route, params, query = {}) => {
|
|
53
|
+
const getPathForParams = pathToRegexp.compile(route.path, { encode: encodeURIComponent });
|
|
54
|
+
const search = queryString.stringify(query);
|
|
55
|
+
const path = getPathForParams(params) + (search ? `?${search}` : '');
|
|
56
|
+
return path;
|
|
57
|
+
};
|
|
58
|
+
export const renderAnyRouteUrl = makeRenderRouteUrl();
|
|
59
|
+
const bindRoute = (services, appBinder, pathExtractor, matcher) => (route) => {
|
|
60
|
+
const getParamsFromPath = pathToRegexp.match(route.path, { decode: decodeURIComponent });
|
|
61
|
+
const boundServiceProvider = route.requestServiceProvider && appBinder(services, route.requestServiceProvider);
|
|
62
|
+
return (request, profile) => {
|
|
63
|
+
const path = pathExtractor(request);
|
|
64
|
+
const match = getParamsFromPath(path);
|
|
65
|
+
if ((!matcher || matcher(request, route)) && match) {
|
|
66
|
+
return profile.track(route.name, routeProfile => () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, profile: routeProfile }, { route, params: match.params }) : undefined));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/*
|
|
71
|
+
* here among other things we're specifying a generic response format that the response and error handling middleware can use,
|
|
72
|
+
* if any routes have responses that don't adhere to this it'll complain about it.
|
|
73
|
+
*
|
|
74
|
+
* eg:
|
|
75
|
+
* export const getRequestResponder = makeGetRequestResponder<AppServices, TRoutes, ApiRouteRequest, Promise<ApiRouteResponse>>()({
|
|
76
|
+
* routes: apiRoutes, // the route definitions
|
|
77
|
+
* pathExtractor, // how to get the path out of the request format
|
|
78
|
+
* routeMatcher, // logic for matching route (if there is any in addition to the path matching)
|
|
79
|
+
* errorHandler, // any special error handling
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* eg an lambda entrypoint:
|
|
83
|
+
* export const handler: (request: APIGatewayProxyEventV2) => Promise<ApiRouteResponse> =
|
|
84
|
+
* getRequestResponder(
|
|
85
|
+
* lambdaServices, // the AppServices for this entrypoint
|
|
86
|
+
* lambdaMiddleware // environment specific response middleware (like cors)
|
|
87
|
+
* );
|
|
88
|
+
*/
|
|
89
|
+
export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatcher, errorHandler }) => (services, responseMiddleware) => {
|
|
90
|
+
const appBinderImpl = (app, middleware) => middleware(app, appBinder);
|
|
91
|
+
const appBinder = memoize(appBinderImpl);
|
|
92
|
+
const boundRoutes = routes().map(bindRoute(services, appBinder, pathExtractor, routeMatcher));
|
|
93
|
+
const boundResponseMiddleware = responseMiddleware ? responseMiddleware(services) : undefined;
|
|
94
|
+
// *note* this opaque promise guard is less generic than i hoped so
|
|
95
|
+
// i'm leaving it here instead of the guards file.
|
|
96
|
+
//
|
|
97
|
+
// its less than ideal because it enforces that the handlers return
|
|
98
|
+
// the same type as the parent promise, usually a handler can be either a
|
|
99
|
+
// promise or a non-promise value and the promise figures it out, but those
|
|
100
|
+
// types are getting complicated quickly here.
|
|
101
|
+
const isPromise = (thing) => thing instanceof Promise;
|
|
102
|
+
return (request) => {
|
|
103
|
+
const { end, ...profile } = createProfile(new Date().toISOString()).start();
|
|
104
|
+
try {
|
|
105
|
+
const executor = mapFind(boundRoutes, (route) => route(request, profile));
|
|
106
|
+
if (executor) {
|
|
107
|
+
const result = boundResponseMiddleware ?
|
|
108
|
+
boundResponseMiddleware(executor(), { request, profile }) : executor();
|
|
109
|
+
if (isPromise(result) && errorHandler) {
|
|
110
|
+
const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
|
|
111
|
+
boundResponseMiddleware(errorHandler(e), { request, profile }) : errorHandler(e);
|
|
112
|
+
return result.catch(errorHandlerWithMiddleware);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (boundResponseMiddleware) {
|
|
119
|
+
return boundResponseMiddleware(undefined, { request, profile });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
if (errorHandler && e instanceof Error) {
|
|
124
|
+
return boundResponseMiddleware ? boundResponseMiddleware(errorHandler(e), { request, profile }) : errorHandler(e);
|
|
125
|
+
}
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
export const apiJsonResponse = (statusCode, data, headers) => ({ statusCode, data, body: JSON.stringify(data), headers: { ...headers, 'content-type': 'application/json' } });
|
|
132
|
+
export const apiTextResponse = (statusCode, data, headers) => ({ statusCode, data, body: data, headers: { ...headers, 'content-type': 'text/plain' } });
|
|
133
|
+
export const apiHtmlResponse = (statusCode, data, headers) => ({ statusCode, data, body: data, headers: { ...headers, 'content-type': 'text/html' } });
|
|
134
|
+
export var METHOD;
|
|
135
|
+
(function (METHOD) {
|
|
136
|
+
METHOD["GET"] = "GET";
|
|
137
|
+
METHOD["HEAD"] = "HEAD";
|
|
138
|
+
METHOD["POST"] = "POST";
|
|
139
|
+
METHOD["PUT"] = "PUT";
|
|
140
|
+
METHOD["PATCH"] = "PATCH";
|
|
141
|
+
METHOD["DELETE"] = "DELETE";
|
|
142
|
+
METHOD["OPTIONS"] = "OPTIONS";
|
|
143
|
+
})(METHOD || (METHOD = {}));
|
|
144
|
+
export * from './helpers';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { compactDecrypt, compactVerify, importSPKI } from 'jose';
|
|
2
|
-
import { once } from '../..';
|
|
3
2
|
import { resolveConfigValue } from '../../config/resolveConfigValue';
|
|
4
3
|
import { ifDefined } from '../../guards';
|
|
4
|
+
import { once } from '../../misc/helpers';
|
|
5
5
|
import { getAuthTokenOrCookie } from '.';
|
|
6
6
|
export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
7
7
|
const config = configProvider[ifDefined(initializer.configSpace, 'decryption')];
|