@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.
Files changed (33) hide show
  1. package/dist/cjs/middleware/apiErrorHandler.d.ts +1 -1
  2. package/dist/cjs/middleware/apiErrorHandler.js +6 -5
  3. package/dist/cjs/routing/index.js +10 -4
  4. package/dist/cjs/services/accountsGateway/index.d.ts +13 -3
  5. package/dist/cjs/services/accountsGateway/index.js +91 -72
  6. package/dist/cjs/services/launchParams/signer.d.ts +2 -1
  7. package/dist/cjs/services/launchParams/signer.js +2 -2
  8. package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +0 -2
  9. package/dist/cjs/services/lrsGateway/xapiUtils.js +1 -1
  10. package/dist/cjs/services/searchProvider/index.d.ts +4 -0
  11. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +1 -0
  12. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +15 -1
  13. package/dist/cjs/services/searchProvider/openSearch.d.ts +1 -0
  14. package/dist/cjs/services/searchProvider/openSearch.js +23 -13
  15. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  16. package/dist/esm/middleware/apiErrorHandler.d.ts +1 -1
  17. package/dist/esm/middleware/apiErrorHandler.js +6 -5
  18. package/dist/esm/routing/index.js +10 -4
  19. package/dist/esm/services/accountsGateway/index.d.ts +13 -3
  20. package/dist/esm/services/accountsGateway/index.js +92 -73
  21. package/dist/esm/services/launchParams/signer.d.ts +2 -1
  22. package/dist/esm/services/launchParams/signer.js +2 -2
  23. package/dist/esm/services/lrsGateway/xapiUtils.d.ts +0 -2
  24. package/dist/esm/services/lrsGateway/xapiUtils.js +1 -1
  25. package/dist/esm/services/searchProvider/index.d.ts +4 -0
  26. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +1 -0
  27. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +15 -1
  28. package/dist/esm/services/searchProvider/openSearch.d.ts +1 -0
  29. package/dist/esm/services/searchProvider/openSearch.js +23 -13
  30. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  31. package/package.json +1 -1
  32. package/script/bin/deploy.bash +2 -1
  33. 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]: (e: E[T], logger: Logger) => ApiResponse<number, any>;
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
  /**
@@ -22,16 +22,17 @@ export const createErrorHandler = (inputHandlers) => {
22
22
  return async (e, logger) => {
23
23
  const name = isAppError(e) ? e.constructor.TYPE : e.constructor.name;
24
24
  const handler = handlers[name];
25
+ const logLevel = handler ? Level.Info : Level.Error;
26
+ logger.logEvent(logLevel, {
27
+ name: e.name,
28
+ message: e.message,
29
+ stack: e.stack,
30
+ });
25
31
  if (handler) {
26
32
  // convincing typescript that this error is the right kind for the handler
27
33
  // we looked up based on the errors name is very annoying
28
34
  return handler(e, logger);
29
35
  }
30
- logger.logEvent(Level.Error, {
31
- name: e.name,
32
- message: e.message,
33
- stack: e.stack,
34
- });
35
36
  return apiTextResponse(500, '500 Error');
36
37
  };
37
38
  };
@@ -105,7 +105,10 @@ const bindRoute = (services, appBinder, pathExtractor, matcher) => (route) => {
105
105
  const path = pathExtractor(request);
106
106
  const match = getParamsFromPath(path);
107
107
  if ((!matcher || matcher(request, route)) && match) {
108
- return () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, logger }, { route, params: match.params }) : undefined);
108
+ return {
109
+ name: route.name,
110
+ executor: () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, logger }, { route, params: match.params }) : undefined)
111
+ };
109
112
  }
110
113
  };
111
114
  };
@@ -158,11 +161,13 @@ export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatc
158
161
  if (logExtractor) {
159
162
  logger.setContext(logExtractor(request));
160
163
  }
164
+ logger.log('begin request');
161
165
  try {
162
- const executor = mapFind(boundRoutes, (route) => route(request, logger));
163
- if (executor) {
166
+ const route = mapFind(boundRoutes, (route) => route(request, logger));
167
+ if (route) {
168
+ logger.log(`route matched ${route.name}`);
164
169
  const result = boundResponseMiddleware ?
165
- boundResponseMiddleware(executor(), { request, logger }) : executor();
170
+ boundResponseMiddleware(route.executor(), { request, logger }) : route.executor();
166
171
  if (isPromise(result) && errorHandler) {
167
172
  const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
168
173
  boundResponseMiddleware(errorHandler(e, logger), { request, logger }) : errorHandler(e, logger);
@@ -173,6 +178,7 @@ export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatc
173
178
  }
174
179
  }
175
180
  else if (boundResponseMiddleware) {
181
+ logger.log('no route matched, returning 404');
176
182
  return boundResponseMiddleware(undefined, { request, logger });
177
183
  }
178
184
  }
@@ -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
- }, logger: Logger, platformId?: string | undefined) => Promise<MappedUserInfo<T>[]>;
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 {};
@@ -1,7 +1,7 @@
1
1
  import { chunk, isEqual } from 'lodash';
2
2
  import queryString from 'query-string';
3
3
  import { resolveConfigValue } from '../../config';
4
- import { ifDefined } from '../../guards';
4
+ import { ifDefined, isDefined } from '../../guards';
5
5
  import { METHOD } from '../../routing';
6
6
  import { Level } from '../logger';
7
7
  class ApiError extends Error {
@@ -14,78 +14,68 @@ export const accountsGateway = (initializer) => (configProvider) => {
14
14
  const config = configProvider[ifDefined(initializer.configSpace, 'accounts')];
15
15
  const accountsBase = resolveConfigValue(config.accountsBase);
16
16
  const accountsAuthToken = resolveConfigValue(config.accountsAuthToken);
17
- const request = async (method, path, options, statuses = [200, 201]) => {
18
- const host = (await accountsBase).replace(/\/+$/, '');
19
- const url = `${host}/api/${path}`;
20
- const config = {
21
- headers: {
22
- Authorization: `Bearer ${options.token || await accountsAuthToken}`,
23
- },
24
- method,
17
+ return ({ logger }) => {
18
+ const request = async (method, path, options, statuses = [200, 201]) => {
19
+ const host = (await accountsBase).replace(/\/+$/, '');
20
+ const url = `${host}/api/${path}`;
21
+ const config = {
22
+ headers: {
23
+ Authorization: `Bearer ${options.token || await accountsAuthToken}`,
24
+ },
25
+ method,
26
+ };
27
+ if (options.body) {
28
+ config.body = JSON.stringify(options.body);
29
+ }
30
+ const response = await initializer.fetch(url, config);
31
+ if (!statuses.includes(response.status)) {
32
+ throw new ApiError(`Received unexpected status code ${response.status} for Accounts API call: ${method} ${url}`, response.status);
33
+ }
34
+ return response.json();
25
35
  };
26
- if (options.body) {
27
- config.body = JSON.stringify(options.body);
28
- }
29
- const response = await initializer.fetch(url, config);
30
- if (!statuses.includes(response.status)) {
31
- throw new ApiError(`Received unexpected status code ${response.status} for Accounts API call: ${method} ${url}`, response.status);
32
- }
33
- return response.json();
34
- };
35
- const findOrCreateUser = async (body) => request(METHOD.POST, 'user/find-or-create', { body });
36
- const findUser = async (body) => {
37
- try {
38
- return await request(METHOD.POST, 'user/find', { body });
39
- }
40
- catch (error) {
41
- if (error instanceof ApiError && error.status === 404) {
42
- return undefined;
36
+ const findOrCreateUser = async (body) => request(METHOD.POST, 'user/find-or-create', { body });
37
+ const findUser = async (body) => {
38
+ try {
39
+ return await request(METHOD.POST, 'user/find', { body });
43
40
  }
44
- else {
45
- throw error;
41
+ catch (error) {
42
+ if (error instanceof ApiError && error.status === 404) {
43
+ return undefined;
44
+ }
45
+ else {
46
+ throw error;
47
+ }
46
48
  }
47
- }
48
- };
49
- const getUser = async (token) => request(METHOD.GET, 'user', { token });
50
- const linkUser = async (body) => request(METHOD.POST, 'user/external-ids', {
51
- body: {
52
- external_id: body.externalId,
53
- user_id: body.userId,
54
- }
55
- });
56
- const searchUsers = async (payload) => request(METHOD.GET, `users?${queryString.stringify(payload)}`, {});
57
- const updateUser = async (token, body) => request(METHOD.PUT, 'user', { body, token });
58
- const getPlatformUserId = (externalIds, platformId) => {
59
- for (const externalId of externalIds) {
60
- const [userPlatformId, userId] = externalId.split('/', 2);
61
- if (userPlatformId === platformId) {
62
- return userId;
49
+ };
50
+ const getUser = async (token) => request(METHOD.GET, 'user', { token });
51
+ const linkUser = async (body) => request(METHOD.POST, 'user/external-ids', {
52
+ body: {
53
+ external_id: body.externalId,
54
+ user_id: body.userId,
63
55
  }
64
- }
65
- };
66
- /*
67
- * If a platformId is given, returns an array where
68
- * the first element is the user id from the platform
69
- * and the second is the value from the map
70
- * Otherwise, returns an array where
71
- * the first element is the user's full_name
72
- * and the second is the value from the map
73
- */
74
- const mapUserUuids = async (userUuidsMap, logger, platformId) => {
75
- const results = [];
76
- // Accounts will not return any results if this search returns more than 10 users
77
- const chunkedUuids = chunk(Object.keys(userUuidsMap), 10);
78
- await Promise.all(chunkedUuids.map(async (uuids) => {
79
- const { items } = await searchUsers({ q: uuids.map((uuid) => `uuid:${uuid}`).join(' ') });
80
- const accountsUuids = items.map((user) => user.uuid);
81
- if (!isEqual(accountsUuids.sort(), uuids.sort())) {
82
- logger.logEvent(Level.Warn, {
83
- message: 'Unexpected Accounts user search results',
84
- uuids,
85
- accountsUuids,
86
- });
56
+ });
57
+ const searchUsers = async (payload) => request(METHOD.GET, `users?${queryString.stringify(payload)}`, {});
58
+ const updateUser = async (token, body) => request(METHOD.PUT, 'user', { body, token });
59
+ const getPlatformUserId = (externalIds, platformId) => {
60
+ for (const externalId of externalIds) {
61
+ const [userPlatformId, userId] = externalId.split('/', 2);
62
+ if (userPlatformId === platformId) {
63
+ return userId;
64
+ }
87
65
  }
88
- items.forEach((user) => {
66
+ };
67
+ /*
68
+ * If a platformId is given, returns an array where
69
+ * the first element is the user id from the platform
70
+ * and the second is the value from the map
71
+ * Otherwise, returns an array where
72
+ * the first element is the user's full_name
73
+ * and the second is the value from the map
74
+ */
75
+ const mapUserUuids = async (userUuidsMap, platformId) => {
76
+ const results = await mapUserData(Object.entries(userUuidsMap), ([id]) => id, (user, [, data]) => {
77
+ if (!user)
78
+ return undefined;
89
79
  const platformUserId = platformId ? getPlatformUserId(user.external_ids, platformId) : undefined;
90
80
  if (platformId && !platformUserId) {
91
81
  logger.logEvent(Level.Warn, {
@@ -100,12 +90,41 @@ export const accountsGateway = (initializer) => (configProvider) => {
100
90
  accountsUuid: user.uuid,
101
91
  });
102
92
  }
103
- results.push({
104
- data: userUuidsMap[user.uuid], fullName: user.full_name, platformUserId, uuid: user.uuid,
93
+ return { data, fullName: user.full_name, platformUserId, uuid: user.uuid };
94
+ });
95
+ return results.filter(isDefined);
96
+ };
97
+ const getUserMap = async (userUuids, mapper) => {
98
+ if (userUuids.size === 0)
99
+ return new Map();
100
+ const chunked = chunk([...userUuids], 10);
101
+ const results = await Promise.all(chunked.map(async (inputChunk) => {
102
+ const { items } = await searchUsers({ q: inputChunk.map(userUuid => `uuid:${userUuid}`).join(' ')
105
103
  });
104
+ const accountsUuids = items.map((user) => user.uuid);
105
+ if (!isEqual(accountsUuids.sort(), inputChunk.sort())) {
106
+ logger.logEvent(Level.Warn, {
107
+ message: 'Unexpected Accounts user search results',
108
+ uuids: inputChunk,
109
+ accountsUuids,
110
+ });
111
+ }
112
+ return items;
113
+ }));
114
+ return new Map(results.flat(1).map(user => [user.uuid, mapper(user)]));
115
+ };
116
+ const mapUserData = async (input, getUserUuid, mapper) => {
117
+ const userMap = await getUserMap(new Set(input.flatMap(getUserUuid)), user => user);
118
+ return input.map(item => mapper(userMap.get(getUserUuid(item)), item));
119
+ };
120
+ const mapUsersData = async (input, getUserUuids, mapper) => {
121
+ const userMap = await getUserMap(new Set(input.flatMap(getUserUuids)), user => user);
122
+ return input.map(item => {
123
+ const userUuids = getUserUuids(item);
124
+ const itemUserMap = new Map(userUuids.map(uuid => [uuid, userMap.get(uuid)]));
125
+ return mapper(itemUserMap, item);
106
126
  });
107
- }));
108
- return results;
127
+ };
128
+ return { findOrCreateUser, findUser, getUser, linkUser, mapUserUuids, searchUsers, updateUser, getUserMap, mapUserData, mapUsersData };
109
129
  };
110
- return { findOrCreateUser, findUser, getUser, linkUser, mapUserUuids, searchUsers, updateUser };
111
130
  };
@@ -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 {};
@@ -39,13 +39,13 @@ export const createLaunchSigner = ({ configSpace }) => (configProvider) => {
39
39
  const maxExpSeconds = maxExp - Math.floor(Date.now() / 1000);
40
40
  return Math.min(expiresInSeconds, maxExpSeconds);
41
41
  };
42
- const sign = async (subject, maxExp) => {
42
+ const sign = async (data, subject, maxExp) => {
43
43
  const alg = await getAlg();
44
44
  // expiresIn can be a number of seconds or a string like '1h' or '1d'
45
45
  const expiresIn = await getExpiresInWithMax(maxExp);
46
46
  const iss = await getIss();
47
47
  const header = { alg, iss };
48
- return jwt.sign({}, await getPrivateKey(), { algorithm: alg, expiresIn, header, issuer: iss, subject });
48
+ return jwt.sign(data, await getPrivateKey(), { algorithm: alg, expiresIn, header, issuer: iss, subject });
49
49
  };
50
50
  return { jwks, sign };
51
51
  };
@@ -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;
@@ -75,7 +75,7 @@ export const getCurrentGrade = async (services, registration, options) => {
75
75
  export const getAssignmentGrades = async (services, assignmentIRI, registration, options) => {
76
76
  const { anyUser, currentPreference, incompleteAttemptsCallback, platformId, scoreMaximum, user } = options !== null && options !== void 0 ? options : {};
77
77
  const infoPerUserUuid = await getRegistrationAttemptInfo(services.lrs, registration, { activity: assignmentIRI, anyUser, currentPreference, user });
78
- const mappedInfo = await services.accountsGateway.mapUserUuids(infoPerUserUuid, services.logger, platformId);
78
+ const mappedInfo = await services.accountsGateway.mapUserUuids(infoPerUserUuid, platformId);
79
79
  if (!incompleteAttemptsCallback) {
80
80
  return getCompletedUserInfosGradeAndProgress(mappedInfo, scoreMaximum);
81
81
  }
@@ -28,5 +28,9 @@ export interface SearchOptions {
28
28
  must?: Filter[];
29
29
  must_not?: Filter[];
30
30
  should?: Filter[];
31
+ sort?: {
32
+ key: string;
33
+ order: 'asc' | 'desc';
34
+ }[];
31
35
  }
32
36
  export {};
@@ -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<{
@@ -12,6 +12,8 @@ var MatchType;
12
12
  const resolveField = (document, field) => field.key.toString().split('.').reduce((result, key) => result[key], document);
13
13
  export const memorySearchTheBadWay = () => ({ store }) => {
14
14
  return {
15
+ // This method is intentionally stubbed because index deletion is not applicable for in-memory storage.
16
+ deleteIndexIfExists: async () => undefined,
15
17
  ensureIndexCreated: async () => undefined,
16
18
  index: async (_options) => undefined,
17
19
  search: async (options) => {
@@ -77,7 +79,19 @@ export const memorySearchTheBadWay = () => ({ store }) => {
77
79
  })
78
80
  .filter(isDefined)
79
81
  .filter(r => !options.query || r.weight >= MIN_MATCH);
80
- results.sort((a, b) => b.weight - a.weight);
82
+ results.sort((a, b) => {
83
+ for (const sort of (options.sort || [])) {
84
+ const aValue = resolveField(a.document, { key: sort.key });
85
+ const bValue = resolveField(b.document, { key: sort.key });
86
+ if (aValue < bValue) {
87
+ return sort.order === 'asc' ? -1 : 1;
88
+ }
89
+ if (aValue > bValue) {
90
+ return sort.order === 'asc' ? 1 : -1;
91
+ }
92
+ }
93
+ return b.weight - a.weight;
94
+ });
81
95
  const page = options.page || 1;
82
96
  const offset = (page - 1) * MAX_RESULTS;
83
97
  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>[];
@@ -17,24 +17,29 @@ export const openSearchService = (initializer = {}) => (configProvider) => {
17
17
  }));
18
18
  return (indexConfig) => {
19
19
  const pageSize = indexConfig.pageSize || 10;
20
- const createIndexIfNotExists = async (indices, params) => {
21
- const { index } = params;
20
+ const deleteIndexIfExists = async () => {
21
+ const { indices } = await client();
22
+ const index = indexConfig.name;
22
23
  const { body } = await indices.exists({ index });
23
- if (!body) {
24
- await indices.create(params);
24
+ if (body) {
25
+ await indices.delete({ index });
25
26
  }
26
27
  };
27
28
  const ensureIndexCreated = async () => {
28
29
  const { indices } = await client();
29
- await createIndexIfNotExists(indices, {
30
- index: indexConfig.name,
31
- body: {
32
- mappings: {
33
- dynamic: false,
34
- properties: indexConfig.mappings
30
+ const index = indexConfig.name;
31
+ const { body } = await indices.exists({ index });
32
+ if (!body) {
33
+ await indices.create({
34
+ index,
35
+ body: {
36
+ mappings: {
37
+ dynamic: false,
38
+ properties: indexConfig.mappings
39
+ }
35
40
  }
36
- }
37
- });
41
+ });
42
+ }
38
43
  };
39
44
  const index = async (params) => {
40
45
  const openSearchClient = await client();
@@ -81,6 +86,11 @@ export const openSearchService = (initializer = {}) => (configProvider) => {
81
86
  });
82
87
  body.query.bool.minimum_should_match = 1;
83
88
  }
89
+ if (options.sort && options.sort.length > 0) {
90
+ body.sort = options.sort.map(sort => ({
91
+ [sort.key]: { order: sort.order }
92
+ }));
93
+ }
84
94
  if (options.page) {
85
95
  body.size = pageSize;
86
96
  body.from = (options.page - 1) * pageSize;
@@ -100,6 +110,6 @@ export const openSearchService = (initializer = {}) => (configProvider) => {
100
110
  const totalPages = Math.ceil(totalItems / pageSize) || 1;
101
111
  return { items, pageSize, currentPage, totalItems, totalPages };
102
112
  };
103
- return { ensureIndexCreated, index, search };
113
+ return { ensureIndexCreated, deleteIndexIfExists, index, search };
104
114
  };
105
115
  };