@openstax/ts-utils 1.25.5 → 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.
@@ -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>, appProvider?: {
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, appProvider) => {
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 = appProvider === null || appProvider === void 0 ? void 0 : appProvider.authProvider) === null || _a === void 0 ? void 0 : _a.getAuthorizedFetchConfig())) || {}, fetchConfig || {});
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 = appProvider === null || appProvider === void 0 ? void 0 : appProvider.logger) === null || _b === void 0 ? void 0 : _b.createSubContext();
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
- 'X-Request-ID': requestId,
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, appProvider) => {
104
+ const createApiGateway = (initializer) => (config, routes, app) => {
102
105
  return Object.fromEntries(Object.entries(routes)
103
- .map(([key, routeConfig]) => ([key, makeRouteClient(initializer, config, routeConfig, appProvider)])));
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>(...[token, validator]: T extends undefined ? [string] : [string, (input: any) => T]) => Promise<T extends undefined ? jwt.JwtPayload : T>;
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
- const verify = (...[token, validator]) => new Promise((resolve, reject) => jsonwebtoken_1.default.verify(token, getKey, {}, (err, payload) => {
64
- if (err && err instanceof jsonwebtoken_1.TokenExpiredError) {
65
- reject(new errors_1.SessionExpiredError());
66
- }
67
- else if (err) {
68
- reject(err);
69
- }
70
- else if (typeof payload !== 'object') {
71
- reject(new Error('received JWT token with unexpected non-JSON payload'));
72
- }
73
- else if (!payload.sub) {
74
- reject(new Error('JWT payload missing sub claim'));
75
- }
76
- else {
77
- // we are migrating away from json encoding all the parameters into the `sub` claim
78
- // and into using separate claims for each parameter. in transition, we check if the sub
79
- // is json and return it if it is. this is still a breaking change when using this
80
- // utility because applications no longer need to independently json parse the result
81
- // starting now.
82
- const parsed = payload;
83
- try {
84
- const jsonSubContents = JSON.parse(payload.sub);
85
- Object.assign(parsed, jsonSubContents);
86
- }
87
- catch (e) { } // eslint-disable-line no-empty
88
- // conditional return types are annoying
89
- resolve((validator ? validator(parsed) : parsed));
90
- }
91
- }));
92
- return { verify };
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,8 +1,9 @@
1
+ export declare type FieldType = string | string[] | number | boolean;
1
2
  export declare type Filter = {
2
3
  key: string;
3
- value: string | string[] | boolean;
4
+ value: FieldType;
4
5
  } | {
5
- terms: Record<string, string | string[] | number | boolean>;
6
+ terms: Record<string, FieldType>;
6
7
  } | {
7
8
  exists: {
8
9
  field: 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.key.toString().split('.').reduce((result, key) => result[key], document);
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,29 +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
- if (!('key' in field && 'value' in field)) {
30
- console.warn('local search only supports key/value filters');
31
- continue;
32
- }
33
- const docValues = (0, __1.coerceArray)(resolveField(document, field));
34
- const coerceValue = getFieldType(field) === 'boolean'
35
- ? (input) => {
36
- if ([true, 'true', '1', 1].includes(input)) {
37
- return true;
38
- }
39
- if ([false, 'false', '0', 0, ''].includes(input)) {
40
- return false;
41
- }
42
- throw new errors_1.InvalidRequestError('input is not a valid boolean filter');
43
- }
44
- : (x) => x;
45
- 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');
46
64
  if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
47
65
  return false;
48
66
  }
@@ -57,7 +75,7 @@ const memorySearchTheBadWay = () => ({ store }) => {
57
75
  if (field.type !== undefined && field.type !== 'text') {
58
76
  continue;
59
77
  }
60
- const value = resolveField(document, field);
78
+ const value = resolveField(document, field.key);
61
79
  if (value === undefined || value === null) {
62
80
  continue;
63
81
  }
@@ -88,8 +106,8 @@ const memorySearchTheBadWay = () => ({ store }) => {
88
106
  .filter(r => !options.query || r.weight >= MIN_MATCH);
89
107
  results.sort((a, b) => {
90
108
  for (const sort of (options.sort || [])) {
91
- const aValue = resolveField(a.document, { key: sort.key });
92
- const bValue = resolveField(b.document, { key: sort.key });
109
+ const aValue = resolveField(a.document, sort.key);
110
+ const bValue = resolveField(b.document, sort.key);
93
111
  if (aValue < bValue) {
94
112
  return sort.order === 'asc' ? -1 : 1;
95
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({
@@ -67,45 +77,16 @@ const openSearchService = (initializer = {}) => (configProvider) => {
67
77
  }
68
78
  };
69
79
  }
70
- const { must_not } = options;
80
+ const { must_not, should } = options;
71
81
  const must = 'filter' in options ? options.filter : options.must;
72
82
  if (must && must.length > 0) {
73
- body.query.bool.filter = [];
74
- must.forEach((filter) => {
75
- if ('key' in filter && 'value' in filter) {
76
- const { key } = filter;
77
- const values = filter.value instanceof Array ? filter.value : [filter.value];
78
- body.query.bool.filter.push({ terms: { [key]: values } });
79
- }
80
- else {
81
- body.query.bool.filter.push(filter);
82
- }
83
- });
83
+ body.query.bool.filter = must.map(mapFilter);
84
84
  }
85
85
  if (must_not && must_not.length > 0) {
86
- body.query.bool.must_not = [];
87
- must_not.forEach((filter) => {
88
- if ('key' in filter && 'value' in filter) {
89
- const { key } = filter;
90
- const values = filter.value instanceof Array ? filter.value : [filter.value];
91
- values.forEach((value) => body.query.bool.must_not.push({ term: { [key]: value } }));
92
- }
93
- else {
94
- body.query.bool.must_not.push(filter);
95
- }
96
- });
86
+ body.query.bool.must_not = must_not.map(mapFilter);
97
87
  }
98
- if (options.should && options.should.length > 0) {
99
- body.query.bool.should = options.should.map(filter => {
100
- if ('key' in filter && 'value' in filter) {
101
- const { key } = filter;
102
- const values = filter.value instanceof Array ? filter.value : [filter.value];
103
- return { terms: { [key]: values } };
104
- }
105
- else {
106
- return filter;
107
- }
108
- });
88
+ if (should && should.length > 0) {
89
+ body.query.bool.should = should.map(mapFilter);
109
90
  body.query.bool.minimum_should_match = 1;
110
91
  }
111
92
  if (options.sort && options.sort.length > 0) {