@openstax/ts-utils 1.0.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.
- package/README.md +118 -0
- package/dist/README.md +118 -0
- package/dist/assertions.d.ts +9 -0
- package/dist/assertions.js +99 -0
- package/dist/aws/securityTokenService.d.ts +2 -0
- package/dist/aws/securityTokenService.js +6 -0
- package/dist/aws/ssmService.d.ts +2 -0
- package/dist/aws/ssmService.js +6 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.js +110 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +32 -0
- package/dist/fetch.d.ts +59 -0
- package/dist/fetch.js +47 -0
- package/dist/guards.d.ts +5 -0
- package/dist/guards.js +34 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +120 -0
- package/dist/middleware.d.ts +9 -0
- package/dist/middleware.js +38 -0
- package/dist/package.json +65 -0
- package/dist/pagination.d.ts +65 -0
- package/dist/pagination.js +83 -0
- package/dist/routing.d.ts +100 -0
- package/dist/routing.js +237 -0
- package/dist/services/apiGateway/index.d.ts +61 -0
- package/dist/services/apiGateway/index.js +70 -0
- package/dist/services/authProvider/browser.d.ts +17 -0
- package/dist/services/authProvider/browser.js +30 -0
- package/dist/services/authProvider/decryption.d.ts +16 -0
- package/dist/services/authProvider/decryption.js +56 -0
- package/dist/services/authProvider/index.d.ts +37 -0
- package/dist/services/authProvider/index.js +17 -0
- package/dist/services/authProvider/subrequest.d.ts +16 -0
- package/dist/services/authProvider/subrequest.js +39 -0
- package/dist/services/exercisesGateway/index.d.ts +80 -0
- package/dist/services/exercisesGateway/index.js +94 -0
- package/dist/services/lrsGateway/attempt-utils.d.ts +60 -0
- package/dist/services/lrsGateway/attempt-utils.js +270 -0
- package/dist/services/lrsGateway/file-system.d.ts +15 -0
- package/dist/services/lrsGateway/file-system.js +126 -0
- package/dist/services/lrsGateway/index.d.ts +110 -0
- package/dist/services/lrsGateway/index.js +116 -0
- package/dist/services/searchProvider/index.d.ts +20 -0
- package/dist/services/searchProvider/index.js +2 -0
- package/dist/services/searchProvider/memorySearchTheBadWay.d.ts +12 -0
- package/dist/services/searchProvider/memorySearchTheBadWay.js +51 -0
- package/dist/services/versionedDocumentStore/dynamodb.d.ts +20 -0
- package/dist/services/versionedDocumentStore/dynamodb.js +151 -0
- package/dist/services/versionedDocumentStore/file-system.d.ts +22 -0
- package/dist/services/versionedDocumentStore/file-system.js +112 -0
- package/dist/services/versionedDocumentStore/index.d.ts +23 -0
- package/dist/services/versionedDocumentStore/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +2 -0
- package/package.json +65 -0
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stateHasError = exports.stateHasData = exports.fetchSuccess = exports.fetchError = exports.fetchLoading = exports.FetchStateType = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* these are just helpers for formatting responses, they don't actually do any loading. especially in UI development they
|
|
6
|
+
* help with continuity over loading, reloading, saving, and errors. this is pretty nice in typescript because you have to deal
|
|
7
|
+
* with the possibility that the result state is in a loading or error state. if you avoid dealing with those states you need
|
|
8
|
+
* to write very clear code to ignore them, which easily presents missing functionality like errors that are not being messaged
|
|
9
|
+
* to the user.
|
|
10
|
+
* */
|
|
11
|
+
var FetchStateType;
|
|
12
|
+
(function (FetchStateType) {
|
|
13
|
+
FetchStateType["SUCCESS"] = "success";
|
|
14
|
+
FetchStateType["ERROR"] = "error";
|
|
15
|
+
FetchStateType["LOADING"] = "loading";
|
|
16
|
+
})(FetchStateType = exports.FetchStateType || (exports.FetchStateType = {}));
|
|
17
|
+
/*
|
|
18
|
+
* keeps existing data but sets the new status
|
|
19
|
+
*
|
|
20
|
+
* const state = fetchLoading(previousState)
|
|
21
|
+
* */
|
|
22
|
+
const fetchLoading = (previous) => ({ type: FetchStateType.LOADING, ...(previous && 'data' in previous ? { data: previous.data } : {}) });
|
|
23
|
+
exports.fetchLoading = fetchLoading;
|
|
24
|
+
/*
|
|
25
|
+
* keeps existing data but sets the new status and error value
|
|
26
|
+
*
|
|
27
|
+
* const state = fetchError(error, previousState)
|
|
28
|
+
* */
|
|
29
|
+
const fetchError = (error, previous) => ({ ...previous, type: FetchStateType.ERROR, error });
|
|
30
|
+
exports.fetchError = fetchError;
|
|
31
|
+
/*
|
|
32
|
+
* formats data with success type
|
|
33
|
+
*
|
|
34
|
+
* const state = fetchSuccess(newData)
|
|
35
|
+
* */
|
|
36
|
+
const fetchSuccess = (data) => ({ type: FetchStateType.SUCCESS, data });
|
|
37
|
+
exports.fetchSuccess = fetchSuccess;
|
|
38
|
+
/*
|
|
39
|
+
* guard for checking if the state has result data, it might be true for any state type.
|
|
40
|
+
* */
|
|
41
|
+
const stateHasData = (state) => 'data' in state;
|
|
42
|
+
exports.stateHasData = stateHasData;
|
|
43
|
+
/*
|
|
44
|
+
* guard for checking if the state has an error, it might be true for error or loading states.
|
|
45
|
+
* */
|
|
46
|
+
const stateHasError = (state) => 'error' in state;
|
|
47
|
+
exports.stateHasError = stateHasError;
|
package/dist/guards.d.ts
ADDED
package/dist/guards.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ifDefined = exports.isPlainObject = exports.isDefined = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* checks if a thing is defined. this is often easy to do with a simple if statement, but in certain
|
|
6
|
+
* situations the guard is required and its nice to have one pre-defined.
|
|
7
|
+
*
|
|
8
|
+
* eg this filters the array but doesn't fix the array type:
|
|
9
|
+
* const result = (array as Array<string | undefined>).filter(thing => !!thing);
|
|
10
|
+
*
|
|
11
|
+
* eg this does the right thing:
|
|
12
|
+
* const result = (array as Array<string | undefined>).filter(isDefined);
|
|
13
|
+
*
|
|
14
|
+
* eg because writing this is annoying:
|
|
15
|
+
* const result = (array as Array<string | undefined>).filter(<X>(x: X): x is Exclude<X, undefined> => x !== undefined);
|
|
16
|
+
*/
|
|
17
|
+
const isDefined = (x) => x !== undefined;
|
|
18
|
+
exports.isDefined = isDefined;
|
|
19
|
+
/*
|
|
20
|
+
* a guard for plain old javascript objects that are not based on some other prototype,
|
|
21
|
+
* for example making them safe to JSON stringify and stuff
|
|
22
|
+
* */
|
|
23
|
+
const isPlainObject = (thing) => thing instanceof Object && thing.__proto__.constructor.name === 'Object';
|
|
24
|
+
exports.isPlainObject = isPlainObject;
|
|
25
|
+
/*
|
|
26
|
+
* this isn't really a guard its just a way to provide a default value without creating a coverage branch.
|
|
27
|
+
*
|
|
28
|
+
* if the first thing is defined it uses it, otherwise it returns the second thing.
|
|
29
|
+
*
|
|
30
|
+
* eg:
|
|
31
|
+
* const valueWithDefault = ifDefined(thing, 'default value')
|
|
32
|
+
*/
|
|
33
|
+
const ifDefined = (x, d) => (0, exports.isDefined)(x) ? x : d;
|
|
34
|
+
exports.ifDefined = ifDefined;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UnionToIntersection } from './types';
|
|
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]>;
|
|
4
|
+
export declare const putKeyValue: <K extends string>(key: K) => <O extends { [key in K]?: any; }>(obj: O, value: O[K]) => O;
|
|
5
|
+
export declare const fnIf: <T1, T2>(condition: boolean, trueValue: T1, falseValue: T2) => T1 | T2;
|
|
6
|
+
export declare const mapFind: <I, R>(array: I[], mapper: (item: I) => R, predicate?: (result: R) => boolean) => R | undefined;
|
|
7
|
+
declare type HashValue = string | number | boolean | null | HashCompoundValue;
|
|
8
|
+
declare type HashCompoundValue = Array<HashValue> | {
|
|
9
|
+
[key: string]: HashValue;
|
|
10
|
+
};
|
|
11
|
+
export declare const hashValue: (value: HashValue) => string;
|
|
12
|
+
export declare const once: <F extends (...args: any[]) => any>(fn: F) => F;
|
|
13
|
+
export declare const getCommonProperties: <T1 extends {}, T2 extends {}>(thing1: T1, thing2: T2) => (keyof T1 & keyof T2)[];
|
|
14
|
+
export declare const merge: <T extends {}[]>(...[thing1, ...tail]: T) => UnionToIntersection<T[number]>;
|
|
15
|
+
export declare const tuple: <A extends any[]>(...args: A) => A;
|
|
16
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tuple = exports.merge = exports.getCommonProperties = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const guards_1 = require("./guards");
|
|
6
|
+
/*
|
|
7
|
+
* there was a reason i made these instead of using lodash/fp but i forget what it was. i think maybe
|
|
8
|
+
* these do more validation that the second function gets a compatible object.
|
|
9
|
+
*
|
|
10
|
+
* const getAuthor = getKeyValue('author');
|
|
11
|
+
* const author = getAuthor(book);
|
|
12
|
+
*
|
|
13
|
+
* const getAuthorOrNope = getKeyValueOr('author', 'nope');
|
|
14
|
+
* const authorOrNope = getAuthorOrNope(book);
|
|
15
|
+
*
|
|
16
|
+
* const putAuthor = putKeyValue('author');
|
|
17
|
+
* const newBook = putAuthor(book, 'tom');
|
|
18
|
+
* */
|
|
19
|
+
const getKeyValue = (key) => (obj) => obj[key];
|
|
20
|
+
exports.getKeyValue = getKeyValue;
|
|
21
|
+
const getKeyValueOr = (key, defaultValue) => (obj) => obj[key] || defaultValue;
|
|
22
|
+
exports.getKeyValueOr = getKeyValueOr;
|
|
23
|
+
const putKeyValue = (key) => (obj, value) => ({ ...obj, [key]: value });
|
|
24
|
+
exports.putKeyValue = putKeyValue;
|
|
25
|
+
/*
|
|
26
|
+
* a shameful helper to avoid needing to test code coverage of branches
|
|
27
|
+
*/
|
|
28
|
+
const fnIf = (condition, trueValue, falseValue) => condition ? trueValue : falseValue;
|
|
29
|
+
exports.fnIf = fnIf;
|
|
30
|
+
/*
|
|
31
|
+
* maps the array and returns the first result that matches the predicate
|
|
32
|
+
* avoids processing extra elements that would happen with .map().find() or .reduce
|
|
33
|
+
*
|
|
34
|
+
* eg the third element of the array is never processed:
|
|
35
|
+
* const result = mapFind([1,2,3], x => 'hello'.charAt(x), x => x === 'l');
|
|
36
|
+
*/
|
|
37
|
+
const mapFind = (array, mapper, predicate = (r) => !!r) => {
|
|
38
|
+
for (const item of array) {
|
|
39
|
+
const mapped = mapper(item);
|
|
40
|
+
if (predicate(mapped)) {
|
|
41
|
+
return mapped;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.mapFind = mapFind;
|
|
46
|
+
/*
|
|
47
|
+
* creates a string hash of lots of different kinds of things.
|
|
48
|
+
*
|
|
49
|
+
* eg:
|
|
50
|
+
* hashValue({someKey: 'someValue'})
|
|
51
|
+
* */
|
|
52
|
+
const hashValue = (value) => {
|
|
53
|
+
const strValue = JSON.stringify(value);
|
|
54
|
+
return (0, crypto_1.createHash)('sha1').update(strValue).digest('hex');
|
|
55
|
+
};
|
|
56
|
+
exports.hashValue = hashValue;
|
|
57
|
+
/*
|
|
58
|
+
* returns a function that will only ever call the given function once, returning the first result for every subsequent call
|
|
59
|
+
*
|
|
60
|
+
* eg:
|
|
61
|
+
* const heavyFunction = () => 'hello';
|
|
62
|
+
* const butOnlyOnce = once(() => 'hello');
|
|
63
|
+
*
|
|
64
|
+
* heavyFunction() // returns `hello`;
|
|
65
|
+
* butOnlyOnce() // returns `hello`;
|
|
66
|
+
*/
|
|
67
|
+
const once = (fn) => {
|
|
68
|
+
let result;
|
|
69
|
+
return ((...args) => result || (result = fn(...args)));
|
|
70
|
+
};
|
|
71
|
+
exports.once = once;
|
|
72
|
+
const getCommonProperties = (thing1, thing2) => Object.keys(thing1).filter((key) => Object.keys(thing2).includes(key));
|
|
73
|
+
exports.getCommonProperties = getCommonProperties;
|
|
74
|
+
/*
|
|
75
|
+
* recursive merge properties of inputs. values are merged if they are
|
|
76
|
+
* plain objects or arrays, otherwise if the same property exists in both
|
|
77
|
+
* objects the value from the second argument will win.
|
|
78
|
+
*
|
|
79
|
+
* unlike lodash merge, this will not change object references for values that
|
|
80
|
+
* exist only in one parameter.
|
|
81
|
+
*
|
|
82
|
+
* eg:
|
|
83
|
+
* merge({thing: 'one'}, {thing: 'two', otherKey: 'one'}, {coolKey: 'coolValue'});
|
|
84
|
+
*/
|
|
85
|
+
const merge = (...[thing1, ...tail]) => {
|
|
86
|
+
const mergedTail = tail.length > 0
|
|
87
|
+
? (0, exports.merge)(...tail)
|
|
88
|
+
: null;
|
|
89
|
+
if (!mergedTail) {
|
|
90
|
+
return thing1;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...thing1,
|
|
94
|
+
...mergedTail,
|
|
95
|
+
...(0, exports.getCommonProperties)(thing1, mergedTail).reduce((result, property) => ({
|
|
96
|
+
...result,
|
|
97
|
+
...((0, guards_1.isPlainObject)(thing1[property]) && (0, guards_1.isPlainObject)(mergedTail[property])
|
|
98
|
+
? { [property]: (0, exports.merge)(thing1[property], mergedTail[property]) }
|
|
99
|
+
: (Array.isArray(thing1[property]) && Array.isArray(mergedTail[property]))
|
|
100
|
+
? { [property]: [...thing1[property], ...mergedTail[property]] }
|
|
101
|
+
: {}),
|
|
102
|
+
}), {}),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
exports.merge = merge;
|
|
106
|
+
/*
|
|
107
|
+
* a silly utility to help typescript realize an array is a tuple
|
|
108
|
+
*
|
|
109
|
+
* eg:
|
|
110
|
+
* const a = [5, 'string'] // type is `Array<string | number>`
|
|
111
|
+
* const t = tuple(5, 'string') type is `[5, 'string']`
|
|
112
|
+
*
|
|
113
|
+
* both have the same javascript value, but one is forced to be a tuple, which
|
|
114
|
+
* is nice if its structure is important. examples are like the React.useState
|
|
115
|
+
* pattern where there are two return values in a tuple, or if you're feeding
|
|
116
|
+
* Object.fromEntries
|
|
117
|
+
*
|
|
118
|
+
*/
|
|
119
|
+
const tuple = (...args) => args;
|
|
120
|
+
exports.tuple = tuple;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TupleExtends } from './types';
|
|
2
|
+
export declare type MiddlewareProvider<Sa, M, A extends any[], R> = (app: Sa) => (middleware: M, ...args: A) => R;
|
|
3
|
+
export declare type MiddlewareTransformProvider<Sa, M, A extends any[], R> = (app: Sa) => (middleware: M, ...args: A) => Omit<M, keyof R> & R;
|
|
4
|
+
export declare type MiddlewareInput<S> = S extends MiddlewareProvider<any, infer M, any, any> ? M : never;
|
|
5
|
+
export declare type ServiceMiddlewareProviderResult<M, S> = [M] extends [never] ? never : [M] extends [MiddlewareInput<S>] ? S extends MiddlewareTransformProvider<any, M, any, infer R> ? R extends M ? R : Omit<M, keyof R> & R : S extends MiddlewareProvider<any, M, any, infer R> ? R : never : never;
|
|
6
|
+
export declare type ServiceMiddlewareProviderArgs<S> = S extends MiddlewareProvider<any, any, infer A, any> ? A : never;
|
|
7
|
+
export declare type ServiceMiddlewareProviderChainResult<C, M> = C extends [infer S1, ...infer S] ? S extends never[] ? ServiceMiddlewareProviderResult<M, S1> : ServiceMiddlewareProviderChainResult<S, ServiceMiddlewareProviderResult<M, S1>> : C extends unknown ? M : never;
|
|
8
|
+
export declare type ServiceMiddlewareProviderChainArgs<C> = C extends [infer S1, ...infer S] ? S extends never[] ? ServiceMiddlewareProviderArgs<S1> : TupleExtends<ServiceMiddlewareProviderChainArgs<S>, ServiceMiddlewareProviderArgs<S1>> extends 'yes' ? ServiceMiddlewareProviderChainArgs<S> : TupleExtends<ServiceMiddlewareProviderArgs<S1>, ServiceMiddlewareProviderChainArgs<S>> extends 'yes' ? ServiceMiddlewareProviderArgs<S1> : never : C extends unknown ? [] : never;
|
|
9
|
+
export declare const makeComposeMiddleware: <Sa, M>() => <C extends any[]>(...chain: C) => MiddlewareProvider<Sa, M, ServiceMiddlewareProviderChainArgs<C>, ServiceMiddlewareProviderChainResult<C, M>>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeComposeMiddleware = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* this function creates a middleware composer for the given AppServices and starting value. the ending
|
|
6
|
+
* value will depend on the composed values. after composing the values, the composer returns a function that
|
|
7
|
+
* expects the given input value and returns the result of the last element of the middleware chain.
|
|
8
|
+
*
|
|
9
|
+
* eg:
|
|
10
|
+
* export const composeServiceMiddleware = makeComposeMiddleware<AppServices, {request: ApiRouteRequest}>();
|
|
11
|
+
* export const composeResponseMiddleware = makeComposeMiddleware<AppServices, Promise<ApiRouteResponse> | undefined>();
|
|
12
|
+
*
|
|
13
|
+
* eg:
|
|
14
|
+
* // `requestServiceProvider` is a function that expects `{request: ApiRouteResponse}` (specified in the call to `makeComposeMiddleware`)
|
|
15
|
+
* // for requestServiceProviders as part of routes in particular, the request responder calls this when it resolves the routes.
|
|
16
|
+
* const requestServiceProvider = composeServiceMiddleware(
|
|
17
|
+
* cookieAuthMiddleware, // this one just depends on `request`, which is in the input, and returns that plus the authProvider
|
|
18
|
+
* myDocumentStoreMiddleware, // this one expects the authProvider, and adds the document store
|
|
19
|
+
* myDocumentSearchMiddleware, // this one expects the document store, and adds the search provider
|
|
20
|
+
* ); // if you try to specify them in the wrong order it'll yell at you
|
|
21
|
+
*
|
|
22
|
+
* WARNING: depending on how you use it, typescript _sometimes_ explodes on these recursive types. if you have problems in your build
|
|
23
|
+
* with memory, you will have to specify the expected result type when calling the middleware reducer. this is a little annoying,
|
|
24
|
+
* but it will still correctly error if the type you specify is wrong, so you've still got the type safety.
|
|
25
|
+
*
|
|
26
|
+
* // this might work, or it might make your compiler run out of memory
|
|
27
|
+
* eg: const myThing = makeComposeMiddleware()(...);
|
|
28
|
+
*
|
|
29
|
+
* // this helps typescript figure out what is going on
|
|
30
|
+
* eg: const myThing: myType = makeComposeMiddleware()(...);
|
|
31
|
+
* */
|
|
32
|
+
const makeComposeMiddleware = () => (...chain) => (app) => {
|
|
33
|
+
const boundChain = chain.map(provider => provider(app));
|
|
34
|
+
return (middleware, ...args) => {
|
|
35
|
+
return boundChain.reduce((result, provider) => provider(result, ...args), middleware);
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
exports.makeComposeMiddleware = makeComposeMiddleware;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openstax/ts-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./": "./dist/"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"typesVersions": {
|
|
14
|
+
"*": {
|
|
15
|
+
"*": [
|
|
16
|
+
"dist/*",
|
|
17
|
+
"dist/*.d.ts",
|
|
18
|
+
"dist/*/index.d.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"coverage-report": "open coverage/index.html",
|
|
25
|
+
"test": "yarn jest",
|
|
26
|
+
"ci:test": "yarn jest --coverage",
|
|
27
|
+
"ci:typecheck": "tsc --noEmit",
|
|
28
|
+
"ci:spelling": "git ls-files | yarn cspell -c ../../cspell.json --file-list stdin",
|
|
29
|
+
"ci:lint": "eslint --max-warnings=0 .",
|
|
30
|
+
"ci": "CI=true npm-run-all ci:*"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@aws-sdk/client-dynamodb": "^3.171.0",
|
|
34
|
+
"@aws-sdk/client-ssm": "^3.171.0",
|
|
35
|
+
"@aws-sdk/client-sts": "^3.171.0",
|
|
36
|
+
"aws-sdk": "^2.1145.0",
|
|
37
|
+
"cookie": "^0.5.0",
|
|
38
|
+
"date-fns": "^2.29.2",
|
|
39
|
+
"jose": "^4.9.3",
|
|
40
|
+
"path-to-regexp": "^6.2.0",
|
|
41
|
+
"query-string": "^7.1.1",
|
|
42
|
+
"uuid": "^9.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/cookie": "^0.5.0",
|
|
46
|
+
"@types/jest": "^27.4.1",
|
|
47
|
+
"@types/node": "^17.0.35",
|
|
48
|
+
"@types/node-fetch": "2.6.1",
|
|
49
|
+
"@types/uuid": "^8.3.4",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
|
51
|
+
"@typescript-eslint/parser": "^5.13.0",
|
|
52
|
+
"cspell": "^5.18.5",
|
|
53
|
+
"eslint": "^8.10.0",
|
|
54
|
+
"eslint-plugin-import": "^2.25.4",
|
|
55
|
+
"eslint-plugin-jest": "^26.1.1",
|
|
56
|
+
"jest": "^29.1.1",
|
|
57
|
+
"node-fetch": "2.6.7",
|
|
58
|
+
"npm-run-all": "^4.1.5",
|
|
59
|
+
"rollup-plugin-commonjs": "^10.1.0",
|
|
60
|
+
"rollup-plugin-typescript2": "^0.31.2",
|
|
61
|
+
"ts-jest": "^29.0.3",
|
|
62
|
+
"typescript": "v4.6",
|
|
63
|
+
"yargs": "^17.5.1"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { QueryParams, RouteMatchRecord } from './routing';
|
|
2
|
+
export declare type PaginationHandler<Pa> = <R>(queryParams: QueryParams, match: RouteMatchRecord<R>) => Pa & {
|
|
3
|
+
getUnusedQueryParams: () => QueryParams;
|
|
4
|
+
};
|
|
5
|
+
export declare const createPaginationMiddleware: <Ri, R = any>() => <Pa>({ getQueryParams, setUnusedQueryParams, paginator }: {
|
|
6
|
+
getQueryParams: (request: Ri) => QueryParams;
|
|
7
|
+
setUnusedQueryParams: (request: Ri, query: QueryParams) => Ri;
|
|
8
|
+
paginator: PaginationHandler<Pa>;
|
|
9
|
+
}) => <M extends {
|
|
10
|
+
request: Ri;
|
|
11
|
+
}>() => (middleware: M, match: RouteMatchRecord<R>) => M & {
|
|
12
|
+
pagination: Pa & {
|
|
13
|
+
getUnusedQueryParams: () => QueryParams;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare const loadMorePagination: <R>(queryParams: Record<string, string | undefined>, { route, params }: RouteMatchRecord<R>) => {
|
|
17
|
+
getUnusedQueryParams: () => {
|
|
18
|
+
[x: string]: string | undefined;
|
|
19
|
+
};
|
|
20
|
+
getPageTokenString: () => string | undefined;
|
|
21
|
+
getPageTokenNumber: () => number | undefined;
|
|
22
|
+
getPaginationResponse: <T>({ items, ...meta }: LoadMorePaginationResultInput<T>) => {
|
|
23
|
+
items: T[];
|
|
24
|
+
meta: {
|
|
25
|
+
nextPageToken: string | number | undefined;
|
|
26
|
+
};
|
|
27
|
+
links: {
|
|
28
|
+
nextPage: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export interface LoadMorePaginationResultInput<T> {
|
|
33
|
+
items: T[];
|
|
34
|
+
nextPageToken: string | number | undefined;
|
|
35
|
+
}
|
|
36
|
+
export declare const pageNumberPagination: <R>(queryParams: Record<string, string | undefined>, { route, params }: RouteMatchRecord<R>) => {
|
|
37
|
+
getUnusedQueryParams: () => {
|
|
38
|
+
[x: string]: string | undefined;
|
|
39
|
+
};
|
|
40
|
+
getPaginationParams: () => {
|
|
41
|
+
page: number | undefined;
|
|
42
|
+
};
|
|
43
|
+
getPaginationResponse: <T>({ items, ...meta }: PageNumberPaginationResultInput<T>) => {
|
|
44
|
+
items: T[];
|
|
45
|
+
meta: {
|
|
46
|
+
pageSize: number;
|
|
47
|
+
currentPage: number;
|
|
48
|
+
totalItems: number;
|
|
49
|
+
totalPages: number;
|
|
50
|
+
};
|
|
51
|
+
links: {
|
|
52
|
+
firstPage: string;
|
|
53
|
+
lastPage: string;
|
|
54
|
+
nextPage: string | undefined;
|
|
55
|
+
prevPage: string | undefined;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export interface PageNumberPaginationResultInput<T> {
|
|
60
|
+
items: T[];
|
|
61
|
+
pageSize: number;
|
|
62
|
+
currentPage: number;
|
|
63
|
+
totalItems: number;
|
|
64
|
+
totalPages: number;
|
|
65
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pageNumberPagination = exports.loadMorePagination = exports.createPaginationMiddleware = void 0;
|
|
4
|
+
const assertions_1 = require("./assertions");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const routing_1 = require("./routing");
|
|
7
|
+
/*
|
|
8
|
+
* helper to create middleware with the given paginator. aside from taking care of annoying to write pagination logic, these helpers also make
|
|
9
|
+
* sure that all item list responses have the same formatting.
|
|
10
|
+
*
|
|
11
|
+
* eg:
|
|
12
|
+
* const getQueryParams = getKeyValueOr('queryStringParameters', {} as QueryParams);
|
|
13
|
+
* const setUnusedQueryParams = putKeyValue('queryStringParameters');
|
|
14
|
+
*
|
|
15
|
+
* export const loadMorePaginationMiddleware = createPaginationMiddleware<ApiRouteRequest>()({getQueryParams, setUnusedQueryParams, paginator: loadMorePagination});
|
|
16
|
+
* export const pageNumberPaginationMiddleware = createPaginationMiddleware<ApiRouteRequest>()({getQueryParams, setUnusedQueryParams, paginator: pageNumberPagination});
|
|
17
|
+
*
|
|
18
|
+
* eg the pagination middleware then provides your necessary inputs (getPageToken... in this case) and formats the response:
|
|
19
|
+
* const result = await services.myDocumentStore.getVersions(key, services.pagination.getPageTokenNumber());
|
|
20
|
+
*
|
|
21
|
+
* if (!result) {
|
|
22
|
+
* throw new NotFoundError('requested item not found');
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* return apiJsonResponse(200, services.pagination.getPaginationResponse(result));
|
|
26
|
+
*/
|
|
27
|
+
const createPaginationMiddleware = () => ({ getQueryParams, setUnusedQueryParams, paginator }) => () => (middleware, match) => {
|
|
28
|
+
const queryParams = getQueryParams(middleware.request);
|
|
29
|
+
const pagination = paginator(queryParams, match);
|
|
30
|
+
// remove pagination params from downstream logic
|
|
31
|
+
middleware.request = setUnusedQueryParams(middleware.request, pagination.getUnusedQueryParams());
|
|
32
|
+
return { ...middleware, pagination };
|
|
33
|
+
};
|
|
34
|
+
exports.createPaginationMiddleware = createPaginationMiddleware;
|
|
35
|
+
const loadMorePagination = (queryParams, { route, params }) => {
|
|
36
|
+
const { pageToken, ...otherParams } = queryParams;
|
|
37
|
+
return {
|
|
38
|
+
getUnusedQueryParams: () => otherParams,
|
|
39
|
+
getPageTokenString: () => pageToken,
|
|
40
|
+
getPageTokenNumber: () => pageToken
|
|
41
|
+
? (0, assertions_1.notNaN)(parseInt(pageToken, 10), () => { throw new errors_1.InvalidRequestError(); })
|
|
42
|
+
: undefined,
|
|
43
|
+
getPaginationResponse: ({ items, ...meta }) => {
|
|
44
|
+
return {
|
|
45
|
+
items,
|
|
46
|
+
meta,
|
|
47
|
+
links: {
|
|
48
|
+
nextPage: meta.nextPageToken
|
|
49
|
+
? (0, routing_1.renderAnyRouteUrl)(route, params, { ...queryParams, pageToken: meta.nextPageToken.toString() })
|
|
50
|
+
: undefined,
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
exports.loadMorePagination = loadMorePagination;
|
|
57
|
+
const pageNumberPagination = (queryParams, { route, params }) => {
|
|
58
|
+
const { page, ...otherParams } = queryParams;
|
|
59
|
+
const numberPage = page
|
|
60
|
+
? (0, assertions_1.notNaN)(parseInt(page, 10), () => { throw new errors_1.InvalidRequestError(); })
|
|
61
|
+
: undefined;
|
|
62
|
+
return {
|
|
63
|
+
getUnusedQueryParams: () => otherParams,
|
|
64
|
+
getPaginationParams: () => ({ page: numberPage }),
|
|
65
|
+
getPaginationResponse: ({ items, ...meta }) => {
|
|
66
|
+
return {
|
|
67
|
+
items,
|
|
68
|
+
meta,
|
|
69
|
+
links: {
|
|
70
|
+
firstPage: (0, routing_1.renderAnyRouteUrl)(route, params, { ...queryParams, page: '1' }),
|
|
71
|
+
lastPage: (0, routing_1.renderAnyRouteUrl)(route, params, { ...queryParams, page: meta.totalPages.toString() }),
|
|
72
|
+
nextPage: meta.currentPage < meta.totalPages
|
|
73
|
+
? (0, routing_1.renderAnyRouteUrl)(route, params, { ...queryParams, page: (meta.currentPage + 1).toString() })
|
|
74
|
+
: undefined,
|
|
75
|
+
prevPage: meta.currentPage > 1
|
|
76
|
+
? (0, routing_1.renderAnyRouteUrl)(route, params, { ...queryParams, page: (meta.currentPage - 1).toString() })
|
|
77
|
+
: undefined
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
exports.pageNumberPagination = pageNumberPagination;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export declare type QueryParams = Record<string, string | undefined>;
|
|
2
|
+
export declare type RouteParams = {
|
|
3
|
+
[key: string]: string;
|
|
4
|
+
};
|
|
5
|
+
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;
|
|
6
|
+
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;
|
|
7
|
+
export declare type OutputForRoute<R> = R extends Route<any, any, any, any, any, infer Ro> ? Ro : never;
|
|
8
|
+
export declare type ParamsForRoute<R> = R extends Route<any, infer P, any, any, any, any> ? P : never;
|
|
9
|
+
export declare type RequestServicesForRoute<R> = R extends Route<any, any, any, infer Sr, any, any> ? Sr : never;
|
|
10
|
+
export declare type ParamsForRouteOrEmpty<R> = ParamsForRoute<R> extends undefined ? {} : Exclude<ParamsForRoute<R>, undefined>;
|
|
11
|
+
export declare type RouteMatchRecord<R> = R extends AnyRoute<R> ? {
|
|
12
|
+
route: R;
|
|
13
|
+
params: ParamsForRoute<R>;
|
|
14
|
+
} : never;
|
|
15
|
+
export declare type PayloadForRoute<R> = RequestServicesForRoute<R> extends {
|
|
16
|
+
payload: any;
|
|
17
|
+
} ? RequestServicesForRoute<R>['payload'] : undefined;
|
|
18
|
+
declare type RequestServiceProvider<Sa, Sr, Ri> = (app: Sa) => <R>(middleware: {
|
|
19
|
+
request: Ri;
|
|
20
|
+
}, match: RouteMatchRecord<R>) => Sr;
|
|
21
|
+
declare type RouteHandler<P, Sr, Ro> = (params: P, request: Sr) => Ro;
|
|
22
|
+
declare type Route<N extends string, P extends RouteParams | undefined, Sa, Sr, Ri, Ro> = (Sr extends undefined ? {
|
|
23
|
+
requestServiceProvider?: RequestServiceProvider<Sa, Sr, Ri> | undefined;
|
|
24
|
+
} : {
|
|
25
|
+
requestServiceProvider: RequestServiceProvider<Sa, Sr, Ri>;
|
|
26
|
+
}) & {
|
|
27
|
+
name: N;
|
|
28
|
+
path: string;
|
|
29
|
+
handler: (params: P, request: Sr) => Ro;
|
|
30
|
+
};
|
|
31
|
+
declare type CreateRouteConfig<Sa, Sr, Ri, N extends string, Ex> = (Sr extends undefined ? {
|
|
32
|
+
requestServiceProvider?: RequestServiceProvider<Sa, Sr, Ri> | undefined;
|
|
33
|
+
} : {
|
|
34
|
+
requestServiceProvider: RequestServiceProvider<Sa, Sr, Ri>;
|
|
35
|
+
}) & {
|
|
36
|
+
name: N;
|
|
37
|
+
path: string;
|
|
38
|
+
} & Ex;
|
|
39
|
+
export interface CreateRoute<Sa, Ri, Ex> {
|
|
40
|
+
<N extends string, Ro, Sr extends unknown | undefined = undefined, P extends RouteParams | undefined = undefined>(config: CreateRouteConfig<Sa, Sr, Ri, N, Ex> & {
|
|
41
|
+
handler: RouteHandler<P, Sr, Ro>;
|
|
42
|
+
}): Route<N, P, Sa, Sr, Ri, Ro> & Ex;
|
|
43
|
+
<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;
|
|
44
|
+
}
|
|
45
|
+
export declare const makeCreateRoute: <Sa, Ri, Ex = {}>() => CreateRoute<Sa, Ri, Ex>;
|
|
46
|
+
export declare const makeRenderRouteUrl: <Ru extends {
|
|
47
|
+
path: string;
|
|
48
|
+
}>() => <R extends Ru>(route: R, params: ParamsForRoute<R>, query?: QueryParams) => string;
|
|
49
|
+
export declare const renderAnyRouteUrl: <R extends any>(route: R, params: ParamsForRoute<R>, query?: QueryParams) => string;
|
|
50
|
+
declare type RequestPathExtractor<Ri> = (request: Ri) => string;
|
|
51
|
+
declare type RequestRouteMatcher<Ri, R> = (request: Ri, route: R) => boolean;
|
|
52
|
+
declare type RequestResponder<Sa, Ri, Ro> = {
|
|
53
|
+
(services: Sa): (request: Ri) => Ro | undefined;
|
|
54
|
+
<RoF>(services: Sa, responseMiddleware: (app: Sa) => (response: Ro | undefined, request: Ri) => RoF): (request: Ri) => RoF;
|
|
55
|
+
};
|
|
56
|
+
export declare const makeGetRequestResponder: <Sa, Ru, Ri, Ro>() => ({ routes, pathExtractor, routeMatcher, errorHandler }: {
|
|
57
|
+
routes: () => AnySpecificRoute<Ru, Sa, Ri, Ro>[];
|
|
58
|
+
pathExtractor: RequestPathExtractor<Ri>;
|
|
59
|
+
routeMatcher?: RequestRouteMatcher<Ri, AnySpecificRoute<Ru, Sa, Ri, Ro>> | undefined;
|
|
60
|
+
errorHandler?: ((e: Error) => Ro) | undefined;
|
|
61
|
+
}) => RequestResponder<Sa, Ri, Ro>;
|
|
62
|
+
export declare type HttpHeaders = {
|
|
63
|
+
[key: string]: string | undefined;
|
|
64
|
+
};
|
|
65
|
+
export declare type JsonCompatibleValue = string | number | null | undefined | boolean;
|
|
66
|
+
export declare type JsonCompatibleArray = Array<JsonCompatibleValue | JsonCompatibleStruct | JsonCompatibleStruct>;
|
|
67
|
+
export declare type JsonCompatibleStruct = {
|
|
68
|
+
[key: string]: JsonCompatibleStruct | JsonCompatibleValue | JsonCompatibleArray;
|
|
69
|
+
};
|
|
70
|
+
export declare type ApiResponse<S extends number, T> = {
|
|
71
|
+
statusCode: S;
|
|
72
|
+
data: T;
|
|
73
|
+
body: string;
|
|
74
|
+
headers?: {
|
|
75
|
+
[key: string]: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export declare const apiJsonResponse: <S extends number, T extends JsonCompatibleStruct>(statusCode: S, data: T, headers?: HttpHeaders | undefined) => ApiResponse<S, T>;
|
|
79
|
+
export declare const apiTextResponse: <S extends number>(statusCode: S, data: string, headers?: HttpHeaders | undefined) => ApiResponse<S, string>;
|
|
80
|
+
export declare enum METHOD {
|
|
81
|
+
GET = "GET",
|
|
82
|
+
HEAD = "HEAD",
|
|
83
|
+
POST = "POST",
|
|
84
|
+
PUT = "PUT",
|
|
85
|
+
PATCH = "PATCH",
|
|
86
|
+
DELETE = "DELETE",
|
|
87
|
+
OPTIONS = "OPTIONS"
|
|
88
|
+
}
|
|
89
|
+
export declare const getHeader: (headers: HttpHeaders, name: string) => string | undefined;
|
|
90
|
+
export declare const getRequestBody: (request: {
|
|
91
|
+
headers: HttpHeaders;
|
|
92
|
+
body?: string | undefined;
|
|
93
|
+
}) => any;
|
|
94
|
+
export declare const unsafePayloadValidator: <T>() => (input: any) => input is T;
|
|
95
|
+
export declare const requestPayloadProvider: <T>(validator: (input: any) => input is T) => () => <M extends {
|
|
96
|
+
request: Parameters<typeof getRequestBody>[0];
|
|
97
|
+
}>(requestServices: M) => M & {
|
|
98
|
+
payload: T;
|
|
99
|
+
};
|
|
100
|
+
export {};
|