@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.
- package/dist/cjs/errors/index.d.ts +11 -0
- package/dist/cjs/errors/index.js +15 -1
- package/dist/cjs/middleware/apiErrorHandler.d.ts +4 -3
- package/dist/cjs/middleware/apiErrorHandler.js +4 -3
- package/dist/cjs/misc/helpers.js +1 -1
- package/dist/cjs/services/accountsGateway/index.js +3 -4
- package/dist/cjs/services/apiGateway/index.d.ts +6 -2
- package/dist/cjs/services/apiGateway/index.js +17 -5
- package/dist/cjs/services/fileServer/localFileServer.js +5 -4
- package/dist/cjs/services/fileServer/s3FileServer.js +1 -0
- package/dist/cjs/services/lrsGateway/index.js +1 -1
- package/dist/cjs/services/openSearch.d.ts +29 -0
- package/dist/cjs/services/openSearch.js +107 -0
- package/dist/cjs/services/searchProvider/index.d.ts +1 -0
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +11 -4
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/errors/index.d.ts +11 -0
- package/dist/esm/errors/index.js +13 -0
- package/dist/esm/middleware/apiErrorHandler.d.ts +4 -3
- package/dist/esm/middleware/apiErrorHandler.js +5 -4
- package/dist/esm/misc/helpers.js +1 -1
- package/dist/esm/services/accountsGateway/index.js +3 -4
- package/dist/esm/services/apiGateway/index.d.ts +6 -2
- package/dist/esm/services/apiGateway/index.js +18 -6
- package/dist/esm/services/fileServer/localFileServer.js +6 -5
- package/dist/esm/services/fileServer/s3FileServer.js +1 -0
- package/dist/esm/services/lrsGateway/index.js +1 -1
- package/dist/esm/services/openSearch.d.ts +29 -0
- package/dist/esm/services/openSearch.js +103 -0
- package/dist/esm/services/searchProvider/index.d.ts +1 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +11 -4
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +3 -1
- 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
|
@@ -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
|
*
|
package/dist/cjs/errors/index.js
CHANGED
|
@@ -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
|
-
|
|
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`,
|
package/dist/cjs/misc/helpers.js
CHANGED
|
@@ -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:
|
|
29
|
-
|
|
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>,
|
|
53
|
-
|
|
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,
|
|
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 (
|
|
61
|
-
const
|
|
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,
|
|
104
|
+
const createApiGateway = (initializer) => (config, routes, appProvider) => {
|
|
93
105
|
return Object.fromEntries(Object.entries(routes)
|
|
94
|
-
.map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig,
|
|
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;
|
|
@@ -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,
|
|
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 ((
|
|
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
|
|
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)
|