@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.
Files changed (57) hide show
  1. package/README.md +118 -0
  2. package/dist/README.md +118 -0
  3. package/dist/assertions.d.ts +9 -0
  4. package/dist/assertions.js +99 -0
  5. package/dist/aws/securityTokenService.d.ts +2 -0
  6. package/dist/aws/securityTokenService.js +6 -0
  7. package/dist/aws/ssmService.d.ts +2 -0
  8. package/dist/aws/ssmService.js +6 -0
  9. package/dist/config.d.ts +26 -0
  10. package/dist/config.js +110 -0
  11. package/dist/errors.d.ts +12 -0
  12. package/dist/errors.js +32 -0
  13. package/dist/fetch.d.ts +59 -0
  14. package/dist/fetch.js +47 -0
  15. package/dist/guards.d.ts +5 -0
  16. package/dist/guards.js +34 -0
  17. package/dist/index.d.ts +16 -0
  18. package/dist/index.js +120 -0
  19. package/dist/middleware.d.ts +9 -0
  20. package/dist/middleware.js +38 -0
  21. package/dist/package.json +65 -0
  22. package/dist/pagination.d.ts +65 -0
  23. package/dist/pagination.js +83 -0
  24. package/dist/routing.d.ts +100 -0
  25. package/dist/routing.js +237 -0
  26. package/dist/services/apiGateway/index.d.ts +61 -0
  27. package/dist/services/apiGateway/index.js +70 -0
  28. package/dist/services/authProvider/browser.d.ts +17 -0
  29. package/dist/services/authProvider/browser.js +30 -0
  30. package/dist/services/authProvider/decryption.d.ts +16 -0
  31. package/dist/services/authProvider/decryption.js +56 -0
  32. package/dist/services/authProvider/index.d.ts +37 -0
  33. package/dist/services/authProvider/index.js +17 -0
  34. package/dist/services/authProvider/subrequest.d.ts +16 -0
  35. package/dist/services/authProvider/subrequest.js +39 -0
  36. package/dist/services/exercisesGateway/index.d.ts +80 -0
  37. package/dist/services/exercisesGateway/index.js +94 -0
  38. package/dist/services/lrsGateway/attempt-utils.d.ts +60 -0
  39. package/dist/services/lrsGateway/attempt-utils.js +270 -0
  40. package/dist/services/lrsGateway/file-system.d.ts +15 -0
  41. package/dist/services/lrsGateway/file-system.js +126 -0
  42. package/dist/services/lrsGateway/index.d.ts +110 -0
  43. package/dist/services/lrsGateway/index.js +116 -0
  44. package/dist/services/searchProvider/index.d.ts +20 -0
  45. package/dist/services/searchProvider/index.js +2 -0
  46. package/dist/services/searchProvider/memorySearchTheBadWay.d.ts +12 -0
  47. package/dist/services/searchProvider/memorySearchTheBadWay.js +51 -0
  48. package/dist/services/versionedDocumentStore/dynamodb.d.ts +20 -0
  49. package/dist/services/versionedDocumentStore/dynamodb.js +151 -0
  50. package/dist/services/versionedDocumentStore/file-system.d.ts +22 -0
  51. package/dist/services/versionedDocumentStore/file-system.js +112 -0
  52. package/dist/services/versionedDocumentStore/index.d.ts +23 -0
  53. package/dist/services/versionedDocumentStore/index.js +2 -0
  54. package/dist/tsconfig.tsbuildinfo +1 -0
  55. package/dist/types.d.ts +6 -0
  56. package/dist/types.js +2 -0
  57. 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;
@@ -0,0 +1,5 @@
1
+ export declare const isDefined: <X>(x: X) => x is Exclude<X, undefined>;
2
+ export declare const isPlainObject: (thing: any) => thing is {
3
+ [key: string]: any;
4
+ };
5
+ export declare const ifDefined: <X, D>(x: X, d: D) => D | Exclude<X, undefined>;
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;
@@ -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 {};