@openstax/ts-utils 1.6.2 → 1.8.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/dist/cjs/{fetch.d.ts → fetch/index.d.ts} +1 -1
- package/dist/{esm/middleware.d.ts → cjs/middleware/index.d.ts} +2 -2
- package/dist/cjs/misc/helpers.js +12 -1
- package/dist/cjs/{pagination.d.ts → pagination/index.d.ts} +1 -1
- package/dist/cjs/{pagination.js → pagination/index.js} +3 -3
- package/dist/cjs/routing/index.d.ts +0 -3
- package/dist/cjs/routing/index.js +7 -9
- package/dist/cjs/services/authProvider/decryption.js +5 -5
- package/dist/cjs/services/authProvider/index.d.ts +0 -2
- package/dist/cjs/services/authProvider/subrequest.js +6 -6
- package/dist/cjs/services/exercisesGateway/index.d.ts +3 -6
- package/dist/cjs/services/exercisesGateway/index.js +7 -7
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +4 -2
- package/dist/cjs/services/lrsGateway/xapiUtils.js +10 -7
- package/dist/cjs/services/versionedDocumentStore/dynamodb.d.ts +1 -4
- package/dist/cjs/services/versionedDocumentStore/dynamodb.js +13 -13
- package/dist/cjs/services/versionedDocumentStore/file-system.d.ts +1 -4
- package/dist/cjs/services/versionedDocumentStore/file-system.js +13 -13
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/cjs/types.d.ts +4 -0
- package/dist/esm/{fetch.d.ts → fetch/index.d.ts} +1 -1
- package/dist/{cjs/middleware.d.ts → esm/middleware/index.d.ts} +2 -2
- package/dist/esm/misc/helpers.js +12 -1
- package/dist/esm/{pagination.d.ts → pagination/index.d.ts} +1 -1
- package/dist/esm/{pagination.js → pagination/index.js} +3 -3
- package/dist/esm/routing/index.d.ts +0 -3
- package/dist/esm/routing/index.js +7 -9
- package/dist/esm/services/authProvider/decryption.js +5 -5
- package/dist/esm/services/authProvider/index.d.ts +0 -2
- package/dist/esm/services/authProvider/subrequest.js +6 -6
- package/dist/esm/services/exercisesGateway/index.d.ts +3 -6
- package/dist/esm/services/exercisesGateway/index.js +7 -7
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +4 -2
- package/dist/esm/services/lrsGateway/xapiUtils.js +10 -7
- package/dist/esm/services/versionedDocumentStore/dynamodb.d.ts +1 -4
- package/dist/esm/services/versionedDocumentStore/dynamodb.js +13 -13
- package/dist/esm/services/versionedDocumentStore/file-system.d.ts +1 -4
- package/dist/esm/services/versionedDocumentStore/file-system.js +13 -13
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/dist/esm/types.d.ts +4 -0
- package/package.json +108 -13
- package/dist/cjs/profile.d.ts +0 -61
- package/dist/cjs/profile.js +0 -199
- package/dist/esm/profile.d.ts +0 -61
- package/dist/esm/profile.js +0 -191
- /package/dist/cjs/{assertions.d.ts → assertions/index.d.ts} +0 -0
- /package/dist/cjs/{assertions.js → assertions/index.js} +0 -0
- /package/dist/cjs/{errors.d.ts → errors/index.d.ts} +0 -0
- /package/dist/cjs/{errors.js → errors/index.js} +0 -0
- /package/dist/cjs/{fetch.js → fetch/index.js} +0 -0
- /package/dist/cjs/{guards.d.ts → guards/index.d.ts} +0 -0
- /package/dist/cjs/{guards.js → guards/index.js} +0 -0
- /package/dist/cjs/{middleware.js → middleware/index.js} +0 -0
- /package/dist/esm/{assertions.d.ts → assertions/index.d.ts} +0 -0
- /package/dist/esm/{assertions.js → assertions/index.js} +0 -0
- /package/dist/esm/{errors.d.ts → errors/index.d.ts} +0 -0
- /package/dist/esm/{errors.js → errors/index.js} +0 -0
- /package/dist/esm/{fetch.js → fetch/index.js} +0 -0
- /package/dist/esm/{guards.d.ts → guards/index.d.ts} +0 -0
- /package/dist/esm/{guards.js → guards/index.js} +0 -0
- /package/dist/esm/{middleware.js → middleware/index.js} +0 -0
package/dist/cjs/types.d.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* There may be a better way to do this; `T1 extends T2` doesn't work
|
|
8
8
|
*/
|
|
9
9
|
export declare type TupleExtends<T1, T2> = T1 extends [infer T1Head, ...infer T1Tail] ? T2 extends [infer T2Head, ...infer T2Tail] ? T1Head extends T2Head ? TupleExtends<T1Tail, T2Tail> extends 'yes' ? 'yes' : 'no' : 'no' : T2 extends [] ? 'yes' : 'no' : T1 extends [] ? T2 extends [] ? 'yes' : 'no' : 'no';
|
|
10
|
+
/**
|
|
11
|
+
* unions each member of the tuple
|
|
12
|
+
*/
|
|
13
|
+
export declare type TupleZip<T1, T2> = T1 extends [infer T1Head, ...infer T1Tail] ? T2 extends [infer T2Head, ...infer T2Tail] ? [T1Head & T2Head, ...TupleZip<T1Tail, T2Tail>] : T2 extends [] ? T1 : never : T1 extends [] ? T2 extends any[] ? T2 : never : never;
|
|
10
14
|
/**
|
|
11
15
|
* If `R` is `Promise<I>`, returns `I`, otherwise returns `R`
|
|
12
16
|
* @deprecated use TypeScript builtin Awaited instead:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TupleZip } from '../types';
|
|
2
2
|
export declare type MiddlewareProvider<Sa, M, A extends any[], R> = (app: Sa, appBinder?: ((app: Sa, provider: MiddlewareProvider<Sa, M, A, R>) => (middleware: M, ...args: A) => R)) => (middleware: M, ...args: A) => R;
|
|
3
3
|
export declare type MiddlewareTransformProvider<Sa, M, A extends any[], R> = (app: Sa) => (middleware: M, ...args: A) => Omit<M, keyof R> & R;
|
|
4
4
|
export declare type MiddlewareInput<S> = S extends MiddlewareProvider<any, infer M, any, any> ? M : never;
|
|
5
5
|
export declare type ServiceMiddlewareProviderResult<M, S> = [M] extends [never] ? never : [M] extends [MiddlewareInput<S>] ? S extends MiddlewareTransformProvider<any, M, any, infer R> ? (Omit<R, keyof M> & M) extends R ? Omit<R, keyof M> & M : R extends M ? R : Omit<M, keyof R> & R : S extends MiddlewareProvider<any, M, any, infer R> ? R : never : never;
|
|
6
6
|
export declare type ServiceMiddlewareProviderArgs<S> = S extends MiddlewareProvider<any, any, infer A, any> ? A : never;
|
|
7
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> :
|
|
8
|
+
export declare type ServiceMiddlewareProviderChainArgs<C> = C extends [infer S1, ...infer S] ? S extends never[] ? ServiceMiddlewareProviderArgs<S1> : TupleZip<ServiceMiddlewareProviderChainArgs<S>, ServiceMiddlewareProviderArgs<S1>> : C extends unknown ? [] : never;
|
|
9
9
|
/**
|
|
10
10
|
* Creates a middleware composer for given AppServices and input value types. The composer accepts a list of middleware
|
|
11
11
|
* that it assembles into a chain. The function returned by the composer takes a value of the given input value type,
|
package/dist/esm/misc/helpers.js
CHANGED
|
@@ -106,7 +106,18 @@ export const mapFind = (array, mapper, predicate = (r) => !!r) => {
|
|
|
106
106
|
export const once = (fn) => {
|
|
107
107
|
const initialValue = {};
|
|
108
108
|
let result = initialValue;
|
|
109
|
-
return ((...args) =>
|
|
109
|
+
return ((...args) => {
|
|
110
|
+
if (result !== initialValue) {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
result = fn(...args);
|
|
114
|
+
if (typeof result === 'object' && result instanceof Promise) {
|
|
115
|
+
// Clear the result when possible but do not return a Promise that resolves to the initialValue
|
|
116
|
+
result.catch(() => result = initialValue);
|
|
117
|
+
}
|
|
118
|
+
// If this is a rejected Promise, it should be returned before catch() actually overwrites it
|
|
119
|
+
return result;
|
|
120
|
+
});
|
|
110
121
|
};
|
|
111
122
|
/**
|
|
112
123
|
* memoizes a function with any number of arguments
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { QueryParams, RouteMatchRecord } from '
|
|
1
|
+
import { QueryParams, RouteMatchRecord } from '../routing';
|
|
2
2
|
export declare type PaginationHandler<Pa, Q extends QueryParams> = <R>(queryParams: Q, match: RouteMatchRecord<R>) => Pa & {
|
|
3
3
|
getUnusedQueryParams: () => Q;
|
|
4
4
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { notNaN } from '
|
|
2
|
-
import { InvalidRequestError } from '
|
|
3
|
-
import { renderAnyRouteUrl } from '
|
|
1
|
+
import { notNaN } from '../assertions';
|
|
2
|
+
import { InvalidRequestError } from '../errors';
|
|
3
|
+
import { renderAnyRouteUrl } from '../routing';
|
|
4
4
|
/**
|
|
5
5
|
* helper to create middleware with the given paginator. aside from taking care of annoying to write pagination logic, these helpers also make
|
|
6
6
|
* sure that all item list responses have the same formatting.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Track } from '../profile';
|
|
2
1
|
import { Logger } from '../services/logger';
|
|
3
2
|
/** HTTP query parameters */
|
|
4
3
|
export declare type QueryParams = Record<string, string | undefined | string[] | null>;
|
|
@@ -35,7 +34,6 @@ export declare type PayloadForRoute<R> = RequestServicesForRoute<R> extends {
|
|
|
35
34
|
} ? RequestServicesForRoute<R>['payload'] : undefined;
|
|
36
35
|
declare type RequestServiceProvider<Sa, Sr, Ri> = (app: Sa) => <R>(middleware: {
|
|
37
36
|
request: Ri;
|
|
38
|
-
profile: Track;
|
|
39
37
|
logger: Logger;
|
|
40
38
|
}, match: RouteMatchRecord<R>) => Sr;
|
|
41
39
|
declare type RouteHandler<P, Sr, Ro> = (params: P, request: Sr) => Ro;
|
|
@@ -168,7 +166,6 @@ declare type RequestResponder<Sa, Ri, Ro> = {
|
|
|
168
166
|
(services: CompatibleServices<Sa>): (request: Ri) => Ro | undefined;
|
|
169
167
|
<RoF>(services: CompatibleServices<Sa>, responseMiddleware: (app: Sa) => (response: Ro | undefined, request: {
|
|
170
168
|
request: Ri;
|
|
171
|
-
profile: Track;
|
|
172
169
|
logger: Logger;
|
|
173
170
|
}) => RoF): (request: Ri) => RoF;
|
|
174
171
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as pathToRegexp from 'path-to-regexp';
|
|
2
2
|
import queryString from 'query-string';
|
|
3
3
|
import { mapFind, memoize } from '../misc/helpers';
|
|
4
|
-
import { createProfile } from '../profile';
|
|
5
4
|
import { createConsoleLogger } from '../services/logger/console';
|
|
6
5
|
/**
|
|
7
6
|
* Makes a createRoute function that can be used to create routes (this is a factory factory). The
|
|
@@ -102,11 +101,11 @@ export const renderAnyRouteUrl = makeRenderRouteUrl();
|
|
|
102
101
|
const bindRoute = (services, appBinder, pathExtractor, matcher) => (route) => {
|
|
103
102
|
const getParamsFromPath = pathToRegexp.match(route.path, { decode: decodeURIComponent });
|
|
104
103
|
const boundServiceProvider = route.requestServiceProvider && appBinder(services, route.requestServiceProvider);
|
|
105
|
-
return (request,
|
|
104
|
+
return (request, logger) => {
|
|
106
105
|
const path = pathExtractor(request);
|
|
107
106
|
const match = getParamsFromPath(path);
|
|
108
107
|
if ((!matcher || matcher(request, route)) && match) {
|
|
109
|
-
return
|
|
108
|
+
return () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, logger }, { route, params: match.params }) : undefined);
|
|
110
109
|
}
|
|
111
110
|
};
|
|
112
111
|
};
|
|
@@ -155,19 +154,18 @@ export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatc
|
|
|
155
154
|
// types are getting complicated quickly here.
|
|
156
155
|
const isPromise = (thing) => thing instanceof Promise;
|
|
157
156
|
return (request) => {
|
|
158
|
-
const { end, ...profile } = createProfile(new Date().toISOString()).start();
|
|
159
157
|
const logger = appLogger.createSubContext();
|
|
160
158
|
if (logExtractor) {
|
|
161
159
|
logger.setContext(logExtractor(request));
|
|
162
160
|
}
|
|
163
161
|
try {
|
|
164
|
-
const executor = mapFind(boundRoutes, (route) => route(request,
|
|
162
|
+
const executor = mapFind(boundRoutes, (route) => route(request, logger));
|
|
165
163
|
if (executor) {
|
|
166
164
|
const result = boundResponseMiddleware ?
|
|
167
|
-
boundResponseMiddleware(executor(), { request,
|
|
165
|
+
boundResponseMiddleware(executor(), { request, logger }) : executor();
|
|
168
166
|
if (isPromise(result) && errorHandler) {
|
|
169
167
|
const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
|
|
170
|
-
boundResponseMiddleware(errorHandler(e, logger), { request,
|
|
168
|
+
boundResponseMiddleware(errorHandler(e, logger), { request, logger }) : errorHandler(e, logger);
|
|
171
169
|
return result.catch(errorHandlerWithMiddleware);
|
|
172
170
|
}
|
|
173
171
|
else {
|
|
@@ -175,13 +173,13 @@ export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatc
|
|
|
175
173
|
}
|
|
176
174
|
}
|
|
177
175
|
else if (boundResponseMiddleware) {
|
|
178
|
-
return boundResponseMiddleware(undefined, { request,
|
|
176
|
+
return boundResponseMiddleware(undefined, { request, logger });
|
|
179
177
|
}
|
|
180
178
|
}
|
|
181
179
|
catch (e) {
|
|
182
180
|
if (errorHandler && e instanceof Error) {
|
|
183
181
|
return boundResponseMiddleware
|
|
184
|
-
? boundResponseMiddleware(errorHandler(e, logger), { request,
|
|
182
|
+
? boundResponseMiddleware(errorHandler(e, logger), { request, logger })
|
|
185
183
|
: errorHandler(e, logger);
|
|
186
184
|
}
|
|
187
185
|
throw e;
|
|
@@ -8,22 +8,22 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
8
8
|
const cookieName = once(() => resolveConfigValue(config.cookieName));
|
|
9
9
|
const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
|
|
10
10
|
const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
|
|
11
|
-
return ({ request
|
|
11
|
+
return ({ request }) => {
|
|
12
12
|
let user;
|
|
13
|
-
const getAuthorizedFetchConfig =
|
|
13
|
+
const getAuthorizedFetchConfig = async () => {
|
|
14
14
|
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
15
15
|
if (!token) {
|
|
16
16
|
return {};
|
|
17
17
|
}
|
|
18
18
|
return { headers };
|
|
19
|
-
}
|
|
20
|
-
const loadUser =
|
|
19
|
+
};
|
|
20
|
+
const loadUser = async () => {
|
|
21
21
|
const [token] = getAuthTokenOrCookie(request, await cookieName());
|
|
22
22
|
if (!token) {
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
25
|
return decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
|
|
26
|
-
}
|
|
26
|
+
};
|
|
27
27
|
return {
|
|
28
28
|
getAuthorizedFetchConfig,
|
|
29
29
|
getUser: async () => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { FetchConfig } from '../../fetch';
|
|
2
|
-
import type { Track } from '../../profile';
|
|
3
2
|
import type { HttpHeaders } from '../../routing';
|
|
4
3
|
export interface TokenUser {
|
|
5
4
|
id: number;
|
|
@@ -41,7 +40,6 @@ export declare type CookieAuthProviderRequest = {
|
|
|
41
40
|
};
|
|
42
41
|
export declare type CookieAuthProvider = (inputs: {
|
|
43
42
|
request: CookieAuthProviderRequest;
|
|
44
|
-
profile: Track;
|
|
45
43
|
}) => AuthProvider;
|
|
46
44
|
export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
|
|
47
45
|
export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
|
|
@@ -6,23 +6,23 @@ export const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
6
6
|
const config = configProvider[ifDefined(initializer.configSpace, 'subrequest')];
|
|
7
7
|
const cookieName = once(() => resolveConfigValue(config.cookieName));
|
|
8
8
|
const accountsBase = once(() => resolveConfigValue(config.accountsBase));
|
|
9
|
-
return ({ request
|
|
9
|
+
return ({ request }) => {
|
|
10
10
|
let user;
|
|
11
|
-
const getAuthorizedFetchConfig =
|
|
11
|
+
const getAuthorizedFetchConfig = async () => {
|
|
12
12
|
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
13
13
|
if (!token) {
|
|
14
14
|
return {};
|
|
15
15
|
}
|
|
16
16
|
return { headers };
|
|
17
|
-
}
|
|
18
|
-
const loadUser =
|
|
17
|
+
};
|
|
18
|
+
const loadUser = async () => {
|
|
19
19
|
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
20
20
|
if (!token) {
|
|
21
21
|
return undefined;
|
|
22
22
|
}
|
|
23
|
-
return
|
|
23
|
+
return initializer.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
|
|
24
24
|
.then(response => response.json());
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
26
|
return {
|
|
27
27
|
getAuthorizedFetchConfig,
|
|
28
28
|
getUser: async () => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
2
|
import { GenericFetch } from '../../fetch';
|
|
3
|
-
import { Track } from '../../profile';
|
|
4
3
|
export declare type Config = {
|
|
5
4
|
defaultCorrectness?: string;
|
|
6
5
|
exercisesHost: string;
|
|
@@ -63,12 +62,10 @@ export declare const exercisesGateway: <C extends string = "exercises">(initiali
|
|
|
63
62
|
defaultCorrectness?: import("../../config").ConfigValueProvider<string> | undefined;
|
|
64
63
|
exercisesHost: import("../../config").ConfigValueProvider<string>;
|
|
65
64
|
exercisesAuthToken: import("../../config").ConfigValueProvider<string>;
|
|
66
|
-
}; }) => ({
|
|
67
|
-
|
|
68
|
-
}) => {
|
|
69
|
-
searchDigest: (query: string, page?: any, per_page?: any) => Promise<string>;
|
|
65
|
+
}; }) => (_: {}) => {
|
|
66
|
+
searchDigest: (query: string, page?: number, per_page?: number) => Promise<string>;
|
|
70
67
|
get: (uuid: string) => Promise<Exercise | undefined>;
|
|
71
|
-
search: (query: string, page?:
|
|
68
|
+
search: (query: string, page?: number, per_page?: number) => Promise<ExercisesSearchResultsWithDigest>;
|
|
72
69
|
};
|
|
73
70
|
export declare type ExercisesGateway = ReturnType<ReturnType<ReturnType<typeof exercisesGateway>>>;
|
|
74
71
|
export {};
|
|
@@ -32,7 +32,7 @@ export const exercisesGateway = (initializer) => (configProvider) => {
|
|
|
32
32
|
}
|
|
33
33
|
return exercise;
|
|
34
34
|
};
|
|
35
|
-
return (
|
|
35
|
+
return (_) => {
|
|
36
36
|
const request = async (method, path, query = undefined) => {
|
|
37
37
|
const host = (await exercisesHost()).replace(/\/+$/, '');
|
|
38
38
|
const baseUrl = `${host}/api/${path}`;
|
|
@@ -44,22 +44,22 @@ export const exercisesGateway = (initializer) => (configProvider) => {
|
|
|
44
44
|
method,
|
|
45
45
|
});
|
|
46
46
|
};
|
|
47
|
-
const searchDigest =
|
|
47
|
+
const searchDigest = async (query, page = 1, per_page = 100) => {
|
|
48
48
|
const response = await request(METHOD.HEAD, 'exercises', { query, page, per_page });
|
|
49
49
|
return assertString(response.headers.get('X-Digest'), 'OpenStax Exercises search endpoint HEAD did not return an X-Digest header');
|
|
50
|
-
}
|
|
51
|
-
const search =
|
|
50
|
+
};
|
|
51
|
+
const search = async (query, page = 1, per_page = 100) => {
|
|
52
52
|
const response = await request(METHOD.GET, 'exercises', { query, page, per_page });
|
|
53
53
|
const digest = assertString(response.headers.get('X-Digest'), 'OpenStax Exercises search endpoint GET did not return an X-Digest header');
|
|
54
54
|
const { items, total_count } = await response.json();
|
|
55
55
|
return { digest, items: await Promise.all(items.map(doDefaultCorrectness)), total_count };
|
|
56
|
-
}
|
|
57
|
-
const get =
|
|
56
|
+
};
|
|
57
|
+
const get = async (uuid) => {
|
|
58
58
|
const response = await request(METHOD.GET, `exercises/${uuid}`);
|
|
59
59
|
return response.status === 404
|
|
60
60
|
? undefined
|
|
61
61
|
: response.json().then(doDefaultCorrectness);
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
63
|
return {
|
|
64
64
|
searchDigest,
|
|
65
65
|
get,
|
|
@@ -9,7 +9,7 @@ export interface Grade {
|
|
|
9
9
|
comment?: string;
|
|
10
10
|
activityProgress: 'Initialized' | 'Started' | 'inProgress' | 'Submitted' | 'Completed';
|
|
11
11
|
gradingProgress: 'FullyGraded' | 'Pending' | 'PendingManual' | 'Failed' | 'NotReady';
|
|
12
|
-
userId
|
|
12
|
+
userId?: string;
|
|
13
13
|
}
|
|
14
14
|
export declare const getRegistrationAttemptInfo: (lrs: LrsGateway, registration: string, options?: {
|
|
15
15
|
activity?: string | undefined;
|
|
@@ -25,7 +25,7 @@ export declare const getScoreGrade: (score: {
|
|
|
25
25
|
raw?: number;
|
|
26
26
|
min?: number;
|
|
27
27
|
max?: number;
|
|
28
|
-
}, completed: boolean, userId
|
|
28
|
+
}, completed: boolean, userId?: string | undefined, maxScore?: number | undefined) => Grade;
|
|
29
29
|
export declare type Progress = {
|
|
30
30
|
scaled: number;
|
|
31
31
|
max?: number;
|
|
@@ -34,6 +34,7 @@ export declare type Progress = {
|
|
|
34
34
|
export declare type GradeAndProgress = {
|
|
35
35
|
grade: Grade;
|
|
36
36
|
progress: Progress;
|
|
37
|
+
name?: string;
|
|
37
38
|
};
|
|
38
39
|
export declare const getCurrentGrade: (services: {
|
|
39
40
|
lrs: LrsGateway;
|
|
@@ -41,6 +42,7 @@ export declare const getCurrentGrade: (services: {
|
|
|
41
42
|
}, registration: string, options?: {
|
|
42
43
|
currentPreference?: "latest" | "oldest" | undefined;
|
|
43
44
|
incompleteAttemptCallback?: ((info: ActivityState) => Promise<GradeAndProgress>) | undefined;
|
|
45
|
+
name?: string | undefined;
|
|
44
46
|
scoreMaximum?: number | undefined;
|
|
45
47
|
userId?: string | undefined;
|
|
46
48
|
} | undefined) => Promise<GradeAndProgress | null>;
|
|
@@ -40,17 +40,20 @@ export const getScoreGrade = (score, completed, userId, maxScore) => {
|
|
|
40
40
|
scoreGiven: roundToPrecision(scoreGiven, -2),
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
|
-
// These methods
|
|
44
|
-
const getCompletedActivityStateGradeAndProgress = (state, userId
|
|
43
|
+
// These methods assign 0's to incomplete activities
|
|
44
|
+
const getCompletedActivityStateGradeAndProgress = ({ name, scoreMaximum, state, userId }) => {
|
|
45
45
|
var _a, _b;
|
|
46
46
|
return ({
|
|
47
|
-
grade: getScoreGrade(((_b = (_a = state.currentAttemptCompleted) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.score) || {}, !!state.currentAttemptCompleted, userId,
|
|
47
|
+
grade: getScoreGrade(((_b = (_a = state.currentAttemptCompleted) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.score) || {}, !!state.currentAttemptCompleted, userId, scoreMaximum),
|
|
48
48
|
progress: {
|
|
49
49
|
scaled: state.currentAttemptCompleted ? 1 : 0,
|
|
50
50
|
},
|
|
51
|
+
name,
|
|
51
52
|
});
|
|
52
53
|
};
|
|
53
|
-
const getCompletedUserInfosGradeAndProgress = (infos, scoreMaximum) => infos.map(({ data, fullName, platformUserId }) => getCompletedActivityStateGradeAndProgress(
|
|
54
|
+
const getCompletedUserInfosGradeAndProgress = (infos, scoreMaximum) => infos.map(({ data, fullName, platformUserId }) => getCompletedActivityStateGradeAndProgress({
|
|
55
|
+
name: fullName, scoreMaximum, userId: platformUserId, state: data
|
|
56
|
+
}));
|
|
54
57
|
export const getCurrentGrade = async (services, registration, options) => {
|
|
55
58
|
var _a;
|
|
56
59
|
const user = await services.ltiAuthProvider.getUser();
|
|
@@ -58,14 +61,14 @@ export const getCurrentGrade = async (services, registration, options) => {
|
|
|
58
61
|
return null;
|
|
59
62
|
}
|
|
60
63
|
const userId = (_a = options === null || options === void 0 ? void 0 : options.userId) !== null && _a !== void 0 ? _a : user.uuid;
|
|
61
|
-
const { currentPreference, incompleteAttemptCallback, scoreMaximum } = options !== null && options !== void 0 ? options : {};
|
|
64
|
+
const { currentPreference, incompleteAttemptCallback, name, scoreMaximum } = options !== null && options !== void 0 ? options : {};
|
|
62
65
|
const infoPerUser = await getRegistrationAttemptInfo(services.lrs, registration, { currentPreference });
|
|
63
66
|
const userInfo = infoPerUser[user.uuid];
|
|
64
67
|
if (!userInfo) {
|
|
65
|
-
return getCompletedActivityStateGradeAndProgress(resolveAttemptInfo([]), userId
|
|
68
|
+
return getCompletedActivityStateGradeAndProgress({ name, scoreMaximum, state: resolveAttemptInfo([]), userId });
|
|
66
69
|
}
|
|
67
70
|
if (userInfo.currentAttemptCompleted || !incompleteAttemptCallback) {
|
|
68
|
-
return getCompletedActivityStateGradeAndProgress(userInfo, userId
|
|
71
|
+
return getCompletedActivityStateGradeAndProgress({ name, scoreMaximum, state: userInfo, userId });
|
|
69
72
|
}
|
|
70
73
|
return incompleteAttemptCallback(userInfo);
|
|
71
74
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
-
import { Track } from '../../profile';
|
|
3
2
|
import { TDocument, VersionedDocumentAuthor } from '.';
|
|
4
3
|
declare type DynamoConfig = {
|
|
5
4
|
tableName: string;
|
|
@@ -9,9 +8,7 @@ interface Initializer<C> {
|
|
|
9
8
|
}
|
|
10
9
|
export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
|
|
11
10
|
tableName: import("../../config").ConfigValueProvider<string>;
|
|
12
|
-
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({
|
|
13
|
-
profile: Track;
|
|
14
|
-
}, hashKey: K, getAuthor: A) => {
|
|
11
|
+
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(_: {}, hashKey: K, getAuthor: A) => {
|
|
15
12
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
16
13
|
getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
|
|
17
14
|
items: T[];
|
|
@@ -48,11 +48,11 @@ const decodeDynamoAttribute = (value) => {
|
|
|
48
48
|
};
|
|
49
49
|
const decodeDynamoDocument = (document) => Object.fromEntries(Object.entries(document).map(([key, value]) => ([key, decodeDynamoAttribute(value)])));
|
|
50
50
|
// i'm not really excited about getAuthor being required, but ts is getting confused about the type when unspecified
|
|
51
|
-
export const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => (
|
|
51
|
+
export const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, getAuthor) => {
|
|
52
52
|
const options = ifDefined(initializer, {});
|
|
53
53
|
const tableName = once(() => resolveConfigValue(configProvider[ifDefined(options.configSpace, 'dynamodb')].tableName));
|
|
54
54
|
return {
|
|
55
|
-
loadAllDocumentsTheBadWay:
|
|
55
|
+
loadAllDocumentsTheBadWay: async () => {
|
|
56
56
|
const loadAllResults = async (ExclusiveStartKey) => {
|
|
57
57
|
var _a;
|
|
58
58
|
const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
|
|
@@ -71,8 +71,8 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
|
|
|
71
71
|
return result;
|
|
72
72
|
}, new Map()));
|
|
73
73
|
return Array.from(allResults.values());
|
|
74
|
-
}
|
|
75
|
-
getVersions:
|
|
74
|
+
},
|
|
75
|
+
getVersions: async (id, startVersion) => {
|
|
76
76
|
const cmd = new QueryCommand({
|
|
77
77
|
TableName: await tableName(),
|
|
78
78
|
KeyConditionExpression: '#hk = :hkv',
|
|
@@ -101,8 +101,8 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
|
|
|
101
101
|
nextPageToken: result.LastEvaluatedKey ? decodeDynamoDocument(result.LastEvaluatedKey).timestamp : undefined
|
|
102
102
|
};
|
|
103
103
|
});
|
|
104
|
-
}
|
|
105
|
-
getItem:
|
|
104
|
+
},
|
|
105
|
+
getItem: async (id, timestamp) => {
|
|
106
106
|
let keyConditionExpression = '#hk = :hkv';
|
|
107
107
|
const expressionAttributeNames = {
|
|
108
108
|
'#hk': hashKey.toString()
|
|
@@ -127,18 +127,18 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
|
|
|
127
127
|
var _a;
|
|
128
128
|
return (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)[0];
|
|
129
129
|
});
|
|
130
|
-
}
|
|
130
|
+
},
|
|
131
131
|
/* prepares a new version of a document with the given data, then allows some additional
|
|
132
132
|
* changes to be input to a `save` function that actually saves it. useful for additional
|
|
133
133
|
* changes based on the new document version or author. the document version and author
|
|
134
134
|
* cannot be modified */
|
|
135
|
-
prepareItem:
|
|
135
|
+
prepareItem: async (item, ...authorArgs) => {
|
|
136
136
|
// this getAuthor type is terrible
|
|
137
137
|
const author = getAuthor ? await getAuthor(...authorArgs) : authorArgs[0];
|
|
138
138
|
const timestamp = new Date().getTime();
|
|
139
139
|
return {
|
|
140
140
|
document: { ...item, timestamp, author },
|
|
141
|
-
save:
|
|
141
|
+
save: async (changes) => {
|
|
142
142
|
const document = {
|
|
143
143
|
...item,
|
|
144
144
|
...changes,
|
|
@@ -151,11 +151,11 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
|
|
|
151
151
|
});
|
|
152
152
|
return dynamodb().send(cmd)
|
|
153
153
|
.then(() => document);
|
|
154
|
-
}
|
|
154
|
+
}
|
|
155
155
|
};
|
|
156
|
-
}
|
|
156
|
+
},
|
|
157
157
|
/* saves a new version of a document with the given data */
|
|
158
|
-
putItem:
|
|
158
|
+
putItem: async (item, ...authorArgs) => {
|
|
159
159
|
// this getAuthor type is terrible
|
|
160
160
|
const author = getAuthor ? await getAuthor(...authorArgs) : authorArgs[0];
|
|
161
161
|
const document = {
|
|
@@ -169,6 +169,6 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
|
|
|
169
169
|
});
|
|
170
170
|
return dynamodb().send(cmd)
|
|
171
171
|
.then(() => document);
|
|
172
|
-
}
|
|
172
|
+
},
|
|
173
173
|
};
|
|
174
174
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
-
import { Track } from '../../profile';
|
|
3
2
|
import { TDocument, VersionedDocumentAuthor } from '.';
|
|
4
3
|
declare type Config = {
|
|
5
4
|
tableName: string;
|
|
@@ -11,9 +10,7 @@ interface Initializer<C> {
|
|
|
11
10
|
}
|
|
12
11
|
export declare const fileSystemVersionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
|
|
13
12
|
tableName: import("../../config").ConfigValueProvider<string>;
|
|
14
|
-
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({
|
|
15
|
-
profile: Track;
|
|
16
|
-
}, hashKey: K, getAuthor: A) => {
|
|
13
|
+
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(_: {}, hashKey: K, getAuthor: A) => {
|
|
17
14
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
18
15
|
getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
|
|
19
16
|
items: T[];
|
|
@@ -4,7 +4,7 @@ import { hashValue } from '../..';
|
|
|
4
4
|
import { resolveConfigValue } from '../../config';
|
|
5
5
|
import { ifDefined } from '../../guards';
|
|
6
6
|
const PAGE_LIMIT = 5;
|
|
7
|
-
export const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider) => (
|
|
7
|
+
export const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, getAuthor) => {
|
|
8
8
|
const tableName = resolveConfigValue(configProvider[initializer.configSpace || 'fileSystem'].tableName);
|
|
9
9
|
const filePath = tableName.then((table) => path.join(initializer.dataDir, table));
|
|
10
10
|
const { readFile, writeFile } = ifDefined(initializer.fs, fsModule);
|
|
@@ -30,14 +30,14 @@ export const fileSystemVersionedDocumentStore = (initializer) => () => (configPr
|
|
|
30
30
|
}));
|
|
31
31
|
let previousSave;
|
|
32
32
|
return {
|
|
33
|
-
loadAllDocumentsTheBadWay:
|
|
33
|
+
loadAllDocumentsTheBadWay: async () => {
|
|
34
34
|
await load;
|
|
35
35
|
return Object.entries(data || []).map(([, versions]) => {
|
|
36
36
|
const versionsList = Object.values(versions);
|
|
37
37
|
return versionsList[versionsList.length - 1];
|
|
38
38
|
});
|
|
39
|
-
}
|
|
40
|
-
getVersions:
|
|
39
|
+
},
|
|
40
|
+
getVersions: async (id, startVersion) => {
|
|
41
41
|
await load;
|
|
42
42
|
const versions = data === null || data === void 0 ? void 0 : data[hashValue(id)];
|
|
43
43
|
const versionsList = versions ? Object.values(versions).reverse() : undefined;
|
|
@@ -51,8 +51,8 @@ export const fileSystemVersionedDocumentStore = (initializer) => () => (configPr
|
|
|
51
51
|
items,
|
|
52
52
|
nextPageToken: hasMore ? items[items.length - 1].timestamp : undefined
|
|
53
53
|
};
|
|
54
|
-
}
|
|
55
|
-
getItem:
|
|
54
|
+
},
|
|
55
|
+
getItem: async (id, timestamp) => {
|
|
56
56
|
await load;
|
|
57
57
|
const versions = data === null || data === void 0 ? void 0 : data[hashValue(id)];
|
|
58
58
|
if (timestamp) {
|
|
@@ -60,14 +60,14 @@ export const fileSystemVersionedDocumentStore = (initializer) => () => (configPr
|
|
|
60
60
|
}
|
|
61
61
|
const versionsList = versions ? Object.values(versions) : [];
|
|
62
62
|
return versionsList[versionsList.length - 1];
|
|
63
|
-
}
|
|
64
|
-
prepareItem:
|
|
63
|
+
},
|
|
64
|
+
prepareItem: async (item, ...authorArgs) => {
|
|
65
65
|
// this getAuthor type is terrible
|
|
66
66
|
const author = getAuthor ? await getAuthor(...authorArgs) : authorArgs[0];
|
|
67
67
|
const timestamp = new Date().getTime();
|
|
68
68
|
return {
|
|
69
69
|
document: { ...item, timestamp, author },
|
|
70
|
-
save:
|
|
70
|
+
save: async (changes) => {
|
|
71
71
|
await load;
|
|
72
72
|
await previousSave;
|
|
73
73
|
const save = previousSave = new Promise(resolve => (async () => {
|
|
@@ -80,10 +80,10 @@ export const fileSystemVersionedDocumentStore = (initializer) => () => (configPr
|
|
|
80
80
|
writeFile(path, JSON.stringify(data, null, 2), () => resolve(document));
|
|
81
81
|
})());
|
|
82
82
|
return await save;
|
|
83
|
-
}
|
|
83
|
+
}
|
|
84
84
|
};
|
|
85
|
-
}
|
|
86
|
-
putItem:
|
|
85
|
+
},
|
|
86
|
+
putItem: async (item, ...authorArgs) => {
|
|
87
87
|
await load;
|
|
88
88
|
await previousSave;
|
|
89
89
|
const save = previousSave = new Promise(resolve => (async () => {
|
|
@@ -98,6 +98,6 @@ export const fileSystemVersionedDocumentStore = (initializer) => () => (configPr
|
|
|
98
98
|
writeFile(path, JSON.stringify(data, null, 2), () => resolve(document));
|
|
99
99
|
})());
|
|
100
100
|
return await save;
|
|
101
|
-
}
|
|
101
|
+
},
|
|
102
102
|
};
|
|
103
103
|
};
|