@openstax/ts-utils 1.25.4 → 1.26.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/services/apiGateway/index.d.ts +2 -1
- package/dist/cjs/services/apiGateway/index.js +9 -6
- package/dist/cjs/services/launchParams/verifier.d.ts +3 -3
- package/dist/cjs/services/launchParams/verifier.js +45 -30
- package/dist/cjs/services/searchProvider/index.d.ts +8 -1
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +40 -18
- package/dist/cjs/services/searchProvider/openSearch.js +16 -20
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/services/apiGateway/index.d.ts +2 -1
- package/dist/esm/services/apiGateway/index.js +9 -6
- package/dist/esm/services/launchParams/verifier.d.ts +3 -3
- package/dist/esm/services/launchParams/verifier.js +46 -31
- package/dist/esm/services/searchProvider/index.d.ts +8 -1
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +40 -18
- package/dist/esm/services/searchProvider/openSearch.js +16 -20
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -50,11 +50,12 @@ export declare const loadResponse: (response: Response) => () => Promise<any>;
|
|
|
50
50
|
interface MakeApiGateway<F> {
|
|
51
51
|
<Ru>(config: ConfigProviderForConfig<{
|
|
52
52
|
apiBase: string;
|
|
53
|
-
}>, routes: MapRoutesToConfig<Ru>,
|
|
53
|
+
}>, routes: MapRoutesToConfig<Ru>, app?: {
|
|
54
54
|
authProvider?: {
|
|
55
55
|
getAuthorizedFetchConfig: () => Promise<ConfigForFetch<F>>;
|
|
56
56
|
};
|
|
57
57
|
logger?: Logger;
|
|
58
|
+
launchToken?: string;
|
|
58
59
|
}): MapRoutesToClient<Ru>;
|
|
59
60
|
}
|
|
60
61
|
export declare const createApiGateway: <F extends GenericFetch<import("../../fetch").FetchConfig, Response>>(initializer: {
|
|
@@ -48,7 +48,7 @@ const loadResponse = (response) => () => {
|
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
exports.loadResponse = loadResponse;
|
|
51
|
-
const makeRouteClient = (initializer, config, route,
|
|
51
|
+
const makeRouteClient = (initializer, config, route, app) => {
|
|
52
52
|
/* TODO this duplicates code with makeRenderRouteUrl, reuse that */
|
|
53
53
|
const renderUrl = async ({ params, query }) => {
|
|
54
54
|
const apiBase = await (0, config_1.resolveConfigValue)(config.apiBase);
|
|
@@ -61,9 +61,9 @@ const makeRouteClient = (initializer, config, route, appProvider) => {
|
|
|
61
61
|
const { fetch } = initializer;
|
|
62
62
|
const url = await renderUrl({ params, query });
|
|
63
63
|
const body = payload ? JSON.stringify(payload) : undefined;
|
|
64
|
-
const baseOptions = (0, __1.merge)((await ((_a =
|
|
64
|
+
const baseOptions = (0, __1.merge)((await ((_a = app === null || app === void 0 ? void 0 : app.authProvider) === null || _a === void 0 ? void 0 : _a.getAuthorizedFetchConfig())) || {}, fetchConfig || {});
|
|
65
65
|
const requestId = (0, uuid_1.v4)();
|
|
66
|
-
const requestLogger = (_b =
|
|
66
|
+
const requestLogger = (_b = app === null || app === void 0 ? void 0 : app.logger) === null || _b === void 0 ? void 0 : _b.createSubContext();
|
|
67
67
|
requestLogger === null || requestLogger === void 0 ? void 0 : requestLogger.setContext({ requestId, url, timeStamp: new Date().getTime() });
|
|
68
68
|
const fetcher = (0, fetchStatusRetry_1.fetchStatusRetry)(fetch, { retries: 1, status: [502], logger: requestLogger });
|
|
69
69
|
requestLogger === null || requestLogger === void 0 ? void 0 : requestLogger.log('Request Initiated', logger_1.Level.Info);
|
|
@@ -73,7 +73,10 @@ const makeRouteClient = (initializer, config, route, appProvider) => {
|
|
|
73
73
|
headers: {
|
|
74
74
|
...fetchConfig === null || fetchConfig === void 0 ? void 0 : fetchConfig.headers,
|
|
75
75
|
...(body ? { 'content-type': 'application/json' } : {}),
|
|
76
|
-
'
|
|
76
|
+
'x-request-id': requestId,
|
|
77
|
+
...((app === null || app === void 0 ? void 0 : app.launchToken) ? {
|
|
78
|
+
'x-launch-token': app.launchToken,
|
|
79
|
+
} : {}),
|
|
77
80
|
}
|
|
78
81
|
})).then(response => {
|
|
79
82
|
if (response.status === 401) {
|
|
@@ -98,8 +101,8 @@ const makeRouteClient = (initializer, config, route, appProvider) => {
|
|
|
98
101
|
routeClient.renderUrl = renderUrl;
|
|
99
102
|
return routeClient;
|
|
100
103
|
};
|
|
101
|
-
const createApiGateway = (initializer) => (config, routes,
|
|
104
|
+
const createApiGateway = (initializer) => (config, routes, app) => {
|
|
102
105
|
return Object.fromEntries(Object.entries(routes)
|
|
103
|
-
.map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig,
|
|
106
|
+
.map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig, app)])));
|
|
104
107
|
};
|
|
105
108
|
exports.createApiGateway = createApiGateway;
|
|
@@ -15,8 +15,8 @@ interface Initializer<C> {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const createLaunchVerifier: <C extends string = "launch">({ configSpace, fetcher }: Initializer<C>) => (configProvider: { [key in C]: {
|
|
17
17
|
trustedDomain: import("../../config").ConfigValueProvider<string>;
|
|
18
|
-
}; }) => {
|
|
19
|
-
verify: <T = undefined>(...
|
|
18
|
+
}; }) => (_services: {}, getDefaultToken?: (() => string) | undefined) => {
|
|
19
|
+
verify: <T = undefined>(...args: T extends undefined ? [] | [string] : [(input: any) => T] | [string, (input: any) => T]) => Promise<T extends undefined ? jwt.JwtPayload : T>;
|
|
20
20
|
};
|
|
21
|
-
export declare type LaunchVerifier = ReturnType<ReturnType<typeof createLaunchVerifier
|
|
21
|
+
export declare type LaunchVerifier = ReturnType<ReturnType<ReturnType<typeof createLaunchVerifier>>>;
|
|
22
22
|
export {};
|
|
@@ -60,35 +60,50 @@ const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvider) => {
|
|
|
60
60
|
callback(error);
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
63
|
+
return (_services, getDefaultToken) => {
|
|
64
|
+
const verify = (...args) => {
|
|
65
|
+
const [inputToken, validator] = args.length === 1
|
|
66
|
+
? typeof args[0] === 'string'
|
|
67
|
+
? [args[0], undefined]
|
|
68
|
+
: [undefined, args[0]]
|
|
69
|
+
: args;
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const token = inputToken !== null && inputToken !== void 0 ? inputToken : getDefaultToken === null || getDefaultToken === void 0 ? void 0 : getDefaultToken();
|
|
72
|
+
if (!token) {
|
|
73
|
+
return reject(new errors_1.InvalidRequestError('Missing token for launch verification'));
|
|
74
|
+
}
|
|
75
|
+
return jsonwebtoken_1.default.verify(token, getKey, {}, (err, payload) => {
|
|
76
|
+
if (err && err instanceof jsonwebtoken_1.TokenExpiredError) {
|
|
77
|
+
reject(new errors_1.SessionExpiredError());
|
|
78
|
+
}
|
|
79
|
+
else if (err) {
|
|
80
|
+
reject(err);
|
|
81
|
+
}
|
|
82
|
+
else if (typeof payload !== 'object') {
|
|
83
|
+
reject(new Error('received JWT token with unexpected non-JSON payload'));
|
|
84
|
+
}
|
|
85
|
+
else if (!payload.sub) {
|
|
86
|
+
reject(new Error('JWT payload missing sub claim'));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// we are migrating away from json encoding all the parameters into the `sub` claim
|
|
90
|
+
// and into using separate claims for each parameter. in transition, we check if the sub
|
|
91
|
+
// is json and return it if it is. this is still a breaking change when using this
|
|
92
|
+
// utility because applications no longer need to independently json parse the result
|
|
93
|
+
// starting now.
|
|
94
|
+
const parsed = payload;
|
|
95
|
+
try {
|
|
96
|
+
const jsonSubContents = JSON.parse(payload.sub);
|
|
97
|
+
Object.assign(parsed, jsonSubContents);
|
|
98
|
+
}
|
|
99
|
+
catch (e) { } // eslint-disable-line no-empty
|
|
100
|
+
// conditional return types are annoying
|
|
101
|
+
resolve((validator ? validator(parsed) : parsed));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
return { verify };
|
|
107
|
+
};
|
|
93
108
|
};
|
|
94
109
|
exports.createLaunchVerifier = createLaunchVerifier;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export declare type FieldType = string | string[] | number | boolean;
|
|
1
2
|
export declare type Filter = {
|
|
2
3
|
key: string;
|
|
3
|
-
value:
|
|
4
|
+
value: FieldType;
|
|
5
|
+
} | {
|
|
6
|
+
terms: Record<string, FieldType>;
|
|
7
|
+
} | {
|
|
8
|
+
exists: {
|
|
9
|
+
field: string;
|
|
10
|
+
};
|
|
4
11
|
};
|
|
5
12
|
declare type Field = {
|
|
6
13
|
key: string;
|
|
@@ -12,7 +12,33 @@ var MatchType;
|
|
|
12
12
|
MatchType[MatchType["MustNot"] = 1] = "MustNot";
|
|
13
13
|
MatchType[MatchType["Should"] = 2] = "Should";
|
|
14
14
|
})(MatchType || (MatchType = {}));
|
|
15
|
-
const resolveField = (document, field) => field.
|
|
15
|
+
const resolveField = (document, field) => field.split('.').reduce((result, key) => result[key], document);
|
|
16
|
+
const matchExists = (exists, document) => {
|
|
17
|
+
const value = resolveField(document, exists.field);
|
|
18
|
+
return value !== undefined && value !== null;
|
|
19
|
+
};
|
|
20
|
+
const matchTerms = (options, terms, document) => {
|
|
21
|
+
const getFieldType = (field) => { var _a; return (_a = options.fields.find(f => f.key == field)) === null || _a === void 0 ? void 0 : _a.type; };
|
|
22
|
+
for (const key in terms) {
|
|
23
|
+
const docValues = (0, __1.coerceArray)(resolveField(document, key));
|
|
24
|
+
const coerceValue = getFieldType(key) === 'boolean'
|
|
25
|
+
? (input) => {
|
|
26
|
+
if ([true, 'true', '1', 1].includes(input)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if ([false, 'false', '0', 0, ''].includes(input)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
throw new errors_1.InvalidRequestError('input is not a valid boolean filter');
|
|
33
|
+
}
|
|
34
|
+
: (x) => x;
|
|
35
|
+
const hasMatch = (0, __1.coerceArray)(terms[key]).map(coerceValue).some(v => docValues.includes(v));
|
|
36
|
+
if (!hasMatch) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
16
42
|
const memorySearchTheBadWay = () => ({ store }) => {
|
|
17
43
|
return {
|
|
18
44
|
// This method is intentionally stubbed because index deletion is not applicable for in-memory storage.
|
|
@@ -20,25 +46,21 @@ const memorySearchTheBadWay = () => ({ store }) => {
|
|
|
20
46
|
ensureIndexCreated: async () => undefined,
|
|
21
47
|
index: async (_options) => undefined,
|
|
22
48
|
search: async (options) => {
|
|
23
|
-
const getFieldType = (field) => { var _a; return (_a = options.fields.find(f => f.key == field.key)) === null || _a === void 0 ? void 0 : _a.type; };
|
|
24
49
|
const results = (await store.loadAllDocumentsTheBadWay())
|
|
25
50
|
.map(document => {
|
|
26
51
|
let weight = 0;
|
|
27
52
|
const matchFilters = (filters, matchType) => {
|
|
28
53
|
for (const field of filters) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
: (x) => x;
|
|
41
|
-
const hasMatch = (0, __1.coerceArray)(field.value).map(coerceValue).some(v => docValues.includes(v));
|
|
54
|
+
const filter = ('key' in field && 'value' in field)
|
|
55
|
+
? { terms: { [field.key]: field.value } }
|
|
56
|
+
: field;
|
|
57
|
+
let hasMatch;
|
|
58
|
+
if ('terms' in filter)
|
|
59
|
+
hasMatch = matchTerms(options, filter.terms, document);
|
|
60
|
+
else if ('exists' in filter)
|
|
61
|
+
hasMatch = matchExists(filter.exists, document);
|
|
62
|
+
else
|
|
63
|
+
throw new errors_1.InvalidRequestError('invalid filter type');
|
|
42
64
|
if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
|
|
43
65
|
return false;
|
|
44
66
|
}
|
|
@@ -53,7 +75,7 @@ const memorySearchTheBadWay = () => ({ store }) => {
|
|
|
53
75
|
if (field.type !== undefined && field.type !== 'text') {
|
|
54
76
|
continue;
|
|
55
77
|
}
|
|
56
|
-
const value = resolveField(document, field);
|
|
78
|
+
const value = resolveField(document, field.key);
|
|
57
79
|
if (value === undefined || value === null) {
|
|
58
80
|
continue;
|
|
59
81
|
}
|
|
@@ -84,8 +106,8 @@ const memorySearchTheBadWay = () => ({ store }) => {
|
|
|
84
106
|
.filter(r => !options.query || r.weight >= MIN_MATCH);
|
|
85
107
|
results.sort((a, b) => {
|
|
86
108
|
for (const sort of (options.sort || [])) {
|
|
87
|
-
const aValue = resolveField(a.document,
|
|
88
|
-
const bValue = resolveField(b.document,
|
|
109
|
+
const aValue = resolveField(a.document, sort.key);
|
|
110
|
+
const bValue = resolveField(b.document, sort.key);
|
|
89
111
|
if (aValue < bValue) {
|
|
90
112
|
return sort.order === 'asc' ? -1 : 1;
|
|
91
113
|
}
|
|
@@ -8,6 +8,16 @@ const aws_1 = require("@opensearch-project/opensearch/aws");
|
|
|
8
8
|
const config_1 = require("../../config");
|
|
9
9
|
const guards_1 = require("../../guards");
|
|
10
10
|
const helpers_1 = require("../../misc/helpers");
|
|
11
|
+
const mapFilter = (filter) => {
|
|
12
|
+
if ('key' in filter && 'value' in filter) {
|
|
13
|
+
const { key } = filter;
|
|
14
|
+
const values = filter.value instanceof Array ? filter.value : [filter.value];
|
|
15
|
+
return { terms: { [key]: values } };
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return filter;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
11
21
|
const openSearchService = (initializer = {}) => (configProvider) => {
|
|
12
22
|
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'deployed')];
|
|
13
23
|
const client = (0, helpers_1.once)(async () => new opensearch_1.Client({
|
|
@@ -57,6 +67,7 @@ const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
57
67
|
const body = {
|
|
58
68
|
query: { bool: {} },
|
|
59
69
|
track_total_hits: true,
|
|
70
|
+
size: pageSize
|
|
60
71
|
};
|
|
61
72
|
if (options.query) {
|
|
62
73
|
body.query.bool.must = {
|
|
@@ -66,30 +77,16 @@ const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
66
77
|
}
|
|
67
78
|
};
|
|
68
79
|
}
|
|
69
|
-
const { must_not } = options;
|
|
80
|
+
const { must_not, should } = options;
|
|
70
81
|
const must = 'filter' in options ? options.filter : options.must;
|
|
71
82
|
if (must && must.length > 0) {
|
|
72
|
-
body.query.bool.filter =
|
|
73
|
-
must.forEach((filter) => {
|
|
74
|
-
const { key } = filter;
|
|
75
|
-
const values = filter.value instanceof Array ? filter.value : [filter.value];
|
|
76
|
-
body.query.bool.filter.push({ terms: { [key]: values } });
|
|
77
|
-
});
|
|
83
|
+
body.query.bool.filter = must.map(mapFilter);
|
|
78
84
|
}
|
|
79
85
|
if (must_not && must_not.length > 0) {
|
|
80
|
-
body.query.bool.must_not =
|
|
81
|
-
must_not.forEach((filter) => {
|
|
82
|
-
const { key } = filter;
|
|
83
|
-
const values = filter.value instanceof Array ? filter.value : [filter.value];
|
|
84
|
-
values.forEach((value) => body.query.bool.must_not.push({ term: { [key]: value } }));
|
|
85
|
-
});
|
|
86
|
+
body.query.bool.must_not = must_not.map(mapFilter);
|
|
86
87
|
}
|
|
87
|
-
if (
|
|
88
|
-
body.query.bool.should =
|
|
89
|
-
const { key } = term;
|
|
90
|
-
const values = term.value instanceof Array ? term.value : [term.value];
|
|
91
|
-
return { terms: { [key]: values } };
|
|
92
|
-
});
|
|
88
|
+
if (should && should.length > 0) {
|
|
89
|
+
body.query.bool.should = should.map(mapFilter);
|
|
93
90
|
body.query.bool.minimum_should_match = 1;
|
|
94
91
|
}
|
|
95
92
|
if (options.sort && options.sort.length > 0) {
|
|
@@ -98,7 +95,6 @@ const openSearchService = (initializer = {}) => (configProvider) => {
|
|
|
98
95
|
}));
|
|
99
96
|
}
|
|
100
97
|
if (options.page) {
|
|
101
|
-
body.size = pageSize;
|
|
102
98
|
body.from = (options.page - 1) * pageSize;
|
|
103
99
|
}
|
|
104
100
|
const response = await (await client()).search({
|