@openstax/ts-utils 1.21.11 → 1.23.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/assertions/index.d.ts +85 -0
- package/dist/cjs/assertions/index.js +157 -0
- package/dist/cjs/aws/ssmService.d.ts +5 -0
- package/dist/cjs/aws/ssmService.js +9 -0
- package/dist/cjs/config/awsParameterConfig.d.ts +10 -0
- package/dist/cjs/config/awsParameterConfig.js +26 -0
- package/dist/cjs/config/envConfig.d.ts +24 -0
- package/dist/cjs/config/envConfig.js +57 -0
- package/dist/cjs/config/index.d.ts +48 -0
- package/dist/cjs/config/index.js +35 -0
- package/dist/cjs/config/lambdaParameterConfig.d.ts +12 -0
- package/dist/cjs/config/lambdaParameterConfig.js +45 -0
- package/dist/cjs/config/replaceConfig.d.ts +14 -0
- package/dist/cjs/config/replaceConfig.js +22 -0
- package/dist/cjs/config/resolveConfigValue.d.ts +5 -0
- package/dist/cjs/config/resolveConfigValue.js +12 -0
- package/dist/cjs/errors/index.d.ts +77 -0
- package/dist/cjs/errors/index.js +109 -0
- package/dist/cjs/fetch/fetchStatusRetry.d.ts +7 -0
- package/dist/cjs/fetch/fetchStatusRetry.js +16 -0
- package/dist/cjs/fetch/index.d.ts +64 -0
- package/dist/cjs/fetch/index.js +55 -0
- package/dist/cjs/guards/index.d.ts +30 -0
- package/dist/cjs/guards/index.js +35 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/middleware/apiErrorHandler.d.ts +24 -0
- package/dist/cjs/middleware/apiErrorHandler.js +41 -0
- package/dist/cjs/middleware/apiSlowResponseMiddleware.d.ts +23 -0
- package/dist/cjs/middleware/apiSlowResponseMiddleware.js +54 -0
- package/dist/cjs/middleware/index.d.ts +47 -0
- package/dist/cjs/middleware/index.js +48 -0
- package/dist/cjs/middleware/lambdaCorsResponseMiddleware.d.ts +20 -0
- package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +42 -0
- package/dist/cjs/middleware/throwNotFoundMiddleware.d.ts +4 -0
- package/dist/cjs/middleware/throwNotFoundMiddleware.js +14 -0
- package/dist/cjs/misc/hashValue.d.ts +10 -0
- package/dist/cjs/misc/hashValue.js +17 -0
- package/dist/cjs/misc/helpers.d.ts +124 -0
- package/dist/cjs/misc/helpers.js +214 -0
- package/dist/cjs/misc/merge.d.ts +21 -0
- package/dist/cjs/misc/merge.js +45 -0
- package/dist/cjs/misc/partitionSequence.d.ts +35 -0
- package/dist/cjs/misc/partitionSequence.js +55 -0
- package/dist/cjs/pagination/index.d.ts +91 -0
- package/dist/cjs/pagination/index.js +83 -0
- package/dist/cjs/routing/helpers.d.ts +57 -0
- package/dist/cjs/routing/helpers.js +90 -0
- package/dist/cjs/routing/index.d.ts +272 -0
- package/dist/cjs/routing/index.js +270 -0
- package/dist/cjs/routing/validators/zod.d.ts +4 -0
- package/dist/cjs/routing/validators/zod.js +12 -0
- package/dist/cjs/services/accountsGateway/index.d.ts +85 -0
- package/dist/cjs/services/accountsGateway/index.js +118 -0
- package/dist/cjs/services/apiGateway/index.d.ts +63 -0
- package/dist/cjs/services/apiGateway/index.js +108 -0
- package/dist/cjs/services/authProvider/browser.d.ts +74 -0
- package/dist/cjs/services/authProvider/browser.js +154 -0
- package/dist/cjs/services/authProvider/decryption.d.ts +19 -0
- package/dist/cjs/services/authProvider/decryption.js +61 -0
- package/dist/cjs/services/authProvider/index.d.ts +61 -0
- package/dist/cjs/services/authProvider/index.js +26 -0
- package/dist/cjs/services/authProvider/subrequest.d.ts +16 -0
- package/dist/cjs/services/authProvider/subrequest.js +50 -0
- package/dist/cjs/services/authProvider/utils/decryptAndVerify.d.ts +29 -0
- package/dist/cjs/services/authProvider/utils/decryptAndVerify.js +91 -0
- package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.d.ts +26 -0
- package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.js +47 -0
- package/dist/cjs/services/authProvider/utils/userRoleValidator.d.ts +13 -0
- package/dist/cjs/services/authProvider/utils/userRoleValidator.js +37 -0
- package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +10 -0
- package/dist/cjs/services/documentStore/dynamoEncoding.js +52 -0
- package/dist/cjs/services/documentStore/index.d.ts +14 -0
- package/dist/cjs/services/documentStore/index.js +2 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +16 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.js +122 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +18 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.js +121 -0
- package/dist/cjs/services/documentStore/unversioned/index.d.ts +2 -0
- package/dist/cjs/services/documentStore/unversioned/index.js +2 -0
- package/dist/cjs/services/documentStore/versioned/dynamodb.d.ts +22 -0
- package/dist/cjs/services/documentStore/versioned/dynamodb.js +135 -0
- package/dist/cjs/services/documentStore/versioned/file-system.d.ts +24 -0
- package/dist/cjs/services/documentStore/versioned/file-system.js +62 -0
- package/dist/cjs/services/documentStore/versioned/index.d.ts +17 -0
- package/dist/cjs/services/documentStore/versioned/index.js +2 -0
- package/dist/cjs/services/exercisesGateway/index.d.ts +71 -0
- package/dist/cjs/services/exercisesGateway/index.js +97 -0
- package/dist/cjs/services/fileServer/index.d.ts +17 -0
- package/dist/cjs/services/fileServer/index.js +19 -0
- package/dist/cjs/services/fileServer/localFileServer.d.ts +13 -0
- package/dist/cjs/services/fileServer/localFileServer.js +23 -0
- package/dist/cjs/services/fileServer/s3FileServer.d.ts +16 -0
- package/dist/cjs/services/fileServer/s3FileServer.js +25 -0
- package/dist/cjs/services/launchParams/index.d.ts +2 -0
- package/dist/cjs/services/launchParams/index.js +7 -0
- package/dist/cjs/services/launchParams/signer.d.ts +27 -0
- package/dist/cjs/services/launchParams/signer.js +58 -0
- package/dist/cjs/services/launchParams/verifier.d.ts +22 -0
- package/dist/cjs/services/launchParams/verifier.js +94 -0
- package/dist/cjs/services/logger/console.d.ts +4 -0
- package/dist/cjs/services/logger/console.js +12 -0
- package/dist/cjs/services/logger/index.d.ts +39 -0
- package/dist/cjs/services/logger/index.js +31 -0
- package/dist/cjs/services/lrsGateway/addStatementDefaultFields.d.ts +5 -0
- package/dist/cjs/services/lrsGateway/addStatementDefaultFields.js +21 -0
- package/dist/cjs/services/lrsGateway/attempt-utils.d.ts +70 -0
- package/dist/cjs/services/lrsGateway/attempt-utils.js +258 -0
- package/dist/cjs/services/lrsGateway/file-system.d.ts +17 -0
- package/dist/cjs/services/lrsGateway/file-system.js +140 -0
- package/dist/cjs/services/lrsGateway/index.d.ts +125 -0
- package/dist/cjs/services/lrsGateway/index.js +138 -0
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +61 -0
- package/dist/cjs/services/lrsGateway/xapiUtils.js +94 -0
- package/dist/cjs/services/postgresConnection/index.d.ts +35 -0
- package/dist/cjs/services/postgresConnection/index.js +63 -0
- package/dist/cjs/services/searchProvider/index.d.ts +31 -0
- package/dist/cjs/services/searchProvider/index.js +2 -0
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +14 -0
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +89 -0
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -0
- package/dist/cjs/types.d.ts +31 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/assertions/index.d.ts +85 -0
- package/dist/esm/assertions/index.js +146 -0
- package/dist/esm/aws/ssmService.d.ts +5 -0
- package/dist/esm/aws/ssmService.js +6 -0
- package/dist/esm/config/awsParameterConfig.d.ts +10 -0
- package/dist/esm/config/awsParameterConfig.js +22 -0
- package/dist/esm/config/envConfig.d.ts +24 -0
- package/dist/esm/config/envConfig.js +53 -0
- package/dist/esm/config/index.d.ts +48 -0
- package/dist/esm/config/index.js +17 -0
- package/dist/esm/config/lambdaParameterConfig.d.ts +12 -0
- package/dist/esm/config/lambdaParameterConfig.js +38 -0
- package/dist/esm/config/replaceConfig.d.ts +14 -0
- package/dist/esm/config/replaceConfig.js +18 -0
- package/dist/esm/config/resolveConfigValue.d.ts +5 -0
- package/dist/esm/config/resolveConfigValue.js +8 -0
- package/dist/esm/errors/index.d.ts +77 -0
- package/dist/esm/errors/index.js +99 -0
- package/dist/esm/fetch/fetchStatusRetry.d.ts +7 -0
- package/dist/esm/fetch/fetchStatusRetry.js +12 -0
- package/dist/esm/fetch/index.d.ts +64 -0
- package/dist/esm/fetch/index.js +46 -0
- package/dist/esm/guards/index.d.ts +30 -0
- package/dist/esm/guards/index.js +28 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/middleware/apiErrorHandler.d.ts +24 -0
- package/dist/esm/middleware/apiErrorHandler.js +37 -0
- package/dist/esm/middleware/apiSlowResponseMiddleware.d.ts +23 -0
- package/dist/esm/middleware/apiSlowResponseMiddleware.js +50 -0
- package/dist/esm/middleware/index.d.ts +47 -0
- package/dist/esm/middleware/index.js +44 -0
- package/dist/esm/middleware/lambdaCorsResponseMiddleware.d.ts +20 -0
- package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +38 -0
- package/dist/esm/middleware/throwNotFoundMiddleware.d.ts +4 -0
- package/dist/esm/middleware/throwNotFoundMiddleware.js +10 -0
- package/dist/esm/misc/hashValue.d.ts +10 -0
- package/dist/esm/misc/hashValue.js +13 -0
- package/dist/esm/misc/helpers.d.ts +124 -0
- package/dist/esm/misc/helpers.js +199 -0
- package/dist/esm/misc/merge.d.ts +21 -0
- package/dist/esm/misc/merge.js +40 -0
- package/dist/esm/misc/partitionSequence.d.ts +35 -0
- package/dist/esm/misc/partitionSequence.js +48 -0
- package/dist/esm/pagination/index.d.ts +91 -0
- package/dist/esm/pagination/index.js +77 -0
- package/dist/esm/routing/helpers.d.ts +57 -0
- package/dist/esm/routing/helpers.js +83 -0
- package/dist/esm/routing/index.d.ts +272 -0
- package/dist/esm/routing/index.js +232 -0
- package/dist/esm/routing/validators/zod.d.ts +4 -0
- package/dist/esm/routing/validators/zod.js +8 -0
- package/dist/esm/services/accountsGateway/index.d.ts +85 -0
- package/dist/esm/services/accountsGateway/index.js +111 -0
- package/dist/esm/services/apiGateway/index.d.ts +63 -0
- package/dist/esm/services/apiGateway/index.js +77 -0
- package/dist/esm/services/authProvider/browser.d.ts +74 -0
- package/dist/esm/services/authProvider/browser.js +150 -0
- package/dist/esm/services/authProvider/decryption.d.ts +19 -0
- package/dist/esm/services/authProvider/decryption.js +57 -0
- package/dist/esm/services/authProvider/index.d.ts +61 -0
- package/dist/esm/services/authProvider/index.js +18 -0
- package/dist/esm/services/authProvider/subrequest.d.ts +16 -0
- package/dist/esm/services/authProvider/subrequest.js +43 -0
- package/dist/esm/services/authProvider/utils/decryptAndVerify.d.ts +29 -0
- package/dist/esm/services/authProvider/utils/decryptAndVerify.js +85 -0
- package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +26 -0
- package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +40 -0
- package/dist/esm/services/authProvider/utils/userRoleValidator.d.ts +13 -0
- package/dist/esm/services/authProvider/utils/userRoleValidator.js +33 -0
- package/dist/esm/services/documentStore/dynamoEncoding.d.ts +10 -0
- package/dist/esm/services/documentStore/dynamoEncoding.js +45 -0
- package/dist/esm/services/documentStore/index.d.ts +14 -0
- package/dist/esm/services/documentStore/index.js +1 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +16 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.js +118 -0
- package/dist/esm/services/documentStore/unversioned/file-system.d.ts +18 -0
- package/dist/esm/services/documentStore/unversioned/file-system.js +91 -0
- package/dist/esm/services/documentStore/unversioned/index.d.ts +2 -0
- package/dist/esm/services/documentStore/unversioned/index.js +1 -0
- package/dist/esm/services/documentStore/versioned/dynamodb.d.ts +22 -0
- package/dist/esm/services/documentStore/versioned/dynamodb.js +131 -0
- package/dist/esm/services/documentStore/versioned/file-system.d.ts +24 -0
- package/dist/esm/services/documentStore/versioned/file-system.js +58 -0
- package/dist/esm/services/documentStore/versioned/index.d.ts +17 -0
- package/dist/esm/services/documentStore/versioned/index.js +1 -0
- package/dist/esm/services/exercisesGateway/index.d.ts +71 -0
- package/dist/esm/services/exercisesGateway/index.js +70 -0
- package/dist/esm/services/fileServer/index.d.ts +17 -0
- package/dist/esm/services/fileServer/index.js +13 -0
- package/dist/esm/services/fileServer/localFileServer.d.ts +13 -0
- package/dist/esm/services/fileServer/localFileServer.js +16 -0
- package/dist/esm/services/fileServer/s3FileServer.d.ts +16 -0
- package/dist/esm/services/fileServer/s3FileServer.js +21 -0
- package/dist/esm/services/launchParams/index.d.ts +2 -0
- package/dist/esm/services/launchParams/index.js +2 -0
- package/dist/esm/services/launchParams/signer.d.ts +27 -0
- package/dist/esm/services/launchParams/signer.js +51 -0
- package/dist/esm/services/launchParams/verifier.d.ts +22 -0
- package/dist/esm/services/launchParams/verifier.js +67 -0
- package/dist/esm/services/logger/console.d.ts +4 -0
- package/dist/esm/services/logger/console.js +8 -0
- package/dist/esm/services/logger/index.d.ts +39 -0
- package/dist/esm/services/logger/index.js +27 -0
- package/dist/esm/services/lrsGateway/addStatementDefaultFields.d.ts +5 -0
- package/dist/esm/services/lrsGateway/addStatementDefaultFields.js +14 -0
- package/dist/esm/services/lrsGateway/attempt-utils.d.ts +70 -0
- package/dist/esm/services/lrsGateway/attempt-utils.js +236 -0
- package/dist/esm/services/lrsGateway/file-system.d.ts +17 -0
- package/dist/esm/services/lrsGateway/file-system.js +110 -0
- package/dist/esm/services/lrsGateway/index.d.ts +125 -0
- package/dist/esm/services/lrsGateway/index.js +111 -0
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +61 -0
- package/dist/esm/services/lrsGateway/xapiUtils.js +84 -0
- package/dist/esm/services/postgresConnection/index.d.ts +35 -0
- package/dist/esm/services/postgresConnection/index.js +56 -0
- package/dist/esm/services/searchProvider/index.d.ts +31 -0
- package/dist/esm/services/searchProvider/index.js +1 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +14 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +85 -0
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -0
- package/dist/esm/types.d.ts +31 -0
- package/dist/esm/types.js +1 -0
- package/package.json +16 -16
- package/script/bin/deploy.bash +8 -0
- package/script/bin/get-env-param.bash +3 -3
- package/script/bin/init-params-script.bash +10 -1
- package/script/bin/upload-params.bash +3 -3
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
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, fetchUntil, ...options }) => {
|
|
61
|
+
const authUser = await authProvider.getUser();
|
|
62
|
+
await load;
|
|
63
|
+
let filteredData = (data || []).filter(statement => {
|
|
64
|
+
var _a, _b, _c, _d;
|
|
65
|
+
const statementDate = new Date(statement.timestamp);
|
|
66
|
+
const sinceDate = options.since ? new Date(options.since) : null;
|
|
67
|
+
const untilDate = options.until ? new Date(options.until) : null;
|
|
68
|
+
return (anyUser === true || statement.actor.account.name === (user || assertDefined(authUser, new UnauthorizedError()).uuid))
|
|
69
|
+
&& (!options.verb || statement.verb.id === options.verb)
|
|
70
|
+
&& (!options.registration || ((_a = statement.context) === null || _a === void 0 ? void 0 : _a.registration) === options.registration)
|
|
71
|
+
&& (!options.activity || (options.related_activities
|
|
72
|
+
? ((statement.object.id === options.activity && statement.object.objectType === 'Activity')
|
|
73
|
+
|| (!!((_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'))))
|
|
74
|
+
: (statement.object.id === options.activity && statement.object.objectType === 'Activity')))
|
|
75
|
+
&& (!sinceDate || statementDate >= sinceDate)
|
|
76
|
+
&& (!untilDate || statementDate <= untilDate);
|
|
77
|
+
});
|
|
78
|
+
if (fetchUntil) {
|
|
79
|
+
for (let i = 0; i < filteredData.length; i += pageSize) {
|
|
80
|
+
if (fetchUntil(filteredData.slice(0, i + pageSize))) {
|
|
81
|
+
filteredData = filteredData.slice(0, i + pageSize);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return filteredData;
|
|
87
|
+
};
|
|
88
|
+
const getMoreXapiStatements = async (more) => {
|
|
89
|
+
const { args, offset } = JSON.parse(more);
|
|
90
|
+
const allResults = await getAllXapiStatements(...args);
|
|
91
|
+
const end = offset + pageSize;
|
|
92
|
+
return {
|
|
93
|
+
more: allResults.length > end ? JSON.stringify({ args, offset: end }) : '',
|
|
94
|
+
statements: allResults.slice(offset, end)
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
const getXapiStatements = async (...args) => {
|
|
98
|
+
const allResults = await getAllXapiStatements(...args);
|
|
99
|
+
return {
|
|
100
|
+
more: allResults.length > pageSize ? JSON.stringify({ args, offset: pageSize }) : '',
|
|
101
|
+
statements: allResults.slice(0, pageSize)
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
putXapiStatements,
|
|
106
|
+
getAllXapiStatements,
|
|
107
|
+
getXapiStatements,
|
|
108
|
+
getMoreXapiStatements,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
+
import { GenericFetch } from '../../fetch';
|
|
3
|
+
import { WithRequired } from '../../types';
|
|
4
|
+
import { AuthProvider } from '../authProvider';
|
|
5
|
+
import { Logger } from '../logger';
|
|
6
|
+
declare type Config = {
|
|
7
|
+
lrsHost: string;
|
|
8
|
+
lrsAuthorization: string;
|
|
9
|
+
};
|
|
10
|
+
interface Initializer<C> {
|
|
11
|
+
configSpace?: C;
|
|
12
|
+
fetch: GenericFetch;
|
|
13
|
+
}
|
|
14
|
+
export interface XapiStatement {
|
|
15
|
+
actor: {
|
|
16
|
+
account: {
|
|
17
|
+
homePage: 'https://openstax.org';
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
objectType: 'Agent';
|
|
21
|
+
};
|
|
22
|
+
id: string;
|
|
23
|
+
context?: {
|
|
24
|
+
contextActivities?: {
|
|
25
|
+
parent?: [
|
|
26
|
+
{
|
|
27
|
+
id: string;
|
|
28
|
+
objectType: 'Activity';
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
};
|
|
32
|
+
statement?: {
|
|
33
|
+
objectType: 'StatementRef';
|
|
34
|
+
id: string;
|
|
35
|
+
};
|
|
36
|
+
registration?: string;
|
|
37
|
+
platform?: string;
|
|
38
|
+
};
|
|
39
|
+
object: {
|
|
40
|
+
definition?: {
|
|
41
|
+
extensions?: {
|
|
42
|
+
[key: string]: string;
|
|
43
|
+
};
|
|
44
|
+
name: {
|
|
45
|
+
[key: string]: string;
|
|
46
|
+
};
|
|
47
|
+
type: string;
|
|
48
|
+
};
|
|
49
|
+
id: string;
|
|
50
|
+
objectType: 'Activity';
|
|
51
|
+
};
|
|
52
|
+
result?: {
|
|
53
|
+
score?: {
|
|
54
|
+
scaled?: number;
|
|
55
|
+
raw?: number;
|
|
56
|
+
min?: number;
|
|
57
|
+
max?: number;
|
|
58
|
+
};
|
|
59
|
+
success?: boolean;
|
|
60
|
+
completion?: boolean;
|
|
61
|
+
response?: string;
|
|
62
|
+
duration?: string;
|
|
63
|
+
extensions?: {
|
|
64
|
+
[key: string]: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
verb: {
|
|
68
|
+
display?: {
|
|
69
|
+
[key: string]: string;
|
|
70
|
+
};
|
|
71
|
+
id: string;
|
|
72
|
+
};
|
|
73
|
+
timestamp: string;
|
|
74
|
+
stored?: string;
|
|
75
|
+
}
|
|
76
|
+
export declare type SavedXapiStatement = WithRequired<XapiStatement, 'stored'>;
|
|
77
|
+
export declare type EagerXapiStatement = Omit<XapiStatement, 'stored'>;
|
|
78
|
+
export declare type UXapiStatement = XapiStatement | EagerXapiStatement | SavedXapiStatement;
|
|
79
|
+
export declare type LrsGateway = ReturnType<ReturnType<ReturnType<typeof lrsGateway>>>;
|
|
80
|
+
export declare type LrsProvider = ReturnType<ReturnType<typeof lrsGateway>>;
|
|
81
|
+
export declare const lrsGateway: <C extends string = "lrs">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
82
|
+
lrsHost: import("../../config").ConfigValueProvider<string>;
|
|
83
|
+
lrsAuthorization: import("../../config").ConfigValueProvider<string>;
|
|
84
|
+
}; }) => ({ authProvider, logger }: {
|
|
85
|
+
authProvider: AuthProvider;
|
|
86
|
+
logger: Logger;
|
|
87
|
+
}) => {
|
|
88
|
+
putXapiStatements: (statements: Array<(Pick<XapiStatement, 'object' | 'verb' | 'context' | 'result'> & {
|
|
89
|
+
id?: string;
|
|
90
|
+
}) | UXapiStatement>) => Promise<EagerXapiStatement[]>;
|
|
91
|
+
getXapiStatements: (params: {
|
|
92
|
+
verb?: string | undefined;
|
|
93
|
+
activity?: string | undefined;
|
|
94
|
+
registration?: string | undefined;
|
|
95
|
+
related_activities?: boolean | undefined;
|
|
96
|
+
user?: string | undefined;
|
|
97
|
+
anyUser?: boolean | undefined;
|
|
98
|
+
since?: string | undefined;
|
|
99
|
+
until?: string | undefined;
|
|
100
|
+
} & {
|
|
101
|
+
ensureSync?: boolean | undefined;
|
|
102
|
+
}) => Promise<{
|
|
103
|
+
more: string;
|
|
104
|
+
statements: XapiStatement[];
|
|
105
|
+
}>;
|
|
106
|
+
getMoreXapiStatements: (more: string) => Promise<{
|
|
107
|
+
more: string;
|
|
108
|
+
statements: XapiStatement[];
|
|
109
|
+
}>;
|
|
110
|
+
getAllXapiStatements: ({ fetchUntil, ...params }: {
|
|
111
|
+
verb?: string | undefined;
|
|
112
|
+
activity?: string | undefined;
|
|
113
|
+
registration?: string | undefined;
|
|
114
|
+
related_activities?: boolean | undefined;
|
|
115
|
+
user?: string | undefined;
|
|
116
|
+
anyUser?: boolean | undefined;
|
|
117
|
+
since?: string | undefined;
|
|
118
|
+
until?: string | undefined;
|
|
119
|
+
} & {
|
|
120
|
+
ensureSync?: boolean | undefined;
|
|
121
|
+
} & {
|
|
122
|
+
fetchUntil?: ((statements: XapiStatement[]) => boolean) | undefined;
|
|
123
|
+
}) => Promise<XapiStatement[]>;
|
|
124
|
+
};
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as queryString from 'query-string';
|
|
2
|
+
import { once } from '../..';
|
|
3
|
+
import { assertDefined } from '../../assertions';
|
|
4
|
+
import { resolveConfigValue } from '../../config';
|
|
5
|
+
import { UnauthorizedError } from '../../errors';
|
|
6
|
+
import { fetchStatusRetry } from '../../fetch/fetchStatusRetry';
|
|
7
|
+
import { ifDefined } from '../../guards';
|
|
8
|
+
import { retryWithDelay } from '../../misc/helpers';
|
|
9
|
+
import { METHOD } from '../../routing';
|
|
10
|
+
import { addStatementDefaultFields } from './addStatementDefaultFields';
|
|
11
|
+
export const lrsGateway = (initializer) => (configProvider) => {
|
|
12
|
+
const config = configProvider[ifDefined(initializer.configSpace, 'lrs')];
|
|
13
|
+
const lrsHost = once(() => resolveConfigValue(config.lrsHost));
|
|
14
|
+
const lrsAuthorization = once(() => resolveConfigValue(config.lrsAuthorization));
|
|
15
|
+
return ({ authProvider, logger }) => {
|
|
16
|
+
const fetcher = fetchStatusRetry(initializer.fetch, {
|
|
17
|
+
logger,
|
|
18
|
+
retries: 2,
|
|
19
|
+
wait: 1000,
|
|
20
|
+
status: [502]
|
|
21
|
+
});
|
|
22
|
+
// Note: This method actually uses POST
|
|
23
|
+
const putXapiStatements = async (statements) => {
|
|
24
|
+
const user = assertDefined(await authProvider.getUser(), new UnauthorizedError);
|
|
25
|
+
const statementsWithDefaults = statements.map(statement => addStatementDefaultFields(statement, user));
|
|
26
|
+
const response = await fetcher((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements', {
|
|
27
|
+
body: JSON.stringify(statementsWithDefaults),
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: await lrsAuthorization(),
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'X-Experience-API-Version': '1.0.0',
|
|
32
|
+
},
|
|
33
|
+
method: METHOD.POST,
|
|
34
|
+
});
|
|
35
|
+
if (![200, 201].includes(response.status)) {
|
|
36
|
+
throw new Error(`Unexpected LRS POST statements response code ${response.status} with body:
|
|
37
|
+
|
|
38
|
+
${await response.text()}`);
|
|
39
|
+
}
|
|
40
|
+
await response.json();
|
|
41
|
+
return statementsWithDefaults;
|
|
42
|
+
};
|
|
43
|
+
// Note: This code does not currently handle a single statement response,
|
|
44
|
+
// which can return 404 if the statement is not found
|
|
45
|
+
const formatGetXapiStatementsResponse = async (response) => {
|
|
46
|
+
if (response.status !== 200) {
|
|
47
|
+
throw new Error(`Unexpected LRS GET statements response code ${response.status} with body:
|
|
48
|
+
|
|
49
|
+
${await response.text()}`);
|
|
50
|
+
}
|
|
51
|
+
return response.json();
|
|
52
|
+
};
|
|
53
|
+
const getMoreXapiStatements = async (more) => fetcher((await lrsHost()).replace(/\/+$/, '') + more, {
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: await lrsAuthorization(),
|
|
56
|
+
'X-Experience-API-Version': '1.0.0',
|
|
57
|
+
},
|
|
58
|
+
}).then(formatGetXapiStatementsResponse);
|
|
59
|
+
const fetchXapiStatements = async ({ user, anyUser, ...options }) => fetcher((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
|
|
60
|
+
...options,
|
|
61
|
+
...(anyUser === true ? {} : {
|
|
62
|
+
agent: JSON.stringify({
|
|
63
|
+
account: {
|
|
64
|
+
homePage: 'https://openstax.org',
|
|
65
|
+
name: user || assertDefined(await authProvider.getUser(), new UnauthorizedError()).uuid,
|
|
66
|
+
},
|
|
67
|
+
objectType: 'Agent',
|
|
68
|
+
}),
|
|
69
|
+
})
|
|
70
|
+
}), {
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: await lrsAuthorization(),
|
|
73
|
+
'X-Experience-API-Version': '1.0.0',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const getXapiStatements = async (params) => {
|
|
77
|
+
const { ensureSync, ...fetchParams } = params;
|
|
78
|
+
if (ensureSync) {
|
|
79
|
+
const date = new Date();
|
|
80
|
+
return retryWithDelay(async (abort) => {
|
|
81
|
+
// this function retries for the consistency, if the fetch errors just abort
|
|
82
|
+
const response = await fetchXapiStatements(fetchParams).catch(abort);
|
|
83
|
+
const consistentThrough = response.headers.get('X-Experience-API-Consistent-Through');
|
|
84
|
+
if (!consistentThrough || new Date(consistentThrough) < date) {
|
|
85
|
+
throw new Error(`xAPI consistent through ${consistentThrough}; not in sync with current date ${date.toISOString()}.`);
|
|
86
|
+
}
|
|
87
|
+
return formatGetXapiStatementsResponse(response);
|
|
88
|
+
}, { retries: 4, logger });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return fetchXapiStatements(fetchParams).then(formatGetXapiStatementsResponse);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const getAllXapiStatements = async ({ fetchUntil, ...params }) => {
|
|
95
|
+
const loadRemaining = async (result) => {
|
|
96
|
+
if (!result.more || (fetchUntil && fetchUntil(result.statements))) {
|
|
97
|
+
return result.statements;
|
|
98
|
+
}
|
|
99
|
+
const { more, statements } = await getMoreXapiStatements(result.more);
|
|
100
|
+
return loadRemaining({ more, statements: [...result.statements, ...statements] });
|
|
101
|
+
};
|
|
102
|
+
return loadRemaining(await getXapiStatements(params));
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
putXapiStatements,
|
|
106
|
+
getXapiStatements,
|
|
107
|
+
getMoreXapiStatements,
|
|
108
|
+
getAllXapiStatements,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AccountsGateway, MappedUserInfo } from '../accountsGateway';
|
|
2
|
+
import { AuthProvider } from '../authProvider';
|
|
3
|
+
import { Logger } from '../logger';
|
|
4
|
+
import { ActivityState } from './attempt-utils';
|
|
5
|
+
import { LrsGateway } from '.';
|
|
6
|
+
export interface Grade {
|
|
7
|
+
scoreGiven: number;
|
|
8
|
+
scoreMaximum: number;
|
|
9
|
+
comment?: string;
|
|
10
|
+
activityProgress: 'Initialized' | 'Started' | 'inProgress' | 'Submitted' | 'Completed';
|
|
11
|
+
gradingProgress: 'FullyGraded' | 'Pending' | 'PendingManual' | 'Failed' | 'NotReady';
|
|
12
|
+
userId?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const getRegistrationAttemptInfo: (lrs: LrsGateway, registration: string, options?: {
|
|
15
|
+
activity?: string | undefined;
|
|
16
|
+
anyUser?: boolean | undefined;
|
|
17
|
+
currentPreference?: "latest" | "oldest" | undefined;
|
|
18
|
+
ensureSync?: boolean | undefined;
|
|
19
|
+
user?: string | undefined;
|
|
20
|
+
} | undefined) => Promise<{
|
|
21
|
+
[key: string]: ActivityState;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const getScoreGrade: (score: {
|
|
24
|
+
scaled?: number;
|
|
25
|
+
raw?: number;
|
|
26
|
+
min?: number;
|
|
27
|
+
max?: number;
|
|
28
|
+
}, completed: boolean, userId?: string | undefined, maxScore?: number | undefined) => Grade;
|
|
29
|
+
export declare type Progress = {
|
|
30
|
+
scaled: number;
|
|
31
|
+
max?: number;
|
|
32
|
+
raw?: number;
|
|
33
|
+
};
|
|
34
|
+
export declare type GradeAndProgress = {
|
|
35
|
+
grade: Grade;
|
|
36
|
+
progress: Progress;
|
|
37
|
+
name?: string;
|
|
38
|
+
};
|
|
39
|
+
export declare const getCurrentGrade: (services: {
|
|
40
|
+
lrs: LrsGateway;
|
|
41
|
+
ltiAuthProvider: AuthProvider;
|
|
42
|
+
}, registration: string, options?: {
|
|
43
|
+
currentPreference?: "latest" | "oldest" | undefined;
|
|
44
|
+
incompleteAttemptCallback?: ((info: ActivityState) => Promise<GradeAndProgress>) | undefined;
|
|
45
|
+
name?: string | undefined;
|
|
46
|
+
scoreMaximum?: number | undefined;
|
|
47
|
+
userId?: string | undefined;
|
|
48
|
+
} | undefined) => Promise<GradeAndProgress | null>;
|
|
49
|
+
export declare type UserActivityInfo = MappedUserInfo<ActivityState>;
|
|
50
|
+
export declare const getAssignmentGrades: (services: {
|
|
51
|
+
accountsGateway: AccountsGateway;
|
|
52
|
+
lrs: LrsGateway;
|
|
53
|
+
logger: Logger;
|
|
54
|
+
}, assignmentIRI: string, registration: string, options?: {
|
|
55
|
+
anyUser?: boolean | undefined;
|
|
56
|
+
currentPreference?: "latest" | "oldest" | undefined;
|
|
57
|
+
incompleteAttemptsCallback?: ((mappedInfo: UserActivityInfo[]) => Promise<GradeAndProgress[]>) | undefined;
|
|
58
|
+
platformId?: string | undefined;
|
|
59
|
+
scoreMaximum?: number | undefined;
|
|
60
|
+
user?: string | undefined;
|
|
61
|
+
} | undefined) => Promise<GradeAndProgress[]>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import partition from 'lodash/fp/partition';
|
|
2
|
+
import { roundToPrecision } from '../..';
|
|
3
|
+
import { resolveAttemptInfo } from './attempt-utils';
|
|
4
|
+
export const getRegistrationAttemptInfo = async (lrs, registration, options) => {
|
|
5
|
+
const { currentPreference, ...xapiOptions } = options !== null && options !== void 0 ? options : {};
|
|
6
|
+
const allStatements = await lrs.getAllXapiStatements({ ...xapiOptions, registration });
|
|
7
|
+
// Partition statements for each user
|
|
8
|
+
const statementsPerUser = {};
|
|
9
|
+
allStatements.forEach((statement) => {
|
|
10
|
+
var _a;
|
|
11
|
+
// If we ever support accounts from different statement.actor.account.homePage, this method may need changes
|
|
12
|
+
// (unless we can guarantee the statement.actor.account.name are unique)
|
|
13
|
+
statementsPerUser[_a = statement.actor.account.name] || (statementsPerUser[_a] = []);
|
|
14
|
+
statementsPerUser[statement.actor.account.name].push(statement);
|
|
15
|
+
});
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const [userUuid, userStatements] of Object.entries(statementsPerUser)) {
|
|
18
|
+
result[userUuid] = resolveAttemptInfo(userStatements, { currentPreference });
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
// generates a payload that can be sent to the LMS, documentation here:
|
|
23
|
+
// lti: http://www.imsglobal.org/spec/lti-ags/v2p0#score-publish-service
|
|
24
|
+
// ltijs: https://cvmcosta.me/ltijs/#/grading
|
|
25
|
+
export const getScoreGrade = (score, completed, userId, maxScore) => {
|
|
26
|
+
const { raw, scaled, max } = score;
|
|
27
|
+
const scoreMaximum = maxScore !== null && maxScore !== void 0 ? maxScore : 100;
|
|
28
|
+
const scoreGiven = raw && max
|
|
29
|
+
? scoreMaximum / max * raw
|
|
30
|
+
: scaled
|
|
31
|
+
? scaled * scoreMaximum
|
|
32
|
+
: 0;
|
|
33
|
+
return {
|
|
34
|
+
userId,
|
|
35
|
+
activityProgress: completed ? 'Completed' : 'Started',
|
|
36
|
+
// canvas assumes that anything that isn't 'FullyGraded' requires manual grading and displays a "needs grading" icon.
|
|
37
|
+
// if you warp your mind you can consider the portion of the assignment which is completed to be fully graded.
|
|
38
|
+
gradingProgress: 'FullyGraded',
|
|
39
|
+
scoreMaximum,
|
|
40
|
+
scoreGiven: roundToPrecision(scoreGiven, -2),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
// These methods assign 0's to incomplete activities
|
|
44
|
+
const getCompletedActivityStateGradeAndProgress = ({ name, scoreMaximum, state, userId }) => {
|
|
45
|
+
var _a, _b;
|
|
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, scoreMaximum),
|
|
48
|
+
progress: {
|
|
49
|
+
scaled: state.currentAttemptCompleted ? 1 : 0,
|
|
50
|
+
},
|
|
51
|
+
name,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
const getCompletedUserInfosGradeAndProgress = (infos, scoreMaximum) => infos.map(({ data, fullName, platformUserId }) => getCompletedActivityStateGradeAndProgress({
|
|
55
|
+
name: fullName, scoreMaximum, userId: platformUserId, state: data
|
|
56
|
+
}));
|
|
57
|
+
export const getCurrentGrade = async (services, registration, options) => {
|
|
58
|
+
var _a;
|
|
59
|
+
const user = await services.ltiAuthProvider.getUser();
|
|
60
|
+
if (!user) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const userId = (_a = options === null || options === void 0 ? void 0 : options.userId) !== null && _a !== void 0 ? _a : user.uuid;
|
|
64
|
+
const { currentPreference, incompleteAttemptCallback, name, scoreMaximum } = options !== null && options !== void 0 ? options : {};
|
|
65
|
+
const infoPerUser = await getRegistrationAttemptInfo(services.lrs, registration, { currentPreference });
|
|
66
|
+
const userInfo = infoPerUser[user.uuid];
|
|
67
|
+
if (!userInfo) {
|
|
68
|
+
return getCompletedActivityStateGradeAndProgress({ name, scoreMaximum, state: resolveAttemptInfo([]), userId });
|
|
69
|
+
}
|
|
70
|
+
if (userInfo.currentAttemptCompleted || !incompleteAttemptCallback) {
|
|
71
|
+
return getCompletedActivityStateGradeAndProgress({ name, scoreMaximum, state: userInfo, userId });
|
|
72
|
+
}
|
|
73
|
+
return incompleteAttemptCallback(userInfo);
|
|
74
|
+
};
|
|
75
|
+
export const getAssignmentGrades = async (services, assignmentIRI, registration, options) => {
|
|
76
|
+
const { anyUser, currentPreference, incompleteAttemptsCallback, platformId, scoreMaximum, user } = options !== null && options !== void 0 ? options : {};
|
|
77
|
+
const infoPerUserUuid = await getRegistrationAttemptInfo(services.lrs, registration, { activity: assignmentIRI, anyUser, currentPreference, user });
|
|
78
|
+
const mappedInfo = await services.accountsGateway.mapUserUuids(infoPerUserUuid, services.logger, platformId);
|
|
79
|
+
if (!incompleteAttemptsCallback) {
|
|
80
|
+
return getCompletedUserInfosGradeAndProgress(mappedInfo, scoreMaximum);
|
|
81
|
+
}
|
|
82
|
+
const [incompleteInfo, completedInfo] = partition((info) => info.data.currentAttemptCompleted === undefined)(mappedInfo);
|
|
83
|
+
return getCompletedUserInfosGradeAndProgress(completedInfo, scoreMaximum).concat(await incompleteAttemptsCallback(incompleteInfo));
|
|
84
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import postgres, { Sql } from 'postgres';
|
|
2
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
3
|
+
import type { Logger } from '../logger';
|
|
4
|
+
declare type Config = {
|
|
5
|
+
host: string;
|
|
6
|
+
readHost: string;
|
|
7
|
+
port: string;
|
|
8
|
+
database: string;
|
|
9
|
+
username: string;
|
|
10
|
+
password: string;
|
|
11
|
+
};
|
|
12
|
+
interface Initializer<C> {
|
|
13
|
+
configSpace?: C;
|
|
14
|
+
}
|
|
15
|
+
export declare const postgresConnection: <C extends string = "local">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
16
|
+
host: import("../../config").ConfigValueProvider<string>;
|
|
17
|
+
readHost: import("../../config").ConfigValueProvider<string>;
|
|
18
|
+
port: import("../../config").ConfigValueProvider<string>;
|
|
19
|
+
database: import("../../config").ConfigValueProvider<string>;
|
|
20
|
+
username: import("../../config").ConfigValueProvider<string>;
|
|
21
|
+
password: import("../../config").ConfigValueProvider<string>;
|
|
22
|
+
}; }) => (services: {
|
|
23
|
+
logger: Logger;
|
|
24
|
+
}) => {
|
|
25
|
+
db: () => Promise<postgres.Sql<{}>>;
|
|
26
|
+
dbRead: () => Promise<postgres.Sql<{}>>;
|
|
27
|
+
migrate: (migrations: Migration[]) => Promise<void>;
|
|
28
|
+
down: () => Promise<void[]>;
|
|
29
|
+
};
|
|
30
|
+
export declare type PostgresConnection = ReturnType<ReturnType<ReturnType<typeof postgresConnection>>>;
|
|
31
|
+
export interface Migration {
|
|
32
|
+
name: string;
|
|
33
|
+
up: (db: Sql) => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import postgres from 'postgres';
|
|
2
|
+
import { assertNotNaN } from '../../assertions';
|
|
3
|
+
import { resolveConfigValue } from '../../config';
|
|
4
|
+
import { ifDefined } from '../../guards';
|
|
5
|
+
import { once } from '../../misc/helpers';
|
|
6
|
+
export const postgresConnection = (initializer) => (configProvider) => {
|
|
7
|
+
const config = configProvider[ifDefined(initializer.configSpace, 'local')];
|
|
8
|
+
const host = once(() => resolveConfigValue(config.host));
|
|
9
|
+
const readHost = once(() => resolveConfigValue(config.readHost));
|
|
10
|
+
const connectionOptions = once(async () => ({
|
|
11
|
+
port: assertNotNaN(parseInt(await resolveConfigValue(config.port), 10), new Error('port must be a number')),
|
|
12
|
+
database: await resolveConfigValue(config.database),
|
|
13
|
+
username: await resolveConfigValue(config.username),
|
|
14
|
+
password: await resolveConfigValue(config.password),
|
|
15
|
+
transform: postgres.camel,
|
|
16
|
+
}));
|
|
17
|
+
const connections = [];
|
|
18
|
+
const sql = once(async () => {
|
|
19
|
+
const options = await connectionOptions();
|
|
20
|
+
const connection = postgres({ ...options, host: await host() });
|
|
21
|
+
connections.push(connection);
|
|
22
|
+
return connection;
|
|
23
|
+
});
|
|
24
|
+
const sqlRead = once(async () => {
|
|
25
|
+
const options = await connectionOptions();
|
|
26
|
+
const connection = postgres({ ...options, host: await readHost() });
|
|
27
|
+
connections.push(connection);
|
|
28
|
+
return connection;
|
|
29
|
+
});
|
|
30
|
+
return (services) => ({
|
|
31
|
+
db: sql,
|
|
32
|
+
dbRead: sqlRead,
|
|
33
|
+
migrate: async (migrations) => {
|
|
34
|
+
await migrate(services, await sql(), migrations);
|
|
35
|
+
},
|
|
36
|
+
down: () => Promise.all(connections.map(c => c.end()))
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
async function migrate(services, db, migrations) {
|
|
40
|
+
await db `
|
|
41
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
42
|
+
id SERIAL PRIMARY KEY,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
ran_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
45
|
+
)
|
|
46
|
+
`;
|
|
47
|
+
const ranMigrations = await db `SELECT name FROM migrations`;
|
|
48
|
+
for (const migration of migrations) {
|
|
49
|
+
if (ranMigrations.some(({ name }) => name === migration.name)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
services.logger.log(`Running migration ${migration.name}`);
|
|
53
|
+
await migration.up(db);
|
|
54
|
+
await db `INSERT INTO migrations (name) VALUES (${migration.name})`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare type Filter = {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string | string[] | boolean;
|
|
4
|
+
};
|
|
5
|
+
declare type Field = {
|
|
6
|
+
key: string;
|
|
7
|
+
type?: 'text';
|
|
8
|
+
weight: number;
|
|
9
|
+
} | {
|
|
10
|
+
key: string;
|
|
11
|
+
type: 'keyword';
|
|
12
|
+
} | {
|
|
13
|
+
key: string;
|
|
14
|
+
type: 'boolean';
|
|
15
|
+
};
|
|
16
|
+
export interface IndexOptions<T> {
|
|
17
|
+
body: T;
|
|
18
|
+
id: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SearchOptions {
|
|
21
|
+
page?: number;
|
|
22
|
+
query: string | undefined;
|
|
23
|
+
fields: Field[];
|
|
24
|
+
/**
|
|
25
|
+
* @deprecated use `must` instead
|
|
26
|
+
*/
|
|
27
|
+
filter?: Filter[];
|
|
28
|
+
must?: Filter[];
|
|
29
|
+
must_not?: Filter[];
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IndexOptions, SearchOptions } from '.';
|
|
2
|
+
export declare const memorySearchTheBadWay: <T>({ loadAllDocumentsTheBadWay }: {
|
|
3
|
+
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
4
|
+
}) => {
|
|
5
|
+
ensureIndexCreated: () => Promise<undefined>;
|
|
6
|
+
index: (_options: IndexOptions<T>) => Promise<undefined>;
|
|
7
|
+
search: (options: SearchOptions) => Promise<{
|
|
8
|
+
items: T[];
|
|
9
|
+
pageSize: number;
|
|
10
|
+
currentPage: number;
|
|
11
|
+
totalItems: number;
|
|
12
|
+
totalPages: number;
|
|
13
|
+
}>;
|
|
14
|
+
};
|