@openstax/ts-utils 1.21.12 → 1.24.1

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 (37) hide show
  1. package/dist/cjs/errors/index.d.ts +11 -0
  2. package/dist/cjs/errors/index.js +15 -1
  3. package/dist/cjs/middleware/apiErrorHandler.d.ts +4 -3
  4. package/dist/cjs/middleware/apiErrorHandler.js +4 -3
  5. package/dist/cjs/misc/helpers.js +1 -1
  6. package/dist/cjs/services/accountsGateway/index.js +3 -4
  7. package/dist/cjs/services/apiGateway/index.d.ts +6 -2
  8. package/dist/cjs/services/apiGateway/index.js +17 -5
  9. package/dist/cjs/services/fileServer/localFileServer.js +5 -4
  10. package/dist/cjs/services/fileServer/s3FileServer.js +1 -0
  11. package/dist/cjs/services/lrsGateway/index.js +1 -1
  12. package/dist/cjs/services/openSearch.d.ts +29 -0
  13. package/dist/cjs/services/openSearch.js +107 -0
  14. package/dist/cjs/services/searchProvider/index.d.ts +1 -0
  15. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +11 -4
  16. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  17. package/dist/esm/errors/index.d.ts +11 -0
  18. package/dist/esm/errors/index.js +13 -0
  19. package/dist/esm/middleware/apiErrorHandler.d.ts +4 -3
  20. package/dist/esm/middleware/apiErrorHandler.js +5 -4
  21. package/dist/esm/misc/helpers.js +1 -1
  22. package/dist/esm/services/accountsGateway/index.js +3 -4
  23. package/dist/esm/services/apiGateway/index.d.ts +6 -2
  24. package/dist/esm/services/apiGateway/index.js +18 -6
  25. package/dist/esm/services/fileServer/localFileServer.js +6 -5
  26. package/dist/esm/services/fileServer/s3FileServer.js +1 -0
  27. package/dist/esm/services/lrsGateway/index.js +1 -1
  28. package/dist/esm/services/openSearch.d.ts +29 -0
  29. package/dist/esm/services/openSearch.js +103 -0
  30. package/dist/esm/services/searchProvider/index.d.ts +1 -0
  31. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +11 -4
  32. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  33. package/package.json +3 -1
  34. package/script/bin/deploy.bash +8 -0
  35. package/script/bin/get-env-param.bash +3 -3
  36. package/script/bin/init-params-script.bash +10 -1
  37. package/script/bin/upload-params.bash +3 -3
@@ -42,6 +42,17 @@ export declare class UnauthorizedError extends Error {
42
42
  static matches: (e: any) => e is typeof UnauthorizedError;
43
43
  constructor(message?: string);
44
44
  }
45
+ /**
46
+ * Forbidden error
47
+ *
48
+ * `ForbiddenError.matches(error)` is a reliable way to check if an error is a
49
+ * `ForbiddenError`; `instanceof` checks may not work if code is split into multiple bundles
50
+ */
51
+ export declare class ForbiddenError extends Error {
52
+ static readonly TYPE = "ForbiddenError";
53
+ static matches: (e: any) => e is typeof ForbiddenError;
54
+ constructor(message?: string);
55
+ }
45
56
  /**
46
57
  * Not found error
47
58
  *
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SessionExpiredError = exports.NotFoundError = exports.UnauthorizedError = exports.ValidationError = exports.InvalidRequestError = exports.isAppError = void 0;
3
+ exports.SessionExpiredError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.ValidationError = exports.InvalidRequestError = exports.isAppError = void 0;
4
4
  /*
5
5
  * if code is split into multiple bundles, sometimes each bundle
6
6
  * will get its own definition of this module and then instanceof checks
@@ -65,6 +65,20 @@ class UnauthorizedError extends Error {
65
65
  exports.UnauthorizedError = UnauthorizedError;
66
66
  UnauthorizedError.TYPE = 'UnauthorizedError';
67
67
  UnauthorizedError.matches = errorIsType(UnauthorizedError);
68
+ /**
69
+ * Forbidden error
70
+ *
71
+ * `ForbiddenError.matches(error)` is a reliable way to check if an error is a
72
+ * `ForbiddenError`; `instanceof` checks may not work if code is split into multiple bundles
73
+ */
74
+ class ForbiddenError extends Error {
75
+ constructor(message) {
76
+ super(message || ForbiddenError.TYPE);
77
+ }
78
+ }
79
+ exports.ForbiddenError = ForbiddenError;
80
+ ForbiddenError.TYPE = 'ForbiddenError';
81
+ ForbiddenError.matches = errorIsType(ForbiddenError);
68
82
  /**
69
83
  * Not found error
70
84
  *
@@ -1,12 +1,13 @@
1
- import { InvalidRequestError, NotFoundError, SessionExpiredError, UnauthorizedError, ValidationError } from '../errors';
1
+ import { ForbiddenError, InvalidRequestError, NotFoundError, SessionExpiredError, UnauthorizedError, ValidationError } from '../errors';
2
2
  import type { ApiResponse } from '../routing';
3
3
  import type { Logger } from '../services/logger';
4
4
  export declare type DefaultErrors = {
5
+ InvalidRequestError: InvalidRequestError;
5
6
  UnauthorizedError: UnauthorizedError;
6
- SessionExpiredError: SessionExpiredError;
7
+ ForbiddenError: ForbiddenError;
7
8
  NotFoundError: NotFoundError;
8
- InvalidRequestError: InvalidRequestError;
9
9
  ValidationError: ValidationError;
10
+ SessionExpiredError: SessionExpiredError;
10
11
  };
11
12
  export declare type Handlers<E> = {
12
13
  [T in keyof E]: (e: E[T], logger: Logger) => ApiResponse<number, any>;
@@ -5,11 +5,12 @@ const errors_1 = require("../errors");
5
5
  const routing_1 = require("../routing");
6
6
  const logger_1 = require("../services/logger");
7
7
  exports.defaultHandlers = {
8
- UnauthorizedError: () => (0, routing_1.apiTextResponse)(401, '401 UnauthorizedError'),
9
- SessionExpiredError: () => (0, routing_1.apiTextResponse)(440, '440 SessionExpiredError'),
10
- NotFoundError: (e) => (0, routing_1.apiTextResponse)(404, `404 ${e.message}`),
11
8
  InvalidRequestError: (e) => (0, routing_1.apiTextResponse)(400, `400 ${e.message}`),
9
+ UnauthorizedError: (e) => (0, routing_1.apiTextResponse)(401, `401 ${e.message}`),
10
+ ForbiddenError: (e) => (0, routing_1.apiTextResponse)(403, `403 ${e.message}`),
11
+ NotFoundError: (e) => (0, routing_1.apiTextResponse)(404, `404 ${e.message}`),
12
12
  ValidationError: (e) => (0, routing_1.apiJsonResponse)(422, e.getData()),
13
+ SessionExpiredError: (e) => (0, routing_1.apiTextResponse)(440, `440 ${e.message}`),
13
14
  };
14
15
  /**
15
16
  * Creates an error handler. Provides default handlers for `UnauthorizedError`,
@@ -84,7 +84,7 @@ const retryWithDelay = (fn, options) => {
84
84
  reject(e);
85
85
  }
86
86
  else {
87
- (_a = options === null || options === void 0 ? void 0 : options.logger) === null || _a === void 0 ? void 0 : _a.log(`failed try ${n} of ${retries}. ${e.message}`);
87
+ (_a = options === null || options === void 0 ? void 0 : options.logger) === null || _a === void 0 ? void 0 : _a.log(`failed try ${n + 1} of ${retries}. ${e.message}`);
88
88
  setTimeout(() => (0, exports.retryWithDelay)(fn, { ...options, n: n + 1 }).then(resolve, reject), timeout);
89
89
  }
90
90
  });
@@ -23,11 +23,10 @@ const accountsGateway = (initializer) => (configProvider) => {
23
23
  const request = async (method, path, options, statuses = [200, 201]) => {
24
24
  const host = (await accountsBase).replace(/\/+$/, '');
25
25
  const url = `${host}/api/${path}`;
26
- const token = options.token || await accountsAuthToken;
27
26
  const config = {
28
- headers: token
29
- ? { Authorization: `Bearer ${token}` }
30
- : {},
27
+ headers: {
28
+ Authorization: `Bearer ${options.token || await accountsAuthToken}`,
29
+ },
31
30
  method,
32
31
  };
33
32
  if (options.body) {
@@ -3,6 +3,7 @@ import { ConfigProviderForConfig } from '../../config';
3
3
  import { ConfigForFetch, GenericFetch, Response } from '../../fetch';
4
4
  import { AnyRoute, ApiResponse, OutputForRoute, ParamsForRoute, PayloadForRoute, QueryParams } from '../../routing';
5
5
  import { UnwrapPromise } from '../../types';
6
+ import { Logger } from '../logger';
6
7
  declare type TResponsePayload<R> = R extends ApiResponse<any, infer P> ? P : never;
7
8
  declare type TResponseStatus<R> = R extends ApiResponse<infer S, any> ? S : never;
8
9
  declare type RouteClient<R> = {
@@ -49,8 +50,11 @@ export declare const loadResponse: (response: Response) => () => Promise<any>;
49
50
  interface MakeApiGateway<F> {
50
51
  <Ru>(config: ConfigProviderForConfig<{
51
52
  apiBase: string;
52
- }>, routes: MapRoutesToConfig<Ru>, authProvider?: {
53
- getAuthorizedFetchConfig: () => Promise<ConfigForFetch<F>>;
53
+ }>, routes: MapRoutesToConfig<Ru>, appProvider?: {
54
+ authProvider?: {
55
+ getAuthorizedFetchConfig: () => Promise<ConfigForFetch<F>>;
56
+ };
57
+ logger?: Logger;
54
58
  }): MapRoutesToClient<Ru>;
55
59
  }
56
60
  export declare const createApiGateway: <F extends GenericFetch<import("../../fetch").FetchConfig, Response>>(initializer: {
@@ -29,10 +29,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.createApiGateway = exports.loadResponse = void 0;
30
30
  const pathToRegexp = __importStar(require("path-to-regexp"));
31
31
  const query_string_1 = __importDefault(require("query-string"));
32
+ const uuid_1 = require("uuid");
32
33
  const __1 = require("../..");
33
34
  const config_1 = require("../../config");
34
35
  const errors_1 = require("../../errors");
35
36
  const fetchStatusRetry_1 = require("../../fetch/fetchStatusRetry");
37
+ const logger_1 = require("../logger");
36
38
  /** Pulls the content out of a response based on the content type */
37
39
  const loadResponse = (response) => () => {
38
40
  const [contentType] = (response.headers.get('content-type') || '').split(';');
@@ -46,7 +48,7 @@ const loadResponse = (response) => () => {
46
48
  }
47
49
  };
48
50
  exports.loadResponse = loadResponse;
49
- const makeRouteClient = (initializer, config, route, authProvider) => {
51
+ const makeRouteClient = (initializer, config, route, appProvider) => {
50
52
  /* TODO this duplicates code with makeRenderRouteUrl, reuse that */
51
53
  const renderUrl = async ({ params, query }) => {
52
54
  const apiBase = await (0, config_1.resolveConfigValue)(config.apiBase);
@@ -55,21 +57,31 @@ const makeRouteClient = (initializer, config, route, authProvider) => {
55
57
  return apiBase.replace(/\/+$/, '') + getPathForParams(params || {}) + (search ? `?${search}` : '');
56
58
  };
57
59
  const routeClient = async ({ params, payload, query, fetchConfig }) => {
60
+ var _a, _b;
61
+ const { fetch } = initializer;
58
62
  const url = await renderUrl({ params, query });
59
63
  const body = payload ? JSON.stringify(payload) : undefined;
60
- const baseOptions = (0, __1.merge)((await (authProvider === null || authProvider === void 0 ? void 0 : authProvider.getAuthorizedFetchConfig())) || {}, fetchConfig || {});
61
- const fetcher = (0, fetchStatusRetry_1.fetchStatusRetry)(initializer.fetch, { retries: 1, status: [502] });
64
+ const baseOptions = (0, __1.merge)((await ((_a = appProvider === null || appProvider === void 0 ? void 0 : appProvider.authProvider) === null || _a === void 0 ? void 0 : _a.getAuthorizedFetchConfig())) || {}, fetchConfig || {});
65
+ const requestId = (0, uuid_1.v4)();
66
+ const requestLogger = (_b = appProvider === null || appProvider === void 0 ? void 0 : appProvider.logger) === null || _b === void 0 ? void 0 : _b.createSubContext();
67
+ requestLogger === null || requestLogger === void 0 ? void 0 : requestLogger.setContext({ requestId, url, timeStamp: new Date().getTime() });
68
+ const fetcher = (0, fetchStatusRetry_1.fetchStatusRetry)(fetch, { retries: 1, status: [502], logger: requestLogger });
69
+ requestLogger === null || requestLogger === void 0 ? void 0 : requestLogger.log('Request Initiated', logger_1.Level.Info);
62
70
  return fetcher(url, (0, __1.merge)(baseOptions, {
63
71
  method: route.method,
64
72
  body,
65
73
  headers: {
66
74
  ...fetchConfig === null || fetchConfig === void 0 ? void 0 : fetchConfig.headers,
67
75
  ...(body ? { 'content-type': 'application/json' } : {}),
76
+ 'X-Request-ID': requestId,
68
77
  }
69
78
  })).then(response => {
70
79
  if (response.status === 401) {
71
80
  throw new errors_1.UnauthorizedError();
72
81
  }
82
+ if (response.status === 403) {
83
+ throw new errors_1.ForbiddenError();
84
+ }
73
85
  if (response.status === 440) {
74
86
  throw new errors_1.SessionExpiredError();
75
87
  }
@@ -89,8 +101,8 @@ const makeRouteClient = (initializer, config, route, authProvider) => {
89
101
  routeClient.renderUrl = renderUrl;
90
102
  return routeClient;
91
103
  };
92
- const createApiGateway = (initializer) => (config, routes, authProvider) => {
104
+ const createApiGateway = (initializer) => (config, routes, appProvider) => {
93
105
  return Object.fromEntries(Object.entries(routes)
94
- .map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig, authProvider)])));
106
+ .map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig, appProvider)])));
95
107
  };
96
108
  exports.createApiGateway = createApiGateway;
@@ -4,16 +4,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.localFileServer = void 0;
7
+ /* cspell:ignore originalname */
7
8
  const fs_1 = __importDefault(require("fs"));
9
+ const https_1 = __importDefault(require("https"));
8
10
  const path_1 = __importDefault(require("path"));
9
- const config_1 = require("../../config");
10
- const guards_1 = require("../../guards");
11
11
  const cors_1 = __importDefault(require("cors"));
12
12
  const express_1 = __importDefault(require("express"));
13
13
  const multer_1 = __importDefault(require("multer"));
14
- const https_1 = __importDefault(require("https"));
15
- const helpers_1 = require("../../misc/helpers");
16
14
  const assertions_1 = require("../../assertions");
15
+ const config_1 = require("../../config");
16
+ const guards_1 = require("../../guards");
17
+ const helpers_1 = require("../../misc/helpers");
17
18
  /* istanbul ignore next */
18
19
  const startServer = (0, helpers_1.once)((port, uploadDir) => {
19
20
  // TODO - re-evaluate the `preservePath` behavior to match whatever s3 does
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.s3FileServer = void 0;
4
+ /* cspell:ignore presigner */
4
5
  const client_s3_1 = require("@aws-sdk/client-s3");
5
6
  const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
6
7
  const __1 = require("../..");
@@ -108,7 +108,7 @@ ${await response.text()}`);
108
108
  const response = await fetchXapiStatements(fetchParams).catch(abort);
109
109
  const consistentThrough = response.headers.get('X-Experience-API-Consistent-Through');
110
110
  if (!consistentThrough || new Date(consistentThrough) < date) {
111
- throw new Error(`xAPI consistent through ${consistentThrough}; not in sync with current date ${date}.`);
111
+ throw new Error(`xAPI consistent through ${consistentThrough}; not in sync with current date ${date.toISOString()}.`);
112
112
  }
113
113
  return formatGetXapiStatementsResponse(response);
114
114
  }, { retries: 4, logger });
@@ -0,0 +1,29 @@
1
+ import { RequestBody } from '@opensearch-project/opensearch/lib/Transport';
2
+ import { ConfigProviderForConfig } from '../config';
3
+ import { IndexOptions, SearchOptions } from './searchProvider';
4
+ export declare type Config = {
5
+ node: string;
6
+ region: string;
7
+ };
8
+ export interface Initializer<C> {
9
+ configSpace?: C;
10
+ }
11
+ export declare type IndexConfig = {
12
+ name: string;
13
+ mappings: Record<string, any>;
14
+ pageSize?: number;
15
+ };
16
+ export declare const openSearchService: <T extends RequestBody<Record<string, any>>, C extends string = "deployed">(initializer?: Initializer<C>) => (indexConfig: IndexConfig, configProvider: { [key in C]: {
17
+ node: import("../config").ConfigValueProvider<string>;
18
+ region: import("../config").ConfigValueProvider<string>;
19
+ }; }) => {
20
+ ensureIndexCreated: () => Promise<void>;
21
+ index: (params: IndexOptions<T>) => Promise<void>;
22
+ search: (options: SearchOptions) => Promise<{
23
+ items: Exclude<T, undefined>[];
24
+ pageSize: number;
25
+ currentPage: number;
26
+ totalItems: number;
27
+ totalPages: number;
28
+ }>;
29
+ };
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openSearchService = void 0;
4
+ // cspell:ignore opensearch, Sigv
5
+ const credential_provider_node_1 = require("@aws-sdk/credential-provider-node");
6
+ const opensearch_1 = require("@opensearch-project/opensearch");
7
+ const aws_1 = require("@opensearch-project/opensearch/aws");
8
+ const config_1 = require("../config");
9
+ const guards_1 = require("../guards");
10
+ const helpers_1 = require("../misc/helpers");
11
+ const openSearchService = (initializer = {}) => (indexConfig, configProvider) => {
12
+ const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'deployed')];
13
+ const pageSize = indexConfig.pageSize || 10;
14
+ const client = (0, helpers_1.once)(async () => new opensearch_1.Client({
15
+ ...(0, aws_1.AwsSigv4Signer)({
16
+ getCredentials: () => (0, credential_provider_node_1.defaultProvider)()(),
17
+ region: await (0, config_1.resolveConfigValue)(config.region),
18
+ service: 'es',
19
+ }),
20
+ node: await (0, config_1.resolveConfigValue)(config.node),
21
+ }));
22
+ const createIndexIfNotExists = async (indices, params) => {
23
+ const { index } = params;
24
+ const { body } = await indices.exists({ index });
25
+ if (!body) {
26
+ await indices.create(params);
27
+ }
28
+ };
29
+ const ensureIndexCreated = async () => {
30
+ const { indices } = await client();
31
+ await createIndexIfNotExists(indices, {
32
+ index: indexConfig.name,
33
+ body: {
34
+ mappings: {
35
+ dynamic: false,
36
+ properties: indexConfig.mappings
37
+ }
38
+ }
39
+ });
40
+ };
41
+ const index = async (params) => {
42
+ const openSearchClient = await client();
43
+ await openSearchClient.index({
44
+ index: indexConfig.name,
45
+ body: params.body,
46
+ id: params.id,
47
+ refresh: true
48
+ });
49
+ };
50
+ const search = async (options) => {
51
+ const body = { query: { bool: {} } };
52
+ if (options.query) {
53
+ body.query.bool.must = {
54
+ multi_match: {
55
+ fields: options.fields.map((field) => 'weight' in field ? `${field.key}^${field.weight}` : field.key),
56
+ query: options.query
57
+ }
58
+ };
59
+ }
60
+ const { must_not } = options;
61
+ const must = 'filter' in options ? options.filter : options.must;
62
+ if (must && must.length > 0) {
63
+ body.query.bool.filter = [];
64
+ must.forEach((filter) => {
65
+ const { key } = filter;
66
+ const values = filter.value instanceof Array ? filter.value : [filter.value];
67
+ body.query.bool.filter.push({ terms: { [key]: values } });
68
+ });
69
+ }
70
+ if (must_not && must_not.length > 0) {
71
+ body.query.bool.must_not = [];
72
+ must_not.forEach((filter) => {
73
+ const { key } = filter;
74
+ const values = filter.value instanceof Array ? filter.value : [filter.value];
75
+ values.forEach((value) => body.query.bool.must_not.push({ term: { [key]: value } }));
76
+ });
77
+ }
78
+ if (options.should && options.should.length > 0) {
79
+ body.query.bool.should = options.should.map(term => {
80
+ const { key } = term;
81
+ const values = term.value instanceof Array ? term.value : [term.value];
82
+ return { terms: { [key]: values } };
83
+ });
84
+ body.query.bool.minimum_should_match = 1;
85
+ }
86
+ if (options.page) {
87
+ body.size = pageSize;
88
+ body.from = (options.page - 1) * pageSize;
89
+ }
90
+ const response = await (await client()).search({
91
+ body,
92
+ index: indexConfig.name
93
+ });
94
+ if (response.statusCode !== 200) {
95
+ throw new Error(`Unexpected status code: ${response.statusCode} from OpenSearch`);
96
+ }
97
+ const hits = response.body.hits;
98
+ const items = hits.hits.map((hit) => hit._source).filter(guards_1.isDefined);
99
+ const currentPage = options.page || 1;
100
+ const { total } = hits;
101
+ const totalItems = typeof total === 'number' ? total : total.value;
102
+ const totalPages = Math.ceil(totalItems / pageSize) || 1;
103
+ return { items, pageSize, currentPage, totalItems, totalPages };
104
+ };
105
+ return { ensureIndexCreated, index, search };
106
+ };
107
+ exports.openSearchService = openSearchService;
@@ -27,5 +27,6 @@ export interface SearchOptions {
27
27
  filter?: Filter[];
28
28
  must?: Filter[];
29
29
  must_not?: Filter[];
30
+ should?: Filter[];
30
31
  }
31
32
  export {};
@@ -10,6 +10,7 @@ var MatchType;
10
10
  (function (MatchType) {
11
11
  MatchType[MatchType["Must"] = 0] = "Must";
12
12
  MatchType[MatchType["MustNot"] = 1] = "MustNot";
13
+ MatchType[MatchType["Should"] = 2] = "Should";
13
14
  })(MatchType || (MatchType = {}));
14
15
  const resolveField = (document, field) => field.key.toString().split('.').reduce((result, key) => result[key], document);
15
16
  const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
@@ -21,7 +22,7 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
21
22
  const results = (await loadAllDocumentsTheBadWay())
22
23
  .map(document => {
23
24
  let weight = 0;
24
- const matchFilters = (filters, mustMatch) => {
25
+ const matchFilters = (filters, matchType) => {
25
26
  for (const field of filters) {
26
27
  const docValues = (0, __1.coerceArray)(resolveField(document, field));
27
28
  const coerceValue = getFieldType(field) === 'boolean'
@@ -36,11 +37,14 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
36
37
  }
37
38
  : (x) => x;
38
39
  const hasMatch = (0, __1.coerceArray)(field.value).map(coerceValue).some(v => docValues.includes(v));
39
- if ((mustMatch === MatchType.Must && !hasMatch) || (mustMatch === MatchType.MustNot && hasMatch)) {
40
+ if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
40
41
  return false;
41
42
  }
43
+ else if (matchType === MatchType.Should && hasMatch) {
44
+ return true;
45
+ }
42
46
  }
43
- return true;
47
+ return matchType !== MatchType.Should;
44
48
  };
45
49
  if (options.query !== undefined) {
46
50
  for (const field of options.fields) {
@@ -61,7 +65,7 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
61
65
  }
62
66
  }
63
67
  }
64
- const { must_not } = options;
68
+ const { must_not, should } = options;
65
69
  const must = 'filter' in options ? options.filter : options.must;
66
70
  if ((must === null || must === void 0 ? void 0 : must.length) && !matchFilters(must, MatchType.Must)) {
67
71
  return undefined;
@@ -69,6 +73,9 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
69
73
  if ((must_not === null || must_not === void 0 ? void 0 : must_not.length) && !matchFilters(must_not, MatchType.MustNot)) {
70
74
  return undefined;
71
75
  }
76
+ if ((should === null || should === void 0 ? void 0 : should.length) && !matchFilters(should, MatchType.Should)) {
77
+ return undefined;
78
+ }
72
79
  return { document, weight };
73
80
  })
74
81
  .filter(guards_1.isDefined)