@openstax/ts-utils 1.1.26 → 1.1.28

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 (117) hide show
  1. package/dist/{assertions.d.ts → cjs/assertions.d.ts} +0 -0
  2. package/dist/{assertions.js → cjs/assertions.js} +0 -0
  3. package/dist/{aws → cjs/aws}/securityTokenService.d.ts +0 -0
  4. package/dist/{aws → cjs/aws}/securityTokenService.js +0 -0
  5. package/dist/{aws → cjs/aws}/ssmService.d.ts +0 -0
  6. package/dist/{aws → cjs/aws}/ssmService.js +0 -0
  7. package/dist/{config.d.ts → cjs/config.d.ts} +0 -0
  8. package/dist/{config.js → cjs/config.js} +0 -0
  9. package/dist/{errors.d.ts → cjs/errors.d.ts} +0 -0
  10. package/dist/{errors.js → cjs/errors.js} +0 -0
  11. package/dist/{fetch.d.ts → cjs/fetch.d.ts} +0 -0
  12. package/dist/{fetch.js → cjs/fetch.js} +0 -0
  13. package/dist/{guards.d.ts → cjs/guards.d.ts} +0 -0
  14. package/dist/{guards.js → cjs/guards.js} +0 -0
  15. package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
  16. package/dist/{index.js → cjs/index.js} +0 -0
  17. package/dist/{middleware.d.ts → cjs/middleware.d.ts} +0 -0
  18. package/dist/{middleware.js → cjs/middleware.js} +0 -0
  19. package/dist/{pagination.d.ts → cjs/pagination.d.ts} +0 -0
  20. package/dist/{pagination.js → cjs/pagination.js} +0 -0
  21. package/dist/{profile.d.ts → cjs/profile.d.ts} +0 -0
  22. package/dist/{profile.js → cjs/profile.js} +0 -0
  23. package/dist/{routing.d.ts → cjs/routing.d.ts} +0 -0
  24. package/dist/{routing.js → cjs/routing.js} +0 -0
  25. package/dist/{services → cjs/services}/apiGateway/index.d.ts +0 -0
  26. package/dist/{services → cjs/services}/apiGateway/index.js +0 -0
  27. package/dist/{services → cjs/services}/authProvider/browser.d.ts +0 -0
  28. package/dist/{services → cjs/services}/authProvider/browser.js +0 -0
  29. package/dist/{services → cjs/services}/authProvider/decryption.d.ts +0 -0
  30. package/dist/{services → cjs/services}/authProvider/decryption.js +0 -0
  31. package/dist/{services → cjs/services}/authProvider/index.d.ts +0 -0
  32. package/dist/{services → cjs/services}/authProvider/index.js +0 -0
  33. package/dist/{services → cjs/services}/authProvider/subrequest.d.ts +0 -0
  34. package/dist/{services → cjs/services}/authProvider/subrequest.js +0 -0
  35. package/dist/{services → cjs/services}/authProvider/utils/embeddedAuthProvider.d.ts +0 -0
  36. package/dist/{services → cjs/services}/authProvider/utils/embeddedAuthProvider.js +0 -0
  37. package/dist/{services → cjs/services}/exercisesGateway/index.d.ts +0 -0
  38. package/dist/{services → cjs/services}/exercisesGateway/index.js +0 -0
  39. package/dist/{services → cjs/services}/lrsGateway/attempt-utils.d.ts +0 -0
  40. package/dist/{services → cjs/services}/lrsGateway/attempt-utils.js +0 -0
  41. package/dist/{services → cjs/services}/lrsGateway/file-system.d.ts +0 -0
  42. package/dist/{services → cjs/services}/lrsGateway/file-system.js +0 -0
  43. package/dist/{services → cjs/services}/lrsGateway/index.d.ts +0 -0
  44. package/dist/{services → cjs/services}/lrsGateway/index.js +0 -0
  45. package/dist/{services → cjs/services}/searchProvider/index.d.ts +0 -0
  46. package/dist/{services → cjs/services}/searchProvider/index.js +0 -0
  47. package/dist/{services → cjs/services}/searchProvider/memorySearchTheBadWay.d.ts +0 -0
  48. package/dist/{services → cjs/services}/searchProvider/memorySearchTheBadWay.js +0 -0
  49. package/dist/{services → cjs/services}/versionedDocumentStore/dynamodb.d.ts +0 -0
  50. package/dist/{services → cjs/services}/versionedDocumentStore/dynamodb.js +0 -0
  51. package/dist/{services → cjs/services}/versionedDocumentStore/file-system.d.ts +0 -0
  52. package/dist/{services → cjs/services}/versionedDocumentStore/file-system.js +0 -0
  53. package/dist/{services → cjs/services}/versionedDocumentStore/index.d.ts +0 -0
  54. package/dist/{services → cjs/services}/versionedDocumentStore/index.js +0 -0
  55. package/dist/cjs/tsconfig.withoutspecs.cjs.tsbuildinfo +1 -0
  56. package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
  57. package/dist/{types.js → cjs/types.js} +0 -0
  58. package/dist/esm/assertions.d.ts +9 -0
  59. package/dist/esm/assertions.js +90 -0
  60. package/dist/esm/aws/securityTokenService.d.ts +2 -0
  61. package/dist/esm/aws/securityTokenService.js +3 -0
  62. package/dist/esm/aws/ssmService.d.ts +2 -0
  63. package/dist/esm/aws/ssmService.js +3 -0
  64. package/dist/esm/config.d.ts +27 -0
  65. package/dist/esm/config.js +127 -0
  66. package/dist/esm/errors.d.ts +12 -0
  67. package/dist/esm/errors.js +26 -0
  68. package/dist/esm/fetch.d.ts +64 -0
  69. package/dist/esm/fetch.js +46 -0
  70. package/dist/esm/guards.d.ts +6 -0
  71. package/dist/esm/guards.js +29 -0
  72. package/dist/esm/index.d.ts +29 -0
  73. package/dist/esm/index.js +210 -0
  74. package/dist/esm/middleware.d.ts +9 -0
  75. package/dist/esm/middleware.js +34 -0
  76. package/dist/esm/pagination.d.ts +63 -0
  77. package/dist/esm/pagination.js +77 -0
  78. package/dist/esm/profile.d.ts +59 -0
  79. package/dist/esm/profile.js +191 -0
  80. package/dist/esm/routing.d.ts +107 -0
  81. package/dist/esm/routing.js +208 -0
  82. package/dist/esm/services/apiGateway/index.d.ts +55 -0
  83. package/dist/esm/services/apiGateway/index.js +51 -0
  84. package/dist/esm/services/authProvider/browser.d.ts +61 -0
  85. package/dist/esm/services/authProvider/browser.js +119 -0
  86. package/dist/esm/services/authProvider/decryption.d.ts +16 -0
  87. package/dist/esm/services/authProvider/decryption.js +61 -0
  88. package/dist/esm/services/authProvider/index.d.ts +42 -0
  89. package/dist/esm/services/authProvider/index.js +15 -0
  90. package/dist/esm/services/authProvider/subrequest.d.ts +16 -0
  91. package/dist/esm/services/authProvider/subrequest.js +36 -0
  92. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +20 -0
  93. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +30 -0
  94. package/dist/esm/services/exercisesGateway/index.d.ts +74 -0
  95. package/dist/esm/services/exercisesGateway/index.js +69 -0
  96. package/dist/esm/services/lrsGateway/attempt-utils.d.ts +62 -0
  97. package/dist/esm/services/lrsGateway/attempt-utils.js +251 -0
  98. package/dist/esm/services/lrsGateway/file-system.d.ts +15 -0
  99. package/dist/esm/services/lrsGateway/file-system.js +96 -0
  100. package/dist/esm/services/lrsGateway/index.d.ts +110 -0
  101. package/dist/esm/services/lrsGateway/index.js +87 -0
  102. package/dist/esm/services/searchProvider/index.d.ts +19 -0
  103. package/dist/esm/services/searchProvider/index.js +1 -0
  104. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +12 -0
  105. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +53 -0
  106. package/dist/esm/services/versionedDocumentStore/dynamodb.d.ts +23 -0
  107. package/dist/esm/services/versionedDocumentStore/dynamodb.js +147 -0
  108. package/dist/esm/services/versionedDocumentStore/file-system.d.ts +25 -0
  109. package/dist/esm/services/versionedDocumentStore/file-system.js +81 -0
  110. package/dist/esm/services/versionedDocumentStore/index.d.ts +23 -0
  111. package/dist/esm/services/versionedDocumentStore/index.js +1 -0
  112. package/dist/esm/tsconfig.withoutspecs.esm.tsbuildinfo +1 -0
  113. package/dist/esm/types.d.ts +6 -0
  114. package/dist/esm/types.js +1 -0
  115. package/package.json +25 -8
  116. package/dist/tsconfig.tsbuildinfo +0 -1
  117. package/dist/tsconfig.withoutspecs.tsbuildinfo +0 -1
@@ -0,0 +1,61 @@
1
+ import { compactDecrypt, compactVerify, importSPKI } from 'jose';
2
+ import { once } from '../..';
3
+ import { resolveConfigValue } from '../../config';
4
+ import { ifDefined } from '../../guards';
5
+ import { getAuthTokenOrCookie } from '.';
6
+ export const decryptionAuthProvider = (initializer) => (configProvider) => {
7
+ const config = configProvider[ifDefined(initializer.configSpace, 'decryption')];
8
+ const cookieName = once(() => resolveConfigValue(config.cookieName));
9
+ const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
10
+ const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
11
+ const decryptAndVerify = async (jwt) => {
12
+ try {
13
+ // Decrypt SSO cookie
14
+ const { plaintext } = await compactDecrypt(jwt, Buffer.from(await encryptionPrivateKey()), // Note: Buffer.from() is node-js only
15
+ { contentEncryptionAlgorithms: ['A256GCM'], keyManagementAlgorithms: ['dir'] });
16
+ // Verify SSO cookie signature
17
+ const { payload } = await compactVerify(plaintext, await importSPKI(await signaturePublicKey(), 'RS256'), { algorithms: ['RS256'] });
18
+ return payload;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ };
24
+ return ({ request, profile }) => {
25
+ let user;
26
+ const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
27
+ const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
28
+ if (!token) {
29
+ return {};
30
+ }
31
+ return { headers };
32
+ });
33
+ const loadUser = profile.track('loadUser', () => async () => {
34
+ const [token] = getAuthTokenOrCookie(request, await cookieName());
35
+ if (!token) {
36
+ return undefined;
37
+ }
38
+ const payload = await decryptAndVerify(token);
39
+ if (!payload) {
40
+ return undefined;
41
+ }
42
+ // Note: Uint8Array.toString() returns text in node-js only
43
+ // The browser version is new TextDecoder().decode(payload)
44
+ const jwt = JSON.parse(payload.toString());
45
+ // Allow clock skew up to 5 minutes
46
+ if (!jwt.sub || !jwt.sub.uuid || (jwt.exp && jwt.exp < Math.floor(Date.now() / 1000) - 300)) {
47
+ return undefined;
48
+ }
49
+ return jwt.sub;
50
+ });
51
+ return {
52
+ getAuthorizedFetchConfig,
53
+ getUser: async () => {
54
+ if (!user) {
55
+ user = await loadUser();
56
+ }
57
+ return user;
58
+ }
59
+ };
60
+ };
61
+ };
@@ -0,0 +1,42 @@
1
+ import { FetchConfig } from '../../fetch';
2
+ import { Track } from '../../profile';
3
+ import { HttpHeaders } from '../../routing';
4
+ export interface User {
5
+ id: number;
6
+ name: string;
7
+ first_name: string;
8
+ last_name: string;
9
+ full_name: string;
10
+ uuid: string;
11
+ faculty_status: string;
12
+ is_administrator: boolean;
13
+ is_not_gdpr_location: boolean;
14
+ contact_infos: Array<{
15
+ type: string;
16
+ value: string;
17
+ is_verified: boolean;
18
+ is_guessed_preferred: boolean;
19
+ }>;
20
+ }
21
+ export declare type AuthProvider = {
22
+ getUser: () => Promise<User | undefined>;
23
+ /**
24
+ * gets second argument for `fetch` that has authentication token or cookie
25
+ */
26
+ getAuthorizedFetchConfig: () => Promise<FetchConfig>;
27
+ };
28
+ export declare type CookieAuthProviderRequest = {
29
+ headers: HttpHeaders;
30
+ cookies?: string[];
31
+ };
32
+ export declare type CookieAuthProvider = (inputs: {
33
+ request: CookieAuthProviderRequest;
34
+ profile: Track;
35
+ }) => AuthProvider;
36
+ export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
37
+ export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
38
+ export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest, cookieName: string) => [string, {
39
+ Authorization: string;
40
+ }] | [string, {
41
+ cookie: string;
42
+ }];
@@ -0,0 +1,15 @@
1
+ import cookie from 'cookie';
2
+ import { tuple } from '../..';
3
+ import { getHeader } from '../../routing';
4
+ export const stubAuthProvider = (user) => ({
5
+ getUser: () => Promise.resolve(user),
6
+ getAuthorizedFetchConfig: () => Promise.resolve(user ? { headers: { Authorization: user.uuid } } : {})
7
+ });
8
+ export const getAuthTokenOrCookie = (request, cookieName) => {
9
+ var _a;
10
+ const authHeader = getHeader(request.headers, 'authorization');
11
+ const cookieValue = cookie.parse(((_a = request.cookies) === null || _a === void 0 ? void 0 : _a.join('; ')) || '')[cookieName];
12
+ return authHeader && authHeader.length >= 8 && authHeader.startsWith('Bearer ')
13
+ ? tuple(authHeader.slice(7), { Authorization: authHeader })
14
+ : tuple(cookieValue, { cookie: cookie.serialize(cookieName, cookieValue) });
15
+ };
@@ -0,0 +1,16 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { GenericFetch } from '../../fetch';
3
+ import { CookieAuthProvider } from '.';
4
+ declare type Config = {
5
+ cookieName: string;
6
+ accountsUrl: string;
7
+ };
8
+ interface Initializer<C> {
9
+ configSpace?: C;
10
+ fetch: GenericFetch;
11
+ }
12
+ export declare const subrequestAuthProvider: <C extends string = "subrequest">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
13
+ cookieName: import("../../config").ConfigValueProvider<string>;
14
+ accountsUrl: import("../../config").ConfigValueProvider<string>;
15
+ }; }) => CookieAuthProvider;
16
+ export {};
@@ -0,0 +1,36 @@
1
+ import { once } from '../..';
2
+ import { resolveConfigValue } from '../../config';
3
+ import { ifDefined } from '../../guards';
4
+ import { getAuthTokenOrCookie } from '.';
5
+ export const subrequestAuthProvider = (initializer) => (configProvider) => {
6
+ const config = configProvider[ifDefined(initializer.configSpace, 'subrequest')];
7
+ const cookieName = once(() => resolveConfigValue(config.cookieName));
8
+ const accountsUrl = once(() => resolveConfigValue(config.accountsUrl));
9
+ return ({ request, profile }) => {
10
+ let user;
11
+ const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
12
+ const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
13
+ if (!token) {
14
+ return {};
15
+ }
16
+ return { headers };
17
+ });
18
+ const loadUser = profile.track('loadUser', p => async () => {
19
+ const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
20
+ if (!token) {
21
+ return undefined;
22
+ }
23
+ return p.trackFetch(initializer.fetch)(await accountsUrl(), { headers })
24
+ .then(response => response.json());
25
+ });
26
+ return {
27
+ getAuthorizedFetchConfig,
28
+ getUser: async () => {
29
+ if (!user) {
30
+ user = await loadUser();
31
+ }
32
+ return user;
33
+ }
34
+ };
35
+ };
36
+ };
@@ -0,0 +1,20 @@
1
+ import { User } from '..';
2
+ import { Window } from '../browser';
3
+ export declare type UserData = {
4
+ user?: User;
5
+ token: string | null;
6
+ };
7
+ declare type UserDataLoader = () => Promise<UserData>;
8
+ export declare enum PostMessageTypes {
9
+ ReceiveUser = "receive-user",
10
+ RequestUser = "request-user"
11
+ }
12
+ export declare const embeddedAuthProvider: (getUserData: UserDataLoader, { queryKey, window }: {
13
+ queryKey?: string | undefined;
14
+ window: Window;
15
+ }) => {
16
+ embeddedQueryValue: string;
17
+ getAuthorizedEmbedUrl: (urlString: string) => string;
18
+ unmount: () => void;
19
+ };
20
+ export {};
@@ -0,0 +1,30 @@
1
+ export var PostMessageTypes;
2
+ (function (PostMessageTypes) {
3
+ PostMessageTypes["ReceiveUser"] = "receive-user";
4
+ PostMessageTypes["RequestUser"] = "request-user";
5
+ })(PostMessageTypes || (PostMessageTypes = {}));
6
+ export const embeddedAuthProvider = (getUserData, { queryKey = 'auth', window }) => {
7
+ const trustedEmbeds = new Set();
8
+ const embeddedQueryValue = 'embedded';
9
+ const messageHandler = event => {
10
+ if (event.data.type === PostMessageTypes.RequestUser && trustedEmbeds.has(event.origin)) {
11
+ getUserData().then(data => {
12
+ event.source.postMessage({ type: PostMessageTypes.ReceiveUser, userData: data }, event.origin);
13
+ });
14
+ }
15
+ };
16
+ window.addEventListener('message', messageHandler);
17
+ const getAuthorizedEmbedUrl = (urlString) => {
18
+ const url = new URL(urlString);
19
+ trustedEmbeds.add(url.origin);
20
+ url.searchParams.set(queryKey, embeddedQueryValue);
21
+ return url.href;
22
+ };
23
+ return {
24
+ embeddedQueryValue,
25
+ getAuthorizedEmbedUrl,
26
+ unmount: () => {
27
+ window.removeEventListener('message', messageHandler);
28
+ }
29
+ };
30
+ };
@@ -0,0 +1,74 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { GenericFetch } from '../../fetch';
3
+ import { Track } from '../../profile';
4
+ export declare type Config = {
5
+ defaultCorrectness?: string;
6
+ exercisesHost: string;
7
+ exercisesAuthToken: string;
8
+ };
9
+ interface Initializer<C> {
10
+ configSpace?: C;
11
+ fetch: GenericFetch;
12
+ }
13
+ export declare type Answer = {
14
+ id: number;
15
+ content_html: string;
16
+ correctness?: string;
17
+ feedback_html?: string;
18
+ };
19
+ export declare type Solution = {
20
+ images: any[];
21
+ solution_type: string;
22
+ content_html: string;
23
+ };
24
+ export declare type Question = {
25
+ id: number;
26
+ is_answer_order_important: boolean;
27
+ stimulus_html: string;
28
+ stem_html: string;
29
+ answers: Answer[];
30
+ hints: string[];
31
+ formats: string[];
32
+ combo_choices: any[];
33
+ collaborator_solutions?: Solution[];
34
+ community_solutions?: Solution[];
35
+ };
36
+ export declare type Exercise = {
37
+ images: any[];
38
+ tags: string[];
39
+ uuid: string;
40
+ group_uuid: string;
41
+ number: number;
42
+ version: number;
43
+ uid: string;
44
+ published_at: string;
45
+ solutions_are_public: boolean;
46
+ authors: any[];
47
+ copyright_holders: any[];
48
+ derived_from: any[];
49
+ is_vocab: boolean;
50
+ questions: Question[];
51
+ delegations: any[];
52
+ versions: number[];
53
+ stimulus_html: string;
54
+ };
55
+ export declare type ExercisesSearchResults = {
56
+ total_count: number;
57
+ items: Exercise[];
58
+ };
59
+ export declare type ExercisesSearchResultsWithDigest = ExercisesSearchResults & {
60
+ digest: string;
61
+ };
62
+ export declare const exercisesGateway: <C extends string = "exercises">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
63
+ defaultCorrectness?: import("../../config").ConfigValueProvider<string> | undefined;
64
+ exercisesHost: import("../../config").ConfigValueProvider<string>;
65
+ exercisesAuthToken: import("../../config").ConfigValueProvider<string>;
66
+ }; }) => ({ profile }: {
67
+ profile: Track;
68
+ }) => {
69
+ searchDigest: (query: string, page?: any, per_page?: any) => Promise<string>;
70
+ get: (uuid: string) => Promise<Exercise | undefined>;
71
+ search: (query: string, page?: any, per_page?: any) => Promise<ExercisesSearchResultsWithDigest>;
72
+ };
73
+ export declare type ExercisesGateway = ReturnType<ReturnType<ReturnType<typeof exercisesGateway>>>;
74
+ export {};
@@ -0,0 +1,69 @@
1
+ import * as queryString from 'query-string';
2
+ import { once } from '../..';
3
+ import { assertString } from '../../assertions';
4
+ import { resolveConfigValue } from '../../config';
5
+ import { ifDefined } from '../../guards';
6
+ import { METHOD } from '../../routing';
7
+ export const exercisesGateway = (initializer) => (configProvider) => {
8
+ const config = configProvider[ifDefined(initializer.configSpace, 'exercises')];
9
+ const exercisesHost = once(() => resolveConfigValue(config.exercisesHost));
10
+ const exercisesAuthToken = once(() => resolveConfigValue(config.exercisesAuthToken));
11
+ const defaultCorrectness = once(() => resolveConfigValue(config.defaultCorrectness || ''));
12
+ const doDefaultCorrectness = async (exercise) => {
13
+ if (await defaultCorrectness() !== 'true') {
14
+ return exercise;
15
+ }
16
+ for (const question of exercise.questions) {
17
+ const existingCorrect = question.answers.find(answer => answer.correctness !== undefined);
18
+ if (question.answers.length < 1 || existingCorrect) {
19
+ continue;
20
+ }
21
+ const defaultCorrectIndex = question.id % question.answers.length;
22
+ const defaultCorrect = question.answers[defaultCorrectIndex];
23
+ const defaultHint = `<em>random default: the correct answer is ${defaultCorrect.id}: ${defaultCorrect.content_html.slice(0, 20)}</em>`;
24
+ question.stem_html += `\n<br>${defaultHint}`;
25
+ question.collaborator_solutions = [
26
+ { solution_type: 'detailed', images: [], content_html: defaultHint }
27
+ ];
28
+ for (const [index, answer] of question.answers.entries()) {
29
+ answer.correctness = defaultCorrectIndex === index ? '1.0' : '0.0';
30
+ answer.feedback_html = defaultCorrectIndex === index ? 'This is the good one!' : defaultHint;
31
+ }
32
+ }
33
+ return exercise;
34
+ };
35
+ return ({ profile }) => {
36
+ const request = async (method, path, query = undefined) => {
37
+ const host = (await exercisesHost()).replace(/\/+$/, '');
38
+ const baseUrl = `${host}/api/${path}`;
39
+ const url = query ? `${baseUrl}?${queryString.stringify(query)}` : baseUrl;
40
+ return initializer.fetch(url, {
41
+ headers: {
42
+ Authorization: `Bearer ${await exercisesAuthToken()}`,
43
+ },
44
+ method,
45
+ });
46
+ };
47
+ const searchDigest = profile.track('searchExercisesDigest', () => async (query, page = 1, per_page = 100) => {
48
+ const response = await request(METHOD.HEAD, 'exercises', { query, page, per_page });
49
+ return assertString(response.headers.get('X-Digest'), 'OpenStax Exercises search endpoint HEAD did not return an X-Digest header');
50
+ });
51
+ const search = profile.track('searchExercises', () => async (query, page = 1, per_page = 100) => {
52
+ const response = await request(METHOD.GET, 'exercises', { query, page, per_page });
53
+ const digest = assertString(response.headers.get('X-Digest'), 'OpenStax Exercises search endpoint GET did not return an X-Digest header');
54
+ const { items, total_count } = await response.json();
55
+ return { digest, items: await Promise.all(items.map(doDefaultCorrectness)), total_count };
56
+ });
57
+ const get = profile.track('getExercise', () => async (uuid) => {
58
+ const response = await request(METHOD.GET, `exercises/${uuid}`);
59
+ return response.status === 404
60
+ ? undefined
61
+ : response.json().then(doDefaultCorrectness);
62
+ });
63
+ return {
64
+ searchDigest,
65
+ get,
66
+ search,
67
+ };
68
+ };
69
+ };
@@ -0,0 +1,62 @@
1
+ import { LrsGateway, XapiStatement } from '.';
2
+ export declare type ActivityState = {
3
+ attempts: number;
4
+ completedAttempts: number;
5
+ currentAttempt?: XapiStatement;
6
+ currentAttemptCompleted?: XapiStatement;
7
+ currentAttemptStatements: XapiStatement[];
8
+ mostRecentAttemptWithCompleted?: XapiStatement;
9
+ mostRecentAttemptWithCompletedCompleted?: XapiStatement;
10
+ };
11
+ export declare const matchAttempt: (statement: XapiStatement) => boolean;
12
+ export declare const matchAttemptCompleted: (attempt: XapiStatement) => (statement: XapiStatement) => boolean;
13
+ export declare const resolveActivityAttempts: (statements: XapiStatement[], activityIRI: string, parentActivityAttempt?: string | undefined) => XapiStatement[];
14
+ export declare const resolveCompletedForAttempt: (statements: XapiStatement[], activityIRI: string, attempt: XapiStatement) => XapiStatement | undefined;
15
+ export declare const oldestStatement: (statements: XapiStatement[]) => XapiStatement | undefined;
16
+ export declare const mostRecentStatement: (statements: XapiStatement[]) => XapiStatement | undefined;
17
+ export declare const resolveActivityAttemptInfo: (statements: XapiStatement[], activityIRI: string, options?: {
18
+ currentAttempt?: string | undefined;
19
+ parentActivityAttempt?: string | undefined;
20
+ currentPreference?: "latest" | "oldest" | undefined;
21
+ } | undefined) => ActivityState;
22
+ export declare const loadStatementsForActivityAndFirstChildren: (gateway: LrsGateway, activityIRI: string, attempt?: string | undefined) => Promise<XapiStatement[]>;
23
+ export declare const loadStatementsForAttempt: (gateway: LrsGateway, attempt: string) => Promise<XapiStatement[]>;
24
+ export declare const loadStatementsForActivity: (gateway: LrsGateway, activityIRI: string, attempt?: string | undefined) => Promise<XapiStatement[]>;
25
+ export declare const loadActivityAttemptInfo: (gateway: LrsGateway, activityIRI: string, options?: {
26
+ currentAttempt?: string | undefined;
27
+ parentActivityAttempt?: string | undefined;
28
+ } | undefined) => Promise<ActivityState>;
29
+ export declare const createStatement: (verb: XapiStatement['verb'], activity: {
30
+ iri: string;
31
+ type: string;
32
+ name: string;
33
+ extensions?: {
34
+ [key: string]: string;
35
+ } | undefined;
36
+ }, attempt: string, parentActivityIRI?: string | undefined) => Pick<XapiStatement, 'object' | 'verb' | 'context'>;
37
+ export declare const createAttemptStatement: (activity: {
38
+ iri: string;
39
+ type: string;
40
+ name: string;
41
+ extensions?: {
42
+ [key: string]: string;
43
+ } | undefined;
44
+ }, parentActivity?: {
45
+ iri?: string | undefined;
46
+ attempt?: string | undefined;
47
+ } | undefined) => Pick<XapiStatement, 'object' | 'verb' | 'context'>;
48
+ export declare const putAttemptStatement: (gateway: LrsGateway, activity: {
49
+ iri: string;
50
+ type: string;
51
+ name: string;
52
+ extensions?: {
53
+ [key: string]: string;
54
+ } | undefined;
55
+ }, parentActivity?: {
56
+ iri?: string | undefined;
57
+ attempt?: string | undefined;
58
+ } | undefined) => Promise<import(".").EagerXapiStatement>;
59
+ export declare const createAttemptActivityStatement: (attemptStatement: XapiStatement, verb: XapiStatement['verb'], result?: XapiStatement['result']) => Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'>;
60
+ export declare const putAttemptActivityStatement: (gateway: LrsGateway, attemptStatement: XapiStatement, verb: XapiStatement['verb'], result?: XapiStatement['result']) => Promise<import(".").EagerXapiStatement>;
61
+ export declare const createCompletedStatement: (attemptStatement: XapiStatement, result?: XapiStatement['result']) => Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'>;
62
+ export declare const putCompletedStatement: (gateway: LrsGateway, attemptStatement: XapiStatement, result: XapiStatement['result']) => Promise<import(".").EagerXapiStatement>;