@openstax/ts-utils 1.25.1-pre2 → 1.25.2
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/middleware/apiErrorHandler.d.ts +1 -1
- package/dist/cjs/middleware/apiErrorHandler.js +6 -5
- package/dist/cjs/routing/index.js +10 -4
- package/dist/cjs/services/accountsGateway/index.d.ts +13 -3
- package/dist/cjs/services/accountsGateway/index.js +91 -72
- package/dist/cjs/services/launchParams/signer.d.ts +2 -1
- package/dist/cjs/services/launchParams/signer.js +2 -2
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +0 -2
- package/dist/cjs/services/lrsGateway/xapiUtils.js +1 -1
- package/dist/cjs/services/searchProvider/index.d.ts +4 -0
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +1 -0
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +15 -1
- package/dist/cjs/services/searchProvider/openSearch.d.ts +1 -0
- package/dist/cjs/services/searchProvider/openSearch.js +23 -13
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/middleware/apiErrorHandler.d.ts +1 -1
- package/dist/esm/middleware/apiErrorHandler.js +6 -5
- package/dist/esm/routing/index.js +10 -4
- package/dist/esm/services/accountsGateway/index.d.ts +13 -3
- package/dist/esm/services/accountsGateway/index.js +92 -73
- package/dist/esm/services/launchParams/signer.d.ts +2 -1
- package/dist/esm/services/launchParams/signer.js +2 -2
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +0 -2
- package/dist/esm/services/lrsGateway/xapiUtils.js +1 -1
- package/dist/esm/services/searchProvider/index.d.ts +4 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +1 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +15 -1
- package/dist/esm/services/searchProvider/openSearch.d.ts +1 -0
- package/dist/esm/services/searchProvider/openSearch.js +23 -13
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/script/bin/deploy.bash +2 -1
- package/script/bin/init-params-script.bash +10 -5
|
@@ -10,7 +10,7 @@ export declare type DefaultErrors = {
|
|
|
10
10
|
SessionExpiredError: SessionExpiredError;
|
|
11
11
|
};
|
|
12
12
|
export declare type Handlers<E> = {
|
|
13
|
-
[T in keyof E]
|
|
13
|
+
[T in keyof E]?: (e: E[T], logger: Logger) => ApiResponse<number, any>;
|
|
14
14
|
};
|
|
15
15
|
export declare const defaultHandlers: Handlers<DefaultErrors>;
|
|
16
16
|
/**
|
|
@@ -25,16 +25,17 @@ const createErrorHandler = (inputHandlers) => {
|
|
|
25
25
|
return async (e, logger) => {
|
|
26
26
|
const name = (0, errors_1.isAppError)(e) ? e.constructor.TYPE : e.constructor.name;
|
|
27
27
|
const handler = handlers[name];
|
|
28
|
+
const logLevel = handler ? logger_1.Level.Info : logger_1.Level.Error;
|
|
29
|
+
logger.logEvent(logLevel, {
|
|
30
|
+
name: e.name,
|
|
31
|
+
message: e.message,
|
|
32
|
+
stack: e.stack,
|
|
33
|
+
});
|
|
28
34
|
if (handler) {
|
|
29
35
|
// convincing typescript that this error is the right kind for the handler
|
|
30
36
|
// we looked up based on the errors name is very annoying
|
|
31
37
|
return handler(e, logger);
|
|
32
38
|
}
|
|
33
|
-
logger.logEvent(logger_1.Level.Error, {
|
|
34
|
-
name: e.name,
|
|
35
|
-
message: e.message,
|
|
36
|
-
stack: e.stack,
|
|
37
|
-
});
|
|
38
39
|
return (0, routing_1.apiTextResponse)(500, '500 Error');
|
|
39
40
|
};
|
|
40
41
|
};
|
|
@@ -139,7 +139,10 @@ const bindRoute = (services, appBinder, pathExtractor, matcher) => (route) => {
|
|
|
139
139
|
const path = pathExtractor(request);
|
|
140
140
|
const match = getParamsFromPath(path);
|
|
141
141
|
if ((!matcher || matcher(request, route)) && match) {
|
|
142
|
-
return
|
|
142
|
+
return {
|
|
143
|
+
name: route.name,
|
|
144
|
+
executor: () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, logger }, { route, params: match.params }) : undefined)
|
|
145
|
+
};
|
|
143
146
|
}
|
|
144
147
|
};
|
|
145
148
|
};
|
|
@@ -192,11 +195,13 @@ const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatcher, er
|
|
|
192
195
|
if (logExtractor) {
|
|
193
196
|
logger.setContext(logExtractor(request));
|
|
194
197
|
}
|
|
198
|
+
logger.log('begin request');
|
|
195
199
|
try {
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
200
|
+
const route = (0, helpers_1.mapFind)(boundRoutes, (route) => route(request, logger));
|
|
201
|
+
if (route) {
|
|
202
|
+
logger.log(`route matched ${route.name}`);
|
|
198
203
|
const result = boundResponseMiddleware ?
|
|
199
|
-
boundResponseMiddleware(executor(), { request, logger }) : executor();
|
|
204
|
+
boundResponseMiddleware(route.executor(), { request, logger }) : route.executor();
|
|
200
205
|
if (isPromise(result) && errorHandler) {
|
|
201
206
|
const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
|
|
202
207
|
boundResponseMiddleware(errorHandler(e, logger), { request, logger }) : errorHandler(e, logger);
|
|
@@ -207,6 +212,7 @@ const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatcher, er
|
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
214
|
else if (boundResponseMiddleware) {
|
|
215
|
+
logger.log('no route matched, returning 404');
|
|
210
216
|
return boundResponseMiddleware(undefined, { request, logger });
|
|
211
217
|
}
|
|
212
218
|
}
|
|
@@ -70,16 +70,26 @@ export declare type MappedUserInfo<T> = {
|
|
|
70
70
|
export declare const accountsGateway: <C extends string = "accounts">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
71
71
|
accountsBase: import("../../config").ConfigValueProvider<string>;
|
|
72
72
|
accountsAuthToken: import("../../config").ConfigValueProvider<string>;
|
|
73
|
-
}; }) => {
|
|
73
|
+
}; }) => ({ logger }: {
|
|
74
|
+
logger: Logger;
|
|
75
|
+
}) => {
|
|
74
76
|
findOrCreateUser: (body: FindOrCreateUserPayload) => Promise<FindOrCreateUserResponse>;
|
|
75
77
|
findUser: (body: FindUserPayload) => Promise<FindUserResponse>;
|
|
76
78
|
getUser: (token: string) => Promise<ApiUser & JsonCompatibleStruct>;
|
|
77
79
|
linkUser: (body: LinkUserPayload) => Promise<LinkUserResponse>;
|
|
78
80
|
mapUserUuids: <T>(userUuidsMap: {
|
|
79
81
|
[uuid: string]: T;
|
|
80
|
-
},
|
|
82
|
+
}, platformId?: string | undefined) => Promise<{
|
|
83
|
+
data: T;
|
|
84
|
+
fullName: string;
|
|
85
|
+
platformUserId: string | undefined;
|
|
86
|
+
uuid: string;
|
|
87
|
+
}[]>;
|
|
81
88
|
searchUsers: (payload: SearchUsersPayload) => Promise<SearchUsersResponse>;
|
|
82
89
|
updateUser: (token: string, body: UpdateUserPayload) => Promise<ApiUser & JsonCompatibleStruct>;
|
|
90
|
+
getUserMap: <R>(userUuids: Set<string>, mapper: (user: ApiUser) => R) => Promise<Map<string, R>>;
|
|
91
|
+
mapUserData: <T_1, R_1>(input: T_1[], getUserUuid: (item: T_1) => string, mapper: (user: ApiUser | undefined, item: T_1) => R_1) => Promise<R_1[]>;
|
|
92
|
+
mapUsersData: <T_2, R_2>(input: T_2[], getUserUuids: (item: T_2) => string[], mapper: (users: Map<string, ApiUser | undefined>, item: T_2) => R_2) => Promise<R_2[]>;
|
|
83
93
|
};
|
|
84
|
-
export declare type AccountsGateway = ReturnType<ReturnType<typeof accountsGateway
|
|
94
|
+
export declare type AccountsGateway = ReturnType<ReturnType<ReturnType<typeof accountsGateway>>>;
|
|
85
95
|
export {};
|
|
@@ -20,78 +20,68 @@ const accountsGateway = (initializer) => (configProvider) => {
|
|
|
20
20
|
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'accounts')];
|
|
21
21
|
const accountsBase = (0, config_1.resolveConfigValue)(config.accountsBase);
|
|
22
22
|
const accountsAuthToken = (0, config_1.resolveConfigValue)(config.accountsAuthToken);
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
return ({ logger }) => {
|
|
24
|
+
const request = async (method, path, options, statuses = [200, 201]) => {
|
|
25
|
+
const host = (await accountsBase).replace(/\/+$/, '');
|
|
26
|
+
const url = `${host}/api/${path}`;
|
|
27
|
+
const config = {
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${options.token || await accountsAuthToken}`,
|
|
30
|
+
},
|
|
31
|
+
method,
|
|
32
|
+
};
|
|
33
|
+
if (options.body) {
|
|
34
|
+
config.body = JSON.stringify(options.body);
|
|
35
|
+
}
|
|
36
|
+
const response = await initializer.fetch(url, config);
|
|
37
|
+
if (!statuses.includes(response.status)) {
|
|
38
|
+
throw new ApiError(`Received unexpected status code ${response.status} for Accounts API call: ${method} ${url}`, response.status);
|
|
39
|
+
}
|
|
40
|
+
return response.json();
|
|
31
41
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!statuses.includes(response.status)) {
|
|
37
|
-
throw new ApiError(`Received unexpected status code ${response.status} for Accounts API call: ${method} ${url}`, response.status);
|
|
38
|
-
}
|
|
39
|
-
return response.json();
|
|
40
|
-
};
|
|
41
|
-
const findOrCreateUser = async (body) => request(routing_1.METHOD.POST, 'user/find-or-create', { body });
|
|
42
|
-
const findUser = async (body) => {
|
|
43
|
-
try {
|
|
44
|
-
return await request(routing_1.METHOD.POST, 'user/find', { body });
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (error instanceof ApiError && error.status === 404) {
|
|
48
|
-
return undefined;
|
|
42
|
+
const findOrCreateUser = async (body) => request(routing_1.METHOD.POST, 'user/find-or-create', { body });
|
|
43
|
+
const findUser = async (body) => {
|
|
44
|
+
try {
|
|
45
|
+
return await request(routing_1.METHOD.POST, 'user/find', { body });
|
|
49
46
|
}
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
52
54
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
user_id: body.userId,
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
const searchUsers = async (payload) => request(routing_1.METHOD.GET, `users?${query_string_1.default.stringify(payload)}`, {});
|
|
63
|
-
const updateUser = async (token, body) => request(routing_1.METHOD.PUT, 'user', { body, token });
|
|
64
|
-
const getPlatformUserId = (externalIds, platformId) => {
|
|
65
|
-
for (const externalId of externalIds) {
|
|
66
|
-
const [userPlatformId, userId] = externalId.split('/', 2);
|
|
67
|
-
if (userPlatformId === platformId) {
|
|
68
|
-
return userId;
|
|
55
|
+
};
|
|
56
|
+
const getUser = async (token) => request(routing_1.METHOD.GET, 'user', { token });
|
|
57
|
+
const linkUser = async (body) => request(routing_1.METHOD.POST, 'user/external-ids', {
|
|
58
|
+
body: {
|
|
59
|
+
external_id: body.externalId,
|
|
60
|
+
user_id: body.userId,
|
|
69
61
|
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
80
|
-
const mapUserUuids = async (userUuidsMap, logger, platformId) => {
|
|
81
|
-
const results = [];
|
|
82
|
-
// Accounts will not return any results if this search returns more than 10 users
|
|
83
|
-
const chunkedUuids = (0, lodash_1.chunk)(Object.keys(userUuidsMap), 10);
|
|
84
|
-
await Promise.all(chunkedUuids.map(async (uuids) => {
|
|
85
|
-
const { items } = await searchUsers({ q: uuids.map((uuid) => `uuid:${uuid}`).join(' ') });
|
|
86
|
-
const accountsUuids = items.map((user) => user.uuid);
|
|
87
|
-
if (!(0, lodash_1.isEqual)(accountsUuids.sort(), uuids.sort())) {
|
|
88
|
-
logger.logEvent(logger_1.Level.Warn, {
|
|
89
|
-
message: 'Unexpected Accounts user search results',
|
|
90
|
-
uuids,
|
|
91
|
-
accountsUuids,
|
|
92
|
-
});
|
|
62
|
+
});
|
|
63
|
+
const searchUsers = async (payload) => request(routing_1.METHOD.GET, `users?${query_string_1.default.stringify(payload)}`, {});
|
|
64
|
+
const updateUser = async (token, body) => request(routing_1.METHOD.PUT, 'user', { body, token });
|
|
65
|
+
const getPlatformUserId = (externalIds, platformId) => {
|
|
66
|
+
for (const externalId of externalIds) {
|
|
67
|
+
const [userPlatformId, userId] = externalId.split('/', 2);
|
|
68
|
+
if (userPlatformId === platformId) {
|
|
69
|
+
return userId;
|
|
70
|
+
}
|
|
93
71
|
}
|
|
94
|
-
|
|
72
|
+
};
|
|
73
|
+
/*
|
|
74
|
+
* If a platformId is given, returns an array where
|
|
75
|
+
* the first element is the user id from the platform
|
|
76
|
+
* and the second is the value from the map
|
|
77
|
+
* Otherwise, returns an array where
|
|
78
|
+
* the first element is the user's full_name
|
|
79
|
+
* and the second is the value from the map
|
|
80
|
+
*/
|
|
81
|
+
const mapUserUuids = async (userUuidsMap, platformId) => {
|
|
82
|
+
const results = await mapUserData(Object.entries(userUuidsMap), ([id]) => id, (user, [, data]) => {
|
|
83
|
+
if (!user)
|
|
84
|
+
return undefined;
|
|
95
85
|
const platformUserId = platformId ? getPlatformUserId(user.external_ids, platformId) : undefined;
|
|
96
86
|
if (platformId && !platformUserId) {
|
|
97
87
|
logger.logEvent(logger_1.Level.Warn, {
|
|
@@ -106,13 +96,42 @@ const accountsGateway = (initializer) => (configProvider) => {
|
|
|
106
96
|
accountsUuid: user.uuid,
|
|
107
97
|
});
|
|
108
98
|
}
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
return { data, fullName: user.full_name, platformUserId, uuid: user.uuid };
|
|
100
|
+
});
|
|
101
|
+
return results.filter(guards_1.isDefined);
|
|
102
|
+
};
|
|
103
|
+
const getUserMap = async (userUuids, mapper) => {
|
|
104
|
+
if (userUuids.size === 0)
|
|
105
|
+
return new Map();
|
|
106
|
+
const chunked = (0, lodash_1.chunk)([...userUuids], 10);
|
|
107
|
+
const results = await Promise.all(chunked.map(async (inputChunk) => {
|
|
108
|
+
const { items } = await searchUsers({ q: inputChunk.map(userUuid => `uuid:${userUuid}`).join(' ')
|
|
111
109
|
});
|
|
110
|
+
const accountsUuids = items.map((user) => user.uuid);
|
|
111
|
+
if (!(0, lodash_1.isEqual)(accountsUuids.sort(), inputChunk.sort())) {
|
|
112
|
+
logger.logEvent(logger_1.Level.Warn, {
|
|
113
|
+
message: 'Unexpected Accounts user search results',
|
|
114
|
+
uuids: inputChunk,
|
|
115
|
+
accountsUuids,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return items;
|
|
119
|
+
}));
|
|
120
|
+
return new Map(results.flat(1).map(user => [user.uuid, mapper(user)]));
|
|
121
|
+
};
|
|
122
|
+
const mapUserData = async (input, getUserUuid, mapper) => {
|
|
123
|
+
const userMap = await getUserMap(new Set(input.flatMap(getUserUuid)), user => user);
|
|
124
|
+
return input.map(item => mapper(userMap.get(getUserUuid(item)), item));
|
|
125
|
+
};
|
|
126
|
+
const mapUsersData = async (input, getUserUuids, mapper) => {
|
|
127
|
+
const userMap = await getUserMap(new Set(input.flatMap(getUserUuids)), user => user);
|
|
128
|
+
return input.map(item => {
|
|
129
|
+
const userUuids = getUserUuids(item);
|
|
130
|
+
const itemUserMap = new Map(userUuids.map(uuid => [uuid, userMap.get(uuid)]));
|
|
131
|
+
return mapper(itemUserMap, item);
|
|
112
132
|
});
|
|
113
|
-
}
|
|
114
|
-
return
|
|
133
|
+
};
|
|
134
|
+
return { findOrCreateUser, findUser, getUser, linkUser, mapUserUuids, searchUsers, updateUser, getUserMap, mapUserData, mapUsersData };
|
|
115
135
|
};
|
|
116
|
-
return { findOrCreateUser, findUser, getUser, linkUser, mapUserUuids, searchUsers, updateUser };
|
|
117
136
|
};
|
|
118
137
|
exports.accountsGateway = accountsGateway;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JWK } from 'node-jose';
|
|
2
2
|
import { ConfigProviderForConfig } from '../../config';
|
|
3
|
+
import type { JsonCompatibleValue } from '../../routing';
|
|
3
4
|
declare type Config = {
|
|
4
5
|
alg: string;
|
|
5
6
|
expiresIn: string;
|
|
@@ -21,7 +22,7 @@ export declare const createLaunchSigner: <C extends string = "launch">({ configS
|
|
|
21
22
|
jwks: () => Promise<{
|
|
22
23
|
keys: JWK.RawKey[];
|
|
23
24
|
}>;
|
|
24
|
-
sign: (subject: string, maxExp?: number | null | undefined) => Promise<string>;
|
|
25
|
+
sign: (data: Record<string, JsonCompatibleValue>, subject: string, maxExp?: number | null | undefined) => Promise<string>;
|
|
25
26
|
};
|
|
26
27
|
export declare type LaunchSigner = ReturnType<ReturnType<typeof createLaunchSigner>>;
|
|
27
28
|
export {};
|
|
@@ -45,13 +45,13 @@ const createLaunchSigner = ({ configSpace }) => (configProvider) => {
|
|
|
45
45
|
const maxExpSeconds = maxExp - Math.floor(Date.now() / 1000);
|
|
46
46
|
return Math.min(expiresInSeconds, maxExpSeconds);
|
|
47
47
|
};
|
|
48
|
-
const sign = async (subject, maxExp) => {
|
|
48
|
+
const sign = async (data, subject, maxExp) => {
|
|
49
49
|
const alg = await getAlg();
|
|
50
50
|
// expiresIn can be a number of seconds or a string like '1h' or '1d'
|
|
51
51
|
const expiresIn = await getExpiresInWithMax(maxExp);
|
|
52
52
|
const iss = await getIss();
|
|
53
53
|
const header = { alg, iss };
|
|
54
|
-
return jsonwebtoken_1.default.sign(
|
|
54
|
+
return jsonwebtoken_1.default.sign(data, await getPrivateKey(), { algorithm: alg, expiresIn, header, issuer: iss, subject });
|
|
55
55
|
};
|
|
56
56
|
return { jwks, sign };
|
|
57
57
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AccountsGateway, MappedUserInfo } from '../accountsGateway';
|
|
2
2
|
import { AuthProvider } from '../authProvider';
|
|
3
|
-
import { Logger } from '../logger';
|
|
4
3
|
import { ActivityState } from './attempt-utils';
|
|
5
4
|
import { LrsGateway } from '.';
|
|
6
5
|
export interface Grade {
|
|
@@ -50,7 +49,6 @@ export declare type UserActivityInfo = MappedUserInfo<ActivityState>;
|
|
|
50
49
|
export declare const getAssignmentGrades: (services: {
|
|
51
50
|
accountsGateway: AccountsGateway;
|
|
52
51
|
lrs: LrsGateway;
|
|
53
|
-
logger: Logger;
|
|
54
52
|
}, assignmentIRI: string, registration: string, options?: {
|
|
55
53
|
anyUser?: boolean | undefined;
|
|
56
54
|
currentPreference?: "latest" | "oldest" | undefined;
|
|
@@ -84,7 +84,7 @@ exports.getCurrentGrade = getCurrentGrade;
|
|
|
84
84
|
const getAssignmentGrades = async (services, assignmentIRI, registration, options) => {
|
|
85
85
|
const { anyUser, currentPreference, incompleteAttemptsCallback, platformId, scoreMaximum, user } = options !== null && options !== void 0 ? options : {};
|
|
86
86
|
const infoPerUserUuid = await (0, exports.getRegistrationAttemptInfo)(services.lrs, registration, { activity: assignmentIRI, anyUser, currentPreference, user });
|
|
87
|
-
const mappedInfo = await services.accountsGateway.mapUserUuids(infoPerUserUuid,
|
|
87
|
+
const mappedInfo = await services.accountsGateway.mapUserUuids(infoPerUserUuid, platformId);
|
|
88
88
|
if (!incompleteAttemptsCallback) {
|
|
89
89
|
return getCompletedUserInfosGradeAndProgress(mappedInfo, scoreMaximum);
|
|
90
90
|
}
|
|
@@ -4,6 +4,7 @@ export declare const memorySearchTheBadWay: () => <T>({ store }: {
|
|
|
4
4
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
5
5
|
};
|
|
6
6
|
}) => {
|
|
7
|
+
deleteIndexIfExists: () => Promise<undefined>;
|
|
7
8
|
ensureIndexCreated: () => Promise<undefined>;
|
|
8
9
|
index: (_options: IndexOptions<T>) => Promise<undefined>;
|
|
9
10
|
search: (options: SearchOptions) => Promise<{
|
|
@@ -15,6 +15,8 @@ var MatchType;
|
|
|
15
15
|
const resolveField = (document, field) => field.key.toString().split('.').reduce((result, key) => result[key], document);
|
|
16
16
|
const memorySearchTheBadWay = () => ({ store }) => {
|
|
17
17
|
return {
|
|
18
|
+
// This method is intentionally stubbed because index deletion is not applicable for in-memory storage.
|
|
19
|
+
deleteIndexIfExists: async () => undefined,
|
|
18
20
|
ensureIndexCreated: async () => undefined,
|
|
19
21
|
index: async (_options) => undefined,
|
|
20
22
|
search: async (options) => {
|
|
@@ -80,7 +82,19 @@ const memorySearchTheBadWay = () => ({ store }) => {
|
|
|
80
82
|
})
|
|
81
83
|
.filter(guards_1.isDefined)
|
|
82
84
|
.filter(r => !options.query || r.weight >= MIN_MATCH);
|
|
83
|
-
results.sort((a, b) =>
|
|
85
|
+
results.sort((a, b) => {
|
|
86
|
+
for (const sort of (options.sort || [])) {
|
|
87
|
+
const aValue = resolveField(a.document, { key: sort.key });
|
|
88
|
+
const bValue = resolveField(b.document, { key: sort.key });
|
|
89
|
+
if (aValue < bValue) {
|
|
90
|
+
return sort.order === 'asc' ? -1 : 1;
|
|
91
|
+
}
|
|
92
|
+
if (aValue > bValue) {
|
|
93
|
+
return sort.order === 'asc' ? 1 : -1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return b.weight - a.weight;
|
|
97
|
+
});
|
|
84
98
|
const page = options.page || 1;
|
|
85
99
|
const offset = (page - 1) * MAX_RESULTS;
|
|
86
100
|
return {
|
|
@@ -17,6 +17,7 @@ export declare const openSearchService: <C extends string = "deployed">(initiali
|
|
|
17
17
|
region: import("../../config").ConfigValueProvider<string>;
|
|
18
18
|
}; }) => <T>(indexConfig: IndexConfig) => {
|
|
19
19
|
ensureIndexCreated: () => Promise<void>;
|
|
20
|
+
deleteIndexIfExists: () => Promise<void>;
|
|
20
21
|
index: (params: IndexOptions<T>) => Promise<void>;
|
|
21
22
|
search: (options: SearchOptions) => Promise<{
|
|
22
23
|
items: Exclude<T, undefined>[];
|
|
@@ -20,24 +20,29 @@ const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
20
20
|
}));
|
|
21
21
|
return (indexConfig) => {
|
|
22
22
|
const pageSize = indexConfig.pageSize || 10;
|
|
23
|
-
const
|
|
24
|
-
const {
|
|
23
|
+
const deleteIndexIfExists = async () => {
|
|
24
|
+
const { indices } = await client();
|
|
25
|
+
const index = indexConfig.name;
|
|
25
26
|
const { body } = await indices.exists({ index });
|
|
26
|
-
if (
|
|
27
|
-
await indices.
|
|
27
|
+
if (body) {
|
|
28
|
+
await indices.delete({ index });
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
const ensureIndexCreated = async () => {
|
|
31
32
|
const { indices } = await client();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const index = indexConfig.name;
|
|
34
|
+
const { body } = await indices.exists({ index });
|
|
35
|
+
if (!body) {
|
|
36
|
+
await indices.create({
|
|
37
|
+
index,
|
|
38
|
+
body: {
|
|
39
|
+
mappings: {
|
|
40
|
+
dynamic: false,
|
|
41
|
+
properties: indexConfig.mappings
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
41
46
|
};
|
|
42
47
|
const index = async (params) => {
|
|
43
48
|
const openSearchClient = await client();
|
|
@@ -84,6 +89,11 @@ const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
84
89
|
});
|
|
85
90
|
body.query.bool.minimum_should_match = 1;
|
|
86
91
|
}
|
|
92
|
+
if (options.sort && options.sort.length > 0) {
|
|
93
|
+
body.sort = options.sort.map(sort => ({
|
|
94
|
+
[sort.key]: { order: sort.order }
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
87
97
|
if (options.page) {
|
|
88
98
|
body.size = pageSize;
|
|
89
99
|
body.from = (options.page - 1) * pageSize;
|
|
@@ -103,7 +113,7 @@ const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
103
113
|
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
|
104
114
|
return { items, pageSize, currentPage, totalItems, totalPages };
|
|
105
115
|
};
|
|
106
|
-
return { ensureIndexCreated, index, search };
|
|
116
|
+
return { ensureIndexCreated, deleteIndexIfExists, index, search };
|
|
107
117
|
};
|
|
108
118
|
};
|
|
109
119
|
exports.openSearchService = openSearchService;
|