@openstax/ts-utils 1.1.27 → 1.1.29

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 (145) 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/cjs/config/awsAccountConfig.d.ts +5 -0
  8. package/dist/cjs/config/awsAccountConfig.js +35 -0
  9. package/dist/cjs/config/awsParameterConfig.d.ts +2 -0
  10. package/dist/cjs/config/awsParameterConfig.js +18 -0
  11. package/dist/cjs/config/envConfig.d.ts +3 -0
  12. package/dist/cjs/config/envConfig.js +35 -0
  13. package/dist/cjs/config/index.d.ts +21 -0
  14. package/dist/cjs/config/index.js +39 -0
  15. package/dist/cjs/config/lambdaParameterConfig.d.ts +2 -0
  16. package/dist/cjs/config/lambdaParameterConfig.js +36 -0
  17. package/dist/cjs/config/replaceConfig.d.ts +4 -0
  18. package/dist/cjs/config/replaceConfig.js +12 -0
  19. package/dist/cjs/config/resolveConfigValue.d.ts +2 -0
  20. package/dist/cjs/config/resolveConfigValue.js +12 -0
  21. package/dist/{config.d.ts → cjs/config.d.ts} +0 -0
  22. package/dist/{config.js → cjs/config.js} +0 -0
  23. package/dist/{errors.d.ts → cjs/errors.d.ts} +0 -0
  24. package/dist/{errors.js → cjs/errors.js} +0 -0
  25. package/dist/{fetch.d.ts → cjs/fetch.d.ts} +0 -0
  26. package/dist/{fetch.js → cjs/fetch.js} +0 -0
  27. package/dist/{guards.d.ts → cjs/guards.d.ts} +0 -0
  28. package/dist/{guards.js → cjs/guards.js} +0 -0
  29. package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
  30. package/dist/{index.js → cjs/index.js} +0 -0
  31. package/dist/{middleware.d.ts → cjs/middleware.d.ts} +0 -0
  32. package/dist/{middleware.js → cjs/middleware.js} +0 -0
  33. package/dist/{pagination.d.ts → cjs/pagination.d.ts} +0 -0
  34. package/dist/{pagination.js → cjs/pagination.js} +0 -0
  35. package/dist/{profile.d.ts → cjs/profile.d.ts} +0 -0
  36. package/dist/{profile.js → cjs/profile.js} +0 -0
  37. package/dist/{routing.d.ts → cjs/routing.d.ts} +0 -0
  38. package/dist/{routing.js → cjs/routing.js} +0 -0
  39. package/dist/{services → cjs/services}/apiGateway/index.d.ts +0 -0
  40. package/dist/{services → cjs/services}/apiGateway/index.js +0 -0
  41. package/dist/{services → cjs/services}/authProvider/browser.d.ts +0 -0
  42. package/dist/{services → cjs/services}/authProvider/browser.js +0 -0
  43. package/dist/{services → cjs/services}/authProvider/decryption.d.ts +0 -0
  44. package/dist/{services → cjs/services}/authProvider/decryption.js +0 -0
  45. package/dist/{services → cjs/services}/authProvider/index.d.ts +0 -0
  46. package/dist/{services → cjs/services}/authProvider/index.js +0 -0
  47. package/dist/{services → cjs/services}/authProvider/subrequest.d.ts +0 -0
  48. package/dist/{services → cjs/services}/authProvider/subrequest.js +0 -0
  49. package/dist/{services → cjs/services}/authProvider/utils/embeddedAuthProvider.d.ts +0 -0
  50. package/dist/{services → cjs/services}/authProvider/utils/embeddedAuthProvider.js +0 -0
  51. package/dist/{services → cjs/services}/exercisesGateway/index.d.ts +0 -0
  52. package/dist/{services → cjs/services}/exercisesGateway/index.js +0 -0
  53. package/dist/{services → cjs/services}/lrsGateway/attempt-utils.d.ts +0 -0
  54. package/dist/{services → cjs/services}/lrsGateway/attempt-utils.js +0 -0
  55. package/dist/{services → cjs/services}/lrsGateway/file-system.d.ts +0 -0
  56. package/dist/{services → cjs/services}/lrsGateway/file-system.js +0 -0
  57. package/dist/{services → cjs/services}/lrsGateway/index.d.ts +0 -0
  58. package/dist/{services → cjs/services}/lrsGateway/index.js +0 -0
  59. package/dist/{services → cjs/services}/searchProvider/index.d.ts +0 -0
  60. package/dist/{services → cjs/services}/searchProvider/index.js +0 -0
  61. package/dist/{services → cjs/services}/searchProvider/memorySearchTheBadWay.d.ts +0 -0
  62. package/dist/{services → cjs/services}/searchProvider/memorySearchTheBadWay.js +0 -0
  63. package/dist/{services → cjs/services}/versionedDocumentStore/dynamodb.d.ts +0 -0
  64. package/dist/{services → cjs/services}/versionedDocumentStore/dynamodb.js +0 -0
  65. package/dist/{services → cjs/services}/versionedDocumentStore/file-system.d.ts +0 -0
  66. package/dist/{services → cjs/services}/versionedDocumentStore/file-system.js +0 -0
  67. package/dist/{services → cjs/services}/versionedDocumentStore/index.d.ts +0 -0
  68. package/dist/{services → cjs/services}/versionedDocumentStore/index.js +0 -0
  69. package/dist/cjs/tsconfig.withoutspecs.cjs.tsbuildinfo +1 -0
  70. package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
  71. package/dist/{types.js → cjs/types.js} +0 -0
  72. package/dist/esm/assertions.d.ts +9 -0
  73. package/dist/esm/assertions.js +90 -0
  74. package/dist/esm/aws/securityTokenService.d.ts +2 -0
  75. package/dist/esm/aws/securityTokenService.js +3 -0
  76. package/dist/esm/aws/ssmService.d.ts +2 -0
  77. package/dist/esm/aws/ssmService.js +3 -0
  78. package/dist/esm/config/awsAccountConfig.d.ts +5 -0
  79. package/dist/esm/config/awsAccountConfig.js +31 -0
  80. package/dist/esm/config/awsParameterConfig.d.ts +2 -0
  81. package/dist/esm/config/awsParameterConfig.js +14 -0
  82. package/dist/esm/config/envConfig.d.ts +3 -0
  83. package/dist/esm/config/envConfig.js +31 -0
  84. package/dist/esm/config/index.d.ts +21 -0
  85. package/dist/esm/config/index.js +21 -0
  86. package/dist/esm/config/lambdaParameterConfig.d.ts +2 -0
  87. package/dist/esm/config/lambdaParameterConfig.js +29 -0
  88. package/dist/esm/config/replaceConfig.d.ts +4 -0
  89. package/dist/esm/config/replaceConfig.js +8 -0
  90. package/dist/esm/config/resolveConfigValue.d.ts +2 -0
  91. package/dist/esm/config/resolveConfigValue.js +8 -0
  92. package/dist/esm/config.d.ts +27 -0
  93. package/dist/esm/config.js +127 -0
  94. package/dist/esm/errors.d.ts +12 -0
  95. package/dist/esm/errors.js +26 -0
  96. package/dist/esm/fetch.d.ts +64 -0
  97. package/dist/esm/fetch.js +46 -0
  98. package/dist/esm/guards.d.ts +6 -0
  99. package/dist/esm/guards.js +29 -0
  100. package/dist/esm/index.d.ts +29 -0
  101. package/dist/esm/index.js +210 -0
  102. package/dist/esm/middleware.d.ts +9 -0
  103. package/dist/esm/middleware.js +34 -0
  104. package/dist/esm/pagination.d.ts +63 -0
  105. package/dist/esm/pagination.js +77 -0
  106. package/dist/esm/profile.d.ts +59 -0
  107. package/dist/esm/profile.js +191 -0
  108. package/dist/esm/routing.d.ts +107 -0
  109. package/dist/esm/routing.js +208 -0
  110. package/dist/esm/services/apiGateway/index.d.ts +55 -0
  111. package/dist/esm/services/apiGateway/index.js +51 -0
  112. package/dist/esm/services/authProvider/browser.d.ts +61 -0
  113. package/dist/esm/services/authProvider/browser.js +119 -0
  114. package/dist/esm/services/authProvider/decryption.d.ts +16 -0
  115. package/dist/esm/services/authProvider/decryption.js +61 -0
  116. package/dist/esm/services/authProvider/index.d.ts +42 -0
  117. package/dist/esm/services/authProvider/index.js +15 -0
  118. package/dist/esm/services/authProvider/subrequest.d.ts +16 -0
  119. package/dist/esm/services/authProvider/subrequest.js +36 -0
  120. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +20 -0
  121. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +30 -0
  122. package/dist/esm/services/exercisesGateway/index.d.ts +74 -0
  123. package/dist/esm/services/exercisesGateway/index.js +69 -0
  124. package/dist/esm/services/lrsGateway/attempt-utils.d.ts +62 -0
  125. package/dist/esm/services/lrsGateway/attempt-utils.js +251 -0
  126. package/dist/esm/services/lrsGateway/file-system.d.ts +15 -0
  127. package/dist/esm/services/lrsGateway/file-system.js +96 -0
  128. package/dist/esm/services/lrsGateway/index.d.ts +110 -0
  129. package/dist/esm/services/lrsGateway/index.js +87 -0
  130. package/dist/esm/services/searchProvider/index.d.ts +19 -0
  131. package/dist/esm/services/searchProvider/index.js +1 -0
  132. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +12 -0
  133. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +53 -0
  134. package/dist/esm/services/versionedDocumentStore/dynamodb.d.ts +23 -0
  135. package/dist/esm/services/versionedDocumentStore/dynamodb.js +147 -0
  136. package/dist/esm/services/versionedDocumentStore/file-system.d.ts +25 -0
  137. package/dist/esm/services/versionedDocumentStore/file-system.js +81 -0
  138. package/dist/esm/services/versionedDocumentStore/index.d.ts +23 -0
  139. package/dist/esm/services/versionedDocumentStore/index.js +1 -0
  140. package/dist/esm/tsconfig.withoutspecs.esm.tsbuildinfo +1 -0
  141. package/dist/esm/types.d.ts +6 -0
  142. package/dist/esm/types.js +1 -0
  143. package/package.json +24 -8
  144. package/dist/tsconfig.tsbuildinfo +0 -1
  145. package/dist/tsconfig.withoutspecs.tsbuildinfo +0 -1
@@ -0,0 +1,251 @@
1
+ /*
2
+ * the structure of xapi statements for handling multiple attempts of an activity
3
+ * including the option of a parent activity/attempt such that a new attempt
4
+ * of a parent activity inherently creates a new attempt scope for sub-activities
5
+ * is done by convention using certain context and verb pieces of a statement.
6
+ * this module provides helpers for creating and retrieving statements according
7
+ * to this convention.
8
+ */
9
+ import formatISODuration from 'date-fns/formatISODuration';
10
+ import intervalToDuration from 'date-fns/intervalToDuration';
11
+ import isAfter from 'date-fns/isAfter';
12
+ import isBefore from 'date-fns/isBefore';
13
+ import parseISO from 'date-fns/parseISO';
14
+ var Verb;
15
+ (function (Verb) {
16
+ Verb["Attempted"] = "http://adlnet.gov/expapi/verbs/attempted";
17
+ Verb["Completed"] = "http://adlnet.gov/expapi/verbs/completed";
18
+ })(Verb || (Verb = {}));
19
+ export const matchAttempt = (statement) => statement.verb.id === Verb.Attempted;
20
+ export const matchAttemptCompleted = (attempt) => (statement) => {
21
+ var _a, _b;
22
+ return statement.verb.id === Verb.Completed
23
+ && statement.context !== undefined
24
+ && ((_a = statement.context.statement) === null || _a === void 0 ? void 0 : _a.id) === attempt.id
25
+ && statement.context.registration === ((_b = attempt.context) === null || _b === void 0 ? void 0 : _b.registration);
26
+ };
27
+ export const resolveActivityAttempts = (statements, activityIRI, parentActivityAttempt) => statements.filter(statement => {
28
+ var _a;
29
+ return matchAttempt(statement)
30
+ && statement.object.id === activityIRI
31
+ && (!parentActivityAttempt || ((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === parentActivityAttempt);
32
+ });
33
+ export const resolveCompletedForAttempt = (statements, activityIRI, attempt) => statements.find(statement => matchAttemptCompleted(attempt)(statement)
34
+ && statement.object.id === activityIRI);
35
+ export const oldestStatement = (statements) => statements.reduce((result, statement) => result && isBefore(parseISO(result.stored || result.timestamp), parseISO(statement.timestamp)) ? result : statement, statements[0]);
36
+ export const mostRecentStatement = (statements) => statements.reduce((result, statement) => result && isAfter(parseISO(result.stored || result.timestamp), parseISO(statement.timestamp)) ? result : statement, statements[0]);
37
+ export const resolveActivityAttemptInfo = (statements, activityIRI, options) => {
38
+ // TODO optimize. i'm 100% that this could all be done in one iteration but i'm not messing around with that for now.
39
+ const attempts = resolveActivityAttempts(statements, activityIRI, options === null || options === void 0 ? void 0 : options.parentActivityAttempt);
40
+ /* attempts that have a completed statement */
41
+ const completedAttempts = attempts.filter(attempt => !!resolveCompletedForAttempt(statements, activityIRI, attempt));
42
+ /* the last attempt sorted by timestamp */
43
+ const currentAttempt = (options === null || options === void 0 ? void 0 : options.currentAttempt)
44
+ ? attempts.find(attempt => attempt.id === options.currentAttempt)
45
+ : (options === null || options === void 0 ? void 0 : options.currentPreference) === 'oldest'
46
+ ? oldestStatement(attempts)
47
+ : mostRecentStatement(attempts);
48
+ /* all statements for the current attempt (doesn't include the attempt or completed statements) */
49
+ const currentAttemptStatements = currentAttempt ? statements.filter(statement => {
50
+ var _a;
51
+ return statement.object.id === activityIRI
52
+ && ((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === currentAttempt.id;
53
+ }) : [];
54
+ const currentAttemptCompleted = currentAttempt && resolveCompletedForAttempt(statements, activityIRI, currentAttempt);
55
+ const mostRecentAttemptWithCompleted = completedAttempts.reduce((current, attempt) => current && isAfter(parseISO(current.timestamp), parseISO(attempt.timestamp)) ? current : attempt, completedAttempts[0]);
56
+ const mostRecentAttemptWithCompletedCompleted = mostRecentAttemptWithCompleted
57
+ && resolveCompletedForAttempt(statements, activityIRI, mostRecentAttemptWithCompleted);
58
+ /*
59
+ * the structure allows for the possibility of multiple incomplete attempts.
60
+ * the implementation can choose at its discretion to ignore the currentAttempt
61
+ * and instead make a new one, for instance if the implementation desires
62
+ * an attempt timeout feature
63
+ */
64
+ return {
65
+ attempts: attempts.length,
66
+ completedAttempts: completedAttempts.length,
67
+ currentAttempt,
68
+ currentAttemptCompleted: currentAttemptCompleted,
69
+ currentAttemptStatements,
70
+ mostRecentAttemptWithCompleted,
71
+ mostRecentAttemptWithCompletedCompleted,
72
+ };
73
+ };
74
+ /*
75
+ * loads all statements (for this actor) that have the given activityIRI as the object.id or the context.contextActivities.parent.id
76
+ *
77
+ * note: if you filter on attempt you're only gonna get the `Attempted` statements from the child activities, subsequent child activity
78
+ * statements would then have to be fetched using `loadStatementsForActivity(gateway, childActivityIRI, childAttemptStatementID)`. this
79
+ * is because child activities could have multiple attempts under one attempt on the parent activity.
80
+ */
81
+ export const loadStatementsForActivityAndFirstChildren = (gateway, activityIRI, attempt) => {
82
+ return gateway.getAllXapiStatements({
83
+ activity: activityIRI,
84
+ related_activities: true,
85
+ ...(attempt ? { registration: attempt } : {})
86
+ });
87
+ };
88
+ /*
89
+ * loads all statements (for this actor) that have the given parent attempt (registration)
90
+ */
91
+ export const loadStatementsForAttempt = (gateway, attempt) => {
92
+ return gateway.getAllXapiStatements({
93
+ registration: attempt
94
+ });
95
+ };
96
+ /*
97
+ * loads all statements (for this actor) that have the given activityIRI as the object.id
98
+ */
99
+ export const loadStatementsForActivity = (gateway, activityIRI, attempt) => {
100
+ return gateway.getAllXapiStatements({
101
+ activity: activityIRI,
102
+ ...(attempt ? { registration: attempt } : {})
103
+ });
104
+ };
105
+ export const loadActivityAttemptInfo = async (gateway, activityIRI, options) => {
106
+ return resolveActivityAttemptInfo(await loadStatementsForActivity(gateway, activityIRI, options === null || options === void 0 ? void 0 : options.parentActivityAttempt), activityIRI, options);
107
+ };
108
+ export const createStatement = (verb, activity, attempt, parentActivityIRI) => {
109
+ return {
110
+ context: {
111
+ ...(parentActivityIRI ? {
112
+ contextActivities: {
113
+ parent: [
114
+ {
115
+ id: parentActivityIRI,
116
+ objectType: 'Activity',
117
+ },
118
+ ],
119
+ },
120
+ } : {}),
121
+ registration: attempt,
122
+ },
123
+ object: {
124
+ definition: {
125
+ extensions: {
126
+ ...activity.extensions
127
+ },
128
+ name: {
129
+ 'en-US': activity.name,
130
+ },
131
+ type: activity.type,
132
+ },
133
+ id: activity.iri,
134
+ objectType: 'Activity'
135
+ },
136
+ verb,
137
+ };
138
+ };
139
+ /*
140
+ * activity:
141
+ * - iri: the IRI formatted id for this activity
142
+ * - type: the IRI formatted activity type (eg: http://id.tincanapi.com/activitytype/school-assignment)
143
+ * - name: the plaintext name of the activity, for reporting
144
+ *
145
+ * parentActivity:
146
+ * - iri: the IRI formatted id for the parent activity
147
+ * - attempt: the statement id for the parent attempt (the object of which should be the parentActivity.iri)
148
+ */
149
+ export const createAttemptStatement = (activity, parentActivity) => {
150
+ return {
151
+ ...((parentActivity === null || parentActivity === void 0 ? void 0 : parentActivity.iri) || (parentActivity === null || parentActivity === void 0 ? void 0 : parentActivity.attempt) ? {
152
+ context: {
153
+ ...(parentActivity.iri ? {
154
+ contextActivities: {
155
+ parent: [
156
+ {
157
+ id: parentActivity.iri,
158
+ objectType: 'Activity',
159
+ },
160
+ ],
161
+ },
162
+ } : {}),
163
+ ...(parentActivity.attempt ? {
164
+ registration: parentActivity.attempt,
165
+ } : {})
166
+ },
167
+ } : {}),
168
+ object: {
169
+ definition: {
170
+ extensions: {
171
+ ...activity.extensions
172
+ },
173
+ name: {
174
+ 'en-US': activity.name,
175
+ },
176
+ type: activity.type,
177
+ },
178
+ id: activity.iri,
179
+ objectType: 'Activity'
180
+ },
181
+ verb: {
182
+ display: { 'en-US': 'Attempted' },
183
+ id: Verb.Attempted,
184
+ },
185
+ };
186
+ };
187
+ /* resolves with the statement id */
188
+ export const putAttemptStatement = async (gateway, activity, parentActivity) => {
189
+ return await gateway.putXapiStatements([createAttemptStatement(activity, parentActivity)])
190
+ .then(statements => statements[0]);
191
+ };
192
+ /*
193
+ * creates a statement under the given attempt.
194
+ *
195
+ * `result` optional context for the attempt result (score, selected answer, etc)
196
+ */
197
+ export const createAttemptActivityStatement = (attemptStatement, verb, result) => {
198
+ var _a;
199
+ return {
200
+ context: {
201
+ ...(((_a = attemptStatement.context) === null || _a === void 0 ? void 0 : _a.contextActivities) ? {
202
+ contextActivities: attemptStatement.context.contextActivities,
203
+ } : {}),
204
+ registration: attemptStatement.id,
205
+ },
206
+ object: attemptStatement.object,
207
+ verb: verb,
208
+ ...(result ? { result } : {}),
209
+ };
210
+ };
211
+ export const putAttemptActivityStatement = async (gateway, attemptStatement, verb, result) => {
212
+ return await gateway.putXapiStatements([createAttemptActivityStatement(attemptStatement, verb, result)])
213
+ .then(statements => statements[0]);
214
+ };
215
+ /*
216
+ * creates a statement that completes the given attempt.
217
+ *
218
+ * `result` optional context for the attempt result (score, selected answer, etc)
219
+ */
220
+ export const createCompletedStatement = (attemptStatement, result) => {
221
+ var _a, _b;
222
+ return {
223
+ context: {
224
+ ...(((_a = attemptStatement.context) === null || _a === void 0 ? void 0 : _a.contextActivities) ? {
225
+ contextActivities: attemptStatement.context.contextActivities,
226
+ } : {}),
227
+ ...(((_b = attemptStatement.context) === null || _b === void 0 ? void 0 : _b.registration) ? {
228
+ registration: attemptStatement.context.registration,
229
+ } : {}),
230
+ statement: {
231
+ objectType: 'StatementRef',
232
+ id: attemptStatement.id,
233
+ }
234
+ },
235
+ object: attemptStatement.object,
236
+ verb: {
237
+ display: { 'en-US': 'Completed' },
238
+ id: Verb.Completed,
239
+ },
240
+ result: {
241
+ duration: formatISODuration(intervalToDuration({
242
+ start: parseISO(attemptStatement.timestamp), end: new Date()
243
+ })),
244
+ ...result,
245
+ }
246
+ };
247
+ };
248
+ export const putCompletedStatement = async (gateway, attemptStatement, result) => {
249
+ return await gateway.putXapiStatements([createCompletedStatement(attemptStatement, result)])
250
+ .then(statements => statements[0]);
251
+ };
@@ -0,0 +1,15 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { AuthProvider } from '../authProvider';
3
+ import { LrsGateway } from '.';
4
+ declare type Config = {
5
+ name: string;
6
+ };
7
+ interface Initializer<C> {
8
+ dataDir: string;
9
+ fs?: Pick<typeof import('fs'), 'readFile' | 'writeFile'>;
10
+ configSpace?: C;
11
+ }
12
+ export declare const fileSystemLrsGateway: <C extends string = "fileSystem">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
13
+ name: import("../../config").ConfigValueProvider<string>;
14
+ }; }) => (authProvider: AuthProvider) => LrsGateway;
15
+ export {};
@@ -0,0 +1,96 @@
1
+ import * as fsModule from 'fs';
2
+ import path from 'path';
3
+ import formatISO from 'date-fns/formatISO';
4
+ import { v4 as uuid } from 'uuid';
5
+ import { assertDefined } from '../../assertions';
6
+ import { resolveConfigValue } from '../../config';
7
+ import { UnauthorizedError } from '../../errors';
8
+ import { ifDefined } from '../../guards';
9
+ const pageSize = 5;
10
+ export const fileSystemLrsGateway = (initializer) => (configProvider) => (authProvider) => {
11
+ const name = resolveConfigValue(configProvider[initializer.configSpace || 'fileSystem'].name);
12
+ const filePath = name.then((fileName) => path.join(initializer.dataDir, fileName));
13
+ const { readFile, writeFile } = ifDefined(initializer.fs, fsModule);
14
+ let data;
15
+ const load = filePath.then(path => new Promise(resolve => {
16
+ readFile(path, (err, readData) => {
17
+ if (err) {
18
+ console.error(err);
19
+ }
20
+ else {
21
+ try {
22
+ data = JSON.parse(readData.toString());
23
+ if (typeof data !== 'object' || !(data instanceof Array)) {
24
+ data = undefined;
25
+ }
26
+ }
27
+ catch (e) {
28
+ console.error(e);
29
+ }
30
+ }
31
+ resolve();
32
+ });
33
+ }));
34
+ let previousSave;
35
+ const putXapiStatements = async (statements) => {
36
+ const user = assertDefined(await authProvider.getUser(), new UnauthorizedError);
37
+ const statementsWithDefaults = statements.map(statement => ({
38
+ ...statement,
39
+ id: uuid(),
40
+ actor: {
41
+ account: {
42
+ homePage: 'https://openstax.org',
43
+ name: user.uuid,
44
+ },
45
+ objectType: 'Agent',
46
+ },
47
+ timestamp: formatISO(new Date()),
48
+ }));
49
+ await load;
50
+ await previousSave;
51
+ const path = await filePath;
52
+ const save = previousSave = new Promise(resolve => {
53
+ data = data || [];
54
+ data.push(...statementsWithDefaults.map(statement => ({ ...statement, stored: statement.timestamp })));
55
+ writeFile(path, JSON.stringify(data, null, 2), () => resolve());
56
+ });
57
+ await save;
58
+ return statementsWithDefaults;
59
+ };
60
+ const getAllXapiStatements = async ({ user, anyUser, ...options }) => {
61
+ const authUser = await authProvider.getUser();
62
+ await load;
63
+ return (data || []).filter(statement => {
64
+ var _a, _b, _c, _d;
65
+ return (anyUser === true || statement.actor.account.name === (user || assertDefined(authUser, new UnauthorizedError()).uuid))
66
+ && (!options.verb || statement.verb.id === options.verb)
67
+ && (!options.registration || ((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === options.registration)
68
+ && (!options.activity || (options.related_activities
69
+ ? ((statement.object.id === options.activity && statement.object.objectType === 'Activity')
70
+ || (!!((_d = (_c = (_b = statement.context) === null || _b === void 0 ? void 0 : _b.contextActivities) === null || _c === void 0 ? void 0 : _c.parent) === null || _d === void 0 ? void 0 : _d.find(parent => parent.id === options.activity && parent.objectType === 'Activity'))))
71
+ : (statement.object.id === options.activity && statement.object.objectType === 'Activity')));
72
+ });
73
+ };
74
+ const getMoreXapiStatements = async (more) => {
75
+ const { args, offset } = JSON.parse(more);
76
+ const allResults = await getAllXapiStatements(...args);
77
+ const end = offset + pageSize;
78
+ return {
79
+ more: allResults.length > end ? JSON.stringify({ args, offset: end }) : '',
80
+ statements: allResults.slice(offset, end)
81
+ };
82
+ };
83
+ const getXapiStatements = async (...args) => {
84
+ const allResults = await getAllXapiStatements(...args);
85
+ return {
86
+ more: allResults.length > pageSize ? JSON.stringify({ args, offset: pageSize }) : '',
87
+ statements: allResults.slice(0, pageSize)
88
+ };
89
+ };
90
+ return {
91
+ putXapiStatements,
92
+ getAllXapiStatements,
93
+ getXapiStatements,
94
+ getMoreXapiStatements,
95
+ };
96
+ };
@@ -0,0 +1,110 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { GenericFetch } from '../../fetch';
3
+ import { WithRequired } from '../../types';
4
+ import { AuthProvider } from '../authProvider';
5
+ declare type Config = {
6
+ lrsHost: string;
7
+ lrsAuthorization: string;
8
+ };
9
+ interface Initializer<C> {
10
+ configSpace?: C;
11
+ fetch: GenericFetch;
12
+ }
13
+ export interface XapiStatement {
14
+ actor: {
15
+ account: {
16
+ homePage: 'https://openstax.org';
17
+ name: string;
18
+ };
19
+ objectType: 'Agent';
20
+ };
21
+ id: string;
22
+ context?: {
23
+ contextActivities?: {
24
+ parent?: [
25
+ {
26
+ id: string;
27
+ objectType: 'Activity';
28
+ }
29
+ ];
30
+ };
31
+ statement?: {
32
+ objectType: 'StatementRef';
33
+ id: string;
34
+ };
35
+ registration?: string;
36
+ platform?: string;
37
+ };
38
+ object: {
39
+ definition?: {
40
+ extensions?: {
41
+ [key: string]: string;
42
+ };
43
+ name: {
44
+ [key: string]: string;
45
+ };
46
+ type: string;
47
+ };
48
+ id: string;
49
+ objectType: 'Activity';
50
+ };
51
+ result?: {
52
+ score?: {
53
+ scaled?: number;
54
+ raw?: number;
55
+ min?: number;
56
+ max?: number;
57
+ };
58
+ success?: boolean;
59
+ completion?: boolean;
60
+ response?: string;
61
+ duration?: string;
62
+ extensions?: {
63
+ [key: string]: string;
64
+ };
65
+ };
66
+ verb: {
67
+ display?: {
68
+ [key: string]: string;
69
+ };
70
+ id: string;
71
+ };
72
+ timestamp: string;
73
+ stored?: string;
74
+ }
75
+ export declare type SavedXapiStatement = WithRequired<XapiStatement, 'stored'>;
76
+ export declare type EagerXapiStatement = Omit<XapiStatement, 'stored'>;
77
+ export declare type LrsGateway = ReturnType<ReturnType<ReturnType<typeof lrsGateway>>>;
78
+ export declare type LrsProvider = ReturnType<ReturnType<typeof lrsGateway>>;
79
+ export declare const lrsGateway: <C extends string = "lrs">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
80
+ lrsHost: import("../../config").ConfigValueProvider<string>;
81
+ lrsAuthorization: import("../../config").ConfigValueProvider<string>;
82
+ }; }) => (authProvider: AuthProvider) => {
83
+ putXapiStatements: (statements: Array<Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'> & {
84
+ id?: string;
85
+ }>) => Promise<EagerXapiStatement[]>;
86
+ getXapiStatements: ({ user, anyUser, ...options }: {
87
+ verb?: string | undefined;
88
+ activity?: string | undefined;
89
+ registration?: string | undefined;
90
+ related_activities?: boolean | undefined;
91
+ user?: string | undefined;
92
+ anyUser?: boolean | undefined;
93
+ }) => Promise<{
94
+ more: string;
95
+ statements: XapiStatement[];
96
+ }>;
97
+ getMoreXapiStatements: (more: string) => Promise<{
98
+ more: string;
99
+ statements: XapiStatement[];
100
+ }>;
101
+ getAllXapiStatements: (args_0: {
102
+ verb?: string | undefined;
103
+ activity?: string | undefined;
104
+ registration?: string | undefined;
105
+ related_activities?: boolean | undefined;
106
+ user?: string | undefined;
107
+ anyUser?: boolean | undefined;
108
+ }) => Promise<XapiStatement[]>;
109
+ };
110
+ export {};
@@ -0,0 +1,87 @@
1
+ import formatISO from 'date-fns/formatISO';
2
+ import * as queryString from 'query-string';
3
+ import { once } from '../..';
4
+ import { assertDefined } from '../../assertions';
5
+ import { resolveConfigValue } from '../../config';
6
+ import { UnauthorizedError } from '../../errors';
7
+ import { ifDefined } from '../../guards';
8
+ import { METHOD } from '../../routing';
9
+ export const lrsGateway = (initializer) => (configProvider) => {
10
+ const config = configProvider[ifDefined(initializer.configSpace, 'lrs')];
11
+ const lrsHost = once(() => resolveConfigValue(config.lrsHost));
12
+ const lrsAuthorization = once(() => resolveConfigValue(config.lrsAuthorization));
13
+ return (authProvider) => {
14
+ const putXapiStatements = async (statements) => {
15
+ const user = assertDefined(await authProvider.getUser(), new UnauthorizedError);
16
+ const statementsWithDefaults = statements.map(statement => ({
17
+ ...statement,
18
+ actor: {
19
+ account: {
20
+ homePage: 'https://openstax.org',
21
+ name: user.uuid,
22
+ },
23
+ objectType: 'Agent',
24
+ },
25
+ timestamp: formatISO(new Date())
26
+ }));
27
+ return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements', {
28
+ body: JSON.stringify(statementsWithDefaults),
29
+ headers: {
30
+ Authorization: await lrsAuthorization(),
31
+ 'Content-Type': 'application/json',
32
+ 'X-Experience-API-Version': '1.0.0',
33
+ },
34
+ method: METHOD.POST,
35
+ })
36
+ .then(response => response.json())
37
+ .then(ids => ids.map((id, index) => ({ id, ...statementsWithDefaults[index] })));
38
+ };
39
+ const getMoreXapiStatements = async (more) => {
40
+ return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + more, {
41
+ headers: {
42
+ Authorization: await lrsAuthorization(),
43
+ 'X-Experience-API-Version': '1.0.0',
44
+ },
45
+ })
46
+ .then(response => response.json())
47
+ .then(json => json);
48
+ };
49
+ const getXapiStatements = async ({ user, anyUser, ...options }) => {
50
+ return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
51
+ ...options,
52
+ ...(anyUser === true ? {} : {
53
+ agent: JSON.stringify({
54
+ account: {
55
+ homePage: 'https://openstax.org',
56
+ name: user || assertDefined(await authProvider.getUser(), new UnauthorizedError()).uuid,
57
+ },
58
+ objectType: 'Agent',
59
+ }),
60
+ })
61
+ }), {
62
+ headers: {
63
+ Authorization: await lrsAuthorization(),
64
+ 'X-Experience-API-Version': '1.0.0',
65
+ },
66
+ })
67
+ .then(response => response.json())
68
+ .then(json => json);
69
+ };
70
+ const getAllXapiStatements = (...args) => {
71
+ const loadRemaining = async (result) => {
72
+ if (!result.more) {
73
+ return result.statements;
74
+ }
75
+ const { more, statements } = await getMoreXapiStatements(result.more);
76
+ return loadRemaining({ more, statements: [...result.statements, ...statements] });
77
+ };
78
+ return getXapiStatements(...args).then(loadRemaining);
79
+ };
80
+ return {
81
+ putXapiStatements,
82
+ getXapiStatements,
83
+ getMoreXapiStatements,
84
+ getAllXapiStatements,
85
+ };
86
+ };
87
+ };
@@ -0,0 +1,19 @@
1
+ declare type Filter = {
2
+ key: string;
3
+ value: string | string[];
4
+ };
5
+ declare type Field = {
6
+ key: string;
7
+ type?: 'text';
8
+ weight: number;
9
+ } | {
10
+ key: string;
11
+ type: 'keyword';
12
+ };
13
+ export interface SearchOptions {
14
+ page?: number;
15
+ query: string | undefined;
16
+ fields: Field[];
17
+ filter?: Filter[];
18
+ }
19
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { SearchOptions } from '.';
2
+ export declare const memorySearchTheBadWay: <T>({ loadAllDocumentsTheBadWay }: {
3
+ loadAllDocumentsTheBadWay: () => Promise<T[]>;
4
+ }) => {
5
+ search: (options: SearchOptions) => Promise<{
6
+ items: T[];
7
+ pageSize: number;
8
+ currentPage: number;
9
+ totalItems: number;
10
+ totalPages: number;
11
+ }>;
12
+ };
@@ -0,0 +1,53 @@
1
+ import { coerceArray } from '../..';
2
+ import { isDefined } from '../../guards';
3
+ const MIN_MATCH = 0.5;
4
+ const MAX_RESULTS = 10;
5
+ const resolveField = (document, field) => field.key.toString().split('.').reduce((result, key) => result[key], document);
6
+ export const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
7
+ return {
8
+ search: async (options) => {
9
+ const results = (await loadAllDocumentsTheBadWay())
10
+ .map(document => {
11
+ let weight = 0;
12
+ if (options.query !== undefined) {
13
+ for (const field of options.fields) {
14
+ if (field.type !== undefined && field.type !== 'text') {
15
+ continue;
16
+ }
17
+ const value = resolveField(document, field);
18
+ if (value === undefined || value === null) {
19
+ continue;
20
+ }
21
+ if (typeof value === 'string') {
22
+ if (value.toUpperCase().indexOf(options.query.toUpperCase()) !== -1) {
23
+ weight += (1 * field.weight);
24
+ }
25
+ }
26
+ else {
27
+ throw new Error(`invalid value type when searching in field ${field.key.toString()}`);
28
+ }
29
+ }
30
+ }
31
+ for (const field of options.filter || []) {
32
+ const value = resolveField(document, field);
33
+ if (!coerceArray(field.value).some(v => coerceArray(value).includes(v))) {
34
+ return undefined;
35
+ }
36
+ }
37
+ return { document, weight };
38
+ })
39
+ .filter(isDefined)
40
+ .filter(r => !options.query || r.weight >= MIN_MATCH);
41
+ results.sort((a, b) => b.weight - a.weight);
42
+ const page = options.page || 1;
43
+ const offset = (page - 1) * MAX_RESULTS;
44
+ return {
45
+ items: results.slice(offset, offset + MAX_RESULTS).map(r => r.document),
46
+ pageSize: MAX_RESULTS,
47
+ currentPage: page,
48
+ totalItems: results.length,
49
+ totalPages: Math.ceil(results.length / MAX_RESULTS)
50
+ };
51
+ }
52
+ };
53
+ };
@@ -0,0 +1,23 @@
1
+ import { ConfigProviderForConfig } from '../../config';
2
+ import { Track } from '../../profile';
3
+ import { TDocument, VersionedDocumentAuthor } from '.';
4
+ declare type DynamoConfig = {
5
+ tableName: string;
6
+ };
7
+ interface Initializer<C> {
8
+ configSpace?: C;
9
+ }
10
+ export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
11
+ tableName: import("../../config").ConfigValueProvider<string>;
12
+ }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({ profile }: {
13
+ profile: Track;
14
+ }, hashKey: K, getAuthor: A) => {
15
+ loadAllDocumentsTheBadWay: () => Promise<T[]>;
16
+ getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
17
+ items: T[];
18
+ nextPageToken: number | undefined;
19
+ } | undefined>;
20
+ getItem: (id: T[K], timestamp?: number | undefined) => Promise<T | undefined>;
21
+ putItem: (item: Omit<T, "timestamp" | "author">, ...authorArgs: A extends Function ? Parameters<A> : [VersionedDocumentAuthor]) => Promise<T>;
22
+ };
23
+ export {};