@openstax/ts-utils 1.26.2 → 1.27.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 (45) hide show
  1. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +4 -2
  2. package/dist/cjs/services/accountsGateway/index.js +5 -4
  3. package/dist/cjs/services/authProvider/decryption.js +3 -1
  4. package/dist/cjs/services/authProvider/index.d.ts +2 -1
  5. package/dist/cjs/services/authProvider/index.js +4 -1
  6. package/dist/cjs/services/authProvider/subrequest.js +2 -0
  7. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +6 -0
  8. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +145 -112
  9. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +4 -0
  10. package/dist/cjs/services/documentStore/unversioned/file-system.js +19 -7
  11. package/dist/cjs/services/documentStore/versioned/dynamodb.d.ts +2 -0
  12. package/dist/cjs/services/documentStore/versioned/dynamodb.js +125 -123
  13. package/dist/cjs/services/fileServer/s3FileServer.d.ts +1 -1
  14. package/dist/cjs/services/fileServer/s3FileServer.js +5 -2
  15. package/dist/cjs/services/launchParams/verifier.js +3 -1
  16. package/dist/cjs/services/searchProvider/index.d.ts +26 -7
  17. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +5 -3
  18. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +97 -41
  19. package/dist/cjs/services/searchProvider/openSearch.d.ts +2 -2
  20. package/dist/cjs/services/searchProvider/openSearch.js +13 -9
  21. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  22. package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +4 -2
  23. package/dist/esm/services/accountsGateway/index.js +5 -4
  24. package/dist/esm/services/authProvider/decryption.js +3 -1
  25. package/dist/esm/services/authProvider/index.d.ts +2 -1
  26. package/dist/esm/services/authProvider/index.js +4 -1
  27. package/dist/esm/services/authProvider/subrequest.js +2 -0
  28. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +6 -0
  29. package/dist/esm/services/documentStore/unversioned/dynamodb.js +146 -113
  30. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +4 -0
  31. package/dist/esm/services/documentStore/unversioned/file-system.js +19 -7
  32. package/dist/esm/services/documentStore/versioned/dynamodb.d.ts +2 -0
  33. package/dist/esm/services/documentStore/versioned/dynamodb.js +125 -123
  34. package/dist/esm/services/fileServer/s3FileServer.d.ts +1 -1
  35. package/dist/esm/services/fileServer/s3FileServer.js +5 -2
  36. package/dist/esm/services/launchParams/verifier.js +3 -1
  37. package/dist/esm/services/searchProvider/index.d.ts +26 -7
  38. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +5 -3
  39. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +97 -41
  40. package/dist/esm/services/searchProvider/openSearch.d.ts +2 -2
  41. package/dist/esm/services/searchProvider/openSearch.js +13 -9
  42. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/dist/cjs/coolFile.d.ts +0 -1
  45. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -3,135 +3,137 @@ import { once } from '../../..';
3
3
  import { resolveConfigValue } from '../../../config';
4
4
  import { ifDefined } from '../../../guards';
5
5
  import { decodeDynamoDocument, encodeDynamoAttribute, encodeDynamoDocument } from '../dynamoEncoding';
6
- const dynamodb = once(() => new DynamoDB({ apiVersion: '2012-08-10' }));
7
6
  // i'm not really excited about getAuthor being required, but ts is getting confused about the type when unspecified
8
- export const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
7
+ export const dynamoVersionedDocumentStore = (initializer) => {
9
8
  const init = ifDefined(initializer, {});
10
- const tableName = once(() => resolveConfigValue(configProvider[ifDefined(init.configSpace, 'dynamodb')].tableName));
11
- return {
12
- loadAllDocumentsTheBadWay: async () => {
13
- const loadAllResults = async (ExclusiveStartKey) => {
14
- var _a;
15
- const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
16
- const result = await dynamodb().send(cmd);
17
- const resultItems = ((_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)) || [];
18
- if (result.LastEvaluatedKey) {
19
- return [...resultItems, ...await loadAllResults(result.LastEvaluatedKey)];
20
- }
21
- return resultItems;
22
- };
23
- const allResults = await loadAllResults().then(results => results.reduce((result, document) => {
24
- const current = result.get(document[hashKey]);
25
- if (!current || current.timestamp < document.timestamp) {
26
- return result.set(document[hashKey], document);
27
- }
28
- return result;
29
- }, new Map()));
30
- return Array.from(allResults.values());
31
- },
32
- getVersions: async (id, startVersion) => {
33
- const cmd = new QueryCommand({
34
- TableName: await tableName(),
35
- KeyConditionExpression: '#hk = :hkv',
36
- ExpressionAttributeValues: {
37
- ':hkv': encodeDynamoAttribute(id)
38
- },
39
- ExpressionAttributeNames: {
9
+ const dynamodb = once(() => { var _a; return (_a = init.dynamoClient) !== null && _a !== void 0 ? _a : new DynamoDB({ apiVersion: '2012-08-10' }); });
10
+ return () => (configProvider) => (_, hashKey, options) => {
11
+ const tableName = once(() => resolveConfigValue(configProvider[ifDefined(init.configSpace, 'dynamodb')].tableName));
12
+ return {
13
+ loadAllDocumentsTheBadWay: async () => {
14
+ const loadAllResults = async (ExclusiveStartKey) => {
15
+ var _a;
16
+ const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
17
+ const result = await dynamodb().send(cmd);
18
+ const resultItems = ((_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)) || [];
19
+ if (result.LastEvaluatedKey) {
20
+ return [...resultItems, ...await loadAllResults(result.LastEvaluatedKey)];
21
+ }
22
+ return resultItems;
23
+ };
24
+ const allResults = await loadAllResults().then(results => results.reduce((result, document) => {
25
+ const current = result.get(document[hashKey]);
26
+ if (!current || current.timestamp < document.timestamp) {
27
+ return result.set(document[hashKey], document);
28
+ }
29
+ return result;
30
+ }, new Map()));
31
+ return Array.from(allResults.values());
32
+ },
33
+ getVersions: async (id, startVersion) => {
34
+ const cmd = new QueryCommand({
35
+ TableName: await tableName(),
36
+ KeyConditionExpression: '#hk = :hkv',
37
+ ExpressionAttributeValues: {
38
+ ':hkv': encodeDynamoAttribute(id)
39
+ },
40
+ ExpressionAttributeNames: {
41
+ '#hk': hashKey.toString()
42
+ },
43
+ ...(startVersion
44
+ ? { ExclusiveStartKey: {
45
+ [hashKey]: encodeDynamoAttribute(id),
46
+ timestamp: { N: startVersion.toString() }
47
+ } }
48
+ : {}),
49
+ ScanIndexForward: false,
50
+ });
51
+ return dynamodb().send(cmd).then(result => {
52
+ var _a;
53
+ const items = (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument);
54
+ if (!items || items.length === 0) {
55
+ return undefined;
56
+ }
57
+ return {
58
+ items,
59
+ nextPageToken: result.LastEvaluatedKey ? decodeDynamoDocument(result.LastEvaluatedKey).timestamp : undefined
60
+ };
61
+ });
62
+ },
63
+ getItem: async (id, timestamp) => {
64
+ let keyConditionExpression = '#hk = :hkv';
65
+ const expressionAttributeNames = {
40
66
  '#hk': hashKey.toString()
41
- },
42
- ...(startVersion
43
- ? { ExclusiveStartKey: {
44
- [hashKey]: encodeDynamoAttribute(id),
45
- timestamp: { N: startVersion.toString() }
46
- } }
47
- : {}),
48
- ScanIndexForward: false,
49
- });
50
- return dynamodb().send(cmd).then(result => {
51
- var _a;
52
- const items = (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument);
53
- if (!items || items.length === 0) {
54
- return undefined;
67
+ };
68
+ const expressionAttributeValues = {
69
+ ':hkv': encodeDynamoAttribute(id)
70
+ };
71
+ if (timestamp) {
72
+ keyConditionExpression += ' and #ts = :tsv';
73
+ expressionAttributeNames['#ts'] = 'timestamp';
74
+ expressionAttributeValues[':tsv'] = encodeDynamoAttribute(timestamp);
55
75
  }
76
+ const cmd = new QueryCommand({
77
+ TableName: await tableName(),
78
+ KeyConditionExpression: keyConditionExpression,
79
+ ExpressionAttributeNames: expressionAttributeNames,
80
+ ExpressionAttributeValues: expressionAttributeValues,
81
+ ScanIndexForward: false,
82
+ Limit: 1
83
+ });
84
+ return dynamodb().send(cmd).then(result => {
85
+ var _a;
86
+ return (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)[0];
87
+ });
88
+ },
89
+ /* prepares a new version of a document with the given data, then allows some additional
90
+ * changes to be input to a `save` function that actually saves it. useful for additional
91
+ * changes based on the new document version or author. the document version and author
92
+ * cannot be modified */
93
+ prepareItem: async (item, ...authorArgs) => {
94
+ // this getAuthor type is terrible
95
+ const author = (options === null || options === void 0 ? void 0 : options.getAuthor) ? await options.getAuthor(...authorArgs) : authorArgs[0];
96
+ const timestamp = new Date().getTime();
56
97
  return {
57
- items,
58
- nextPageToken: result.LastEvaluatedKey ? decodeDynamoDocument(result.LastEvaluatedKey).timestamp : undefined
98
+ document: { ...item, timestamp, author },
99
+ save: async (changes) => {
100
+ var _a;
101
+ const document = {
102
+ ...item,
103
+ ...changes,
104
+ timestamp,
105
+ author
106
+ };
107
+ const cmd = new PutItemCommand({
108
+ TableName: await tableName(),
109
+ Item: encodeDynamoDocument(document),
110
+ });
111
+ const updatedDoc = await dynamodb().send(cmd)
112
+ .then(() => document);
113
+ await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
114
+ return updatedDoc;
115
+ }
59
116
  };
60
- });
61
- },
62
- getItem: async (id, timestamp) => {
63
- let keyConditionExpression = '#hk = :hkv';
64
- const expressionAttributeNames = {
65
- '#hk': hashKey.toString()
66
- };
67
- const expressionAttributeValues = {
68
- ':hkv': encodeDynamoAttribute(id)
69
- };
70
- if (timestamp) {
71
- keyConditionExpression += ' and #ts = :tsv';
72
- expressionAttributeNames['#ts'] = 'timestamp';
73
- expressionAttributeValues[':tsv'] = encodeDynamoAttribute(timestamp);
74
- }
75
- const cmd = new QueryCommand({
76
- TableName: await tableName(),
77
- KeyConditionExpression: keyConditionExpression,
78
- ExpressionAttributeNames: expressionAttributeNames,
79
- ExpressionAttributeValues: expressionAttributeValues,
80
- ScanIndexForward: false,
81
- Limit: 1
82
- });
83
- return dynamodb().send(cmd).then(result => {
117
+ },
118
+ /* saves a new version of a document with the given data */
119
+ putItem: async (item, ...authorArgs) => {
84
120
  var _a;
85
- return (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)[0];
86
- });
87
- },
88
- /* prepares a new version of a document with the given data, then allows some additional
89
- * changes to be input to a `save` function that actually saves it. useful for additional
90
- * changes based on the new document version or author. the document version and author
91
- * cannot be modified */
92
- prepareItem: async (item, ...authorArgs) => {
93
- // this getAuthor type is terrible
94
- const author = (options === null || options === void 0 ? void 0 : options.getAuthor) ? await options.getAuthor(...authorArgs) : authorArgs[0];
95
- const timestamp = new Date().getTime();
96
- return {
97
- document: { ...item, timestamp, author },
98
- save: async (changes) => {
99
- var _a;
100
- const document = {
101
- ...item,
102
- ...changes,
103
- timestamp,
104
- author
105
- };
106
- const cmd = new PutItemCommand({
107
- TableName: await tableName(),
108
- Item: encodeDynamoDocument(document),
109
- });
110
- const updatedDoc = await dynamodb().send(cmd)
111
- .then(() => document);
112
- await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
113
- return updatedDoc;
114
- }
115
- };
116
- },
117
- /* saves a new version of a document with the given data */
118
- putItem: async (item, ...authorArgs) => {
119
- var _a;
120
- // this getAuthor type is terrible
121
- const author = (options === null || options === void 0 ? void 0 : options.getAuthor) ? await options.getAuthor(...authorArgs) : authorArgs[0];
122
- const document = {
123
- ...item,
124
- timestamp: new Date().getTime(),
125
- author
126
- };
127
- const cmd = new PutItemCommand({
128
- TableName: await tableName(),
129
- Item: encodeDynamoDocument(document),
130
- });
131
- const updatedDoc = await dynamodb().send(cmd)
132
- .then(() => document);
133
- await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
134
- return updatedDoc;
135
- },
121
+ // this getAuthor type is terrible
122
+ const author = (options === null || options === void 0 ? void 0 : options.getAuthor) ? await options.getAuthor(...authorArgs) : authorArgs[0];
123
+ const document = {
124
+ ...item,
125
+ timestamp: new Date().getTime(),
126
+ author
127
+ };
128
+ const cmd = new PutItemCommand({
129
+ TableName: await tableName(),
130
+ Item: encodeDynamoDocument(document),
131
+ });
132
+ const updatedDoc = await dynamodb().send(cmd)
133
+ .then(() => document);
134
+ await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
135
+ return updatedDoc;
136
+ },
137
+ };
136
138
  };
137
139
  };
@@ -7,7 +7,7 @@ export declare type Config = {
7
7
  };
8
8
  interface Initializer<C> {
9
9
  configSpace?: C;
10
- s3Client?: typeof S3Client;
10
+ getS3Client?: (...args: ConstructorParameters<typeof S3Client>) => S3Client;
11
11
  }
12
12
  export declare const s3FileServer: <C extends string = "deployed">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
13
13
  bucketName: import("../../config").ConfigValueProvider<string>;
@@ -9,8 +9,11 @@ export const s3FileServer = (initializer) => (configProvider) => {
9
9
  const config = configProvider[ifDefined(initializer.configSpace, 'deployed')];
10
10
  const bucketName = once(() => resolveConfigValue(config.bucketName));
11
11
  const bucketRegion = once(() => resolveConfigValue(config.bucketRegion));
12
- const client = ifDefined(initializer.s3Client, S3Client);
13
- const s3Service = once(async () => new client({ apiVersion: '2012-08-10', region: await bucketRegion() }));
12
+ const s3Service = once(async () => {
13
+ var _a, _b;
14
+ const args = { apiVersion: '2012-08-10', region: await bucketRegion() };
15
+ return (_b = (_a = initializer.getS3Client) === null || _a === void 0 ? void 0 : _a.call(initializer, args)) !== null && _b !== void 0 ? _b : new S3Client(args);
16
+ });
14
17
  /*
15
18
  * https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html
16
19
  */
@@ -4,11 +4,13 @@ import { memoize } from '../..';
4
4
  import { resolveConfigValue } from '../../config';
5
5
  import { InvalidRequestError, SessionExpiredError } from '../../errors';
6
6
  import { ifDefined } from '../../guards';
7
+ import { once } from '../../misc/helpers';
7
8
  /**
8
9
  * Creates a class that can verify launch params
9
10
  */
10
11
  export const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvider) => {
11
12
  const config = configProvider[ifDefined(configSpace, 'launch')];
13
+ const getTrustedDomain = once(() => resolveConfigValue(config.trustedDomain));
12
14
  const getJwksClient = memoize((jwksUri) => new JwksClient({ fetcher, jwksUri }));
13
15
  const getJwksKey = memoize(async (jwksUri, kid) => {
14
16
  const client = getJwksClient(jwksUri);
@@ -24,7 +26,7 @@ export const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvide
24
26
  try {
25
27
  const jwksUrl = new URL('/.well-known/jwks.json', iss);
26
28
  const launchDomain = jwksUrl.hostname;
27
- const trustedDomain = await resolveConfigValue(config.trustedDomain);
29
+ const trustedDomain = await getTrustedDomain();
28
30
  if (launchDomain !== trustedDomain && !launchDomain.endsWith(`.${trustedDomain}`)) {
29
31
  return callback(new Error(`Untrusted launch domain: "${launchDomain}"`));
30
32
  }
@@ -1,13 +1,28 @@
1
1
  export declare type FieldType = string | string[] | number | boolean;
2
- export declare type Filter = {
3
- key: string;
4
- value: FieldType;
5
- } | {
2
+ export declare type ESFilter = {
6
3
  terms: Record<string, FieldType>;
7
4
  } | {
8
5
  exists: {
9
6
  field: string;
10
7
  };
8
+ } | {
9
+ nested: {
10
+ path: string;
11
+ query: Filter;
12
+ };
13
+ } | {
14
+ bool: BoolFilter;
15
+ };
16
+ export declare type Filter = {
17
+ key: string;
18
+ value: FieldType;
19
+ } | ESFilter;
20
+ export declare type BoolFilter = {
21
+ must?: Filter[];
22
+ must_not?: Filter[];
23
+ should?: Filter[];
24
+ filter?: Filter[];
25
+ minimum_should_match?: number;
11
26
  };
12
27
  declare type Field = {
13
28
  key: string;
@@ -24,13 +39,17 @@ export interface IndexOptions<T> {
24
39
  body: T;
25
40
  id: string;
26
41
  }
42
+ export declare type FieldMapping = {
43
+ type: 'keyword' | 'text' | 'boolean';
44
+ } | {
45
+ type?: 'nested' | 'object';
46
+ properties: Record<string, FieldMapping>;
47
+ };
48
+ export declare type FieldMappings = Record<string, FieldMapping>;
27
49
  export interface SearchOptions {
28
50
  page?: number;
29
51
  query: string | undefined;
30
52
  fields: Field[];
31
- /**
32
- * @deprecated use `must` instead
33
- */
34
53
  filter?: Filter[];
35
54
  must?: Filter[];
36
55
  must_not?: Filter[];
@@ -1,9 +1,11 @@
1
- import { IndexOptions, SearchOptions } from '.';
2
- export declare const memorySearchTheBadWay: () => <T>({ store }: {
1
+ import { FieldMappings, IndexOptions, SearchOptions } from '.';
2
+ export declare type Config<T> = {
3
+ mappings: FieldMappings;
3
4
  store: {
4
5
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
5
6
  };
6
- }) => {
7
+ };
8
+ export declare const memorySearchTheBadWay: () => <T>({ store, mappings }: Config<T>) => {
7
9
  deleteIndexIfExists: () => Promise<undefined>;
8
10
  ensureIndexCreated: () => Promise<undefined>;
9
11
  index: (_options: IndexOptions<T>) => Promise<undefined>;
@@ -9,16 +9,58 @@ var MatchType;
9
9
  MatchType[MatchType["MustNot"] = 1] = "MustNot";
10
10
  MatchType[MatchType["Should"] = 2] = "Should";
11
11
  })(MatchType || (MatchType = {}));
12
- const resolveField = (document, field) => field.split('.').reduce((result, key) => result[key], document);
13
- const matchExists = (exists, document) => {
14
- const value = resolveField(document, exists.field);
12
+ const resolveField = (context, field) => {
13
+ const path = field.startsWith(context.scope + '.')
14
+ ? field.slice(context.scope.length + 1)
15
+ : field;
16
+ return path.split('.').reduce((result, key) => {
17
+ if (result === undefined) {
18
+ throw new Error(`Key "${key}" not found in document resolving ${field}`);
19
+ }
20
+ return result[key];
21
+ }, context.document);
22
+ };
23
+ const getFieldDefinition = (context, field) => {
24
+ let current = context.fields;
25
+ const parts = field.split('.');
26
+ const head = parts.slice(0, -1);
27
+ const tail = parts[parts.length - 1];
28
+ for (const part of head) {
29
+ const currentDefinition = current[part];
30
+ if (!currentDefinition || !('properties' in currentDefinition)) {
31
+ throw new Error(`Field "${field}" not found in mappings`);
32
+ }
33
+ current = currentDefinition.properties;
34
+ }
35
+ if (!current[tail]) {
36
+ throw new Error(`Field "${field}" not found in mappings`);
37
+ }
38
+ return current[tail];
39
+ };
40
+ const getFieldType = (context, field) => {
41
+ const definition = getFieldDefinition(context, field);
42
+ return definition.type;
43
+ };
44
+ const matchNested = (context, nested) => {
45
+ const nestedPath = nested.path;
46
+ const nestedDocuments = resolveField(context, nestedPath);
47
+ if (!Array.isArray(nestedDocuments)) {
48
+ throw new InvalidRequestError(`Nested path "${nestedPath}" is not an array`);
49
+ }
50
+ return nestedDocuments.some(nestedDocument => matchClause({
51
+ ...context,
52
+ scope: nestedPath,
53
+ document: nestedDocument
54
+ }, nested.query));
55
+ };
56
+ const matchExists = (context, exists) => {
57
+ const value = resolveField(context, exists.field);
15
58
  return value !== undefined && value !== null;
16
59
  };
17
- const matchTerms = (options, terms, document) => {
18
- const getFieldType = (field) => { var _a; return (_a = options.fields.find(f => f.key == field)) === null || _a === void 0 ? void 0 : _a.type; };
60
+ const matchTerms = (context, terms) => {
19
61
  for (const key in terms) {
20
- const docValues = coerceArray(resolveField(document, key));
21
- const coerceValue = getFieldType(key) === 'boolean'
62
+ const docValues = coerceArray(resolveField(context, key));
63
+ const coerceValue = getFieldType(context, key) === 'boolean'
22
64
  ? (input) => {
23
65
  if ([true, 'true', '1', 1].includes(input)) {
24
66
  return true;
@@ -36,7 +78,50 @@ const matchTerms = (options, terms, document) => {
36
78
  }
37
79
  return true;
38
80
  };
39
- export const memorySearchTheBadWay = () => ({ store }) => {
81
+ const matchClause = (context, clause) => {
82
+ const filter = ('key' in clause && 'value' in clause)
83
+ ? { terms: { [clause.key]: clause.value } }
84
+ : clause;
85
+ if ('terms' in filter)
86
+ return matchTerms(context, filter.terms);
87
+ else if ('exists' in filter)
88
+ return matchExists(context, filter.exists);
89
+ else if ('bool' in filter)
90
+ return matchBool(context, filter.bool);
91
+ else if ('nested' in filter)
92
+ return matchNested(context, filter.nested);
93
+ else
94
+ throw new InvalidRequestError('invalid filter type');
95
+ };
96
+ const matchFilters = (context, filters, matchType) => {
97
+ for (const field of filters) {
98
+ const hasMatch = matchClause(context, field);
99
+ if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
100
+ return false;
101
+ }
102
+ else if (matchType === MatchType.Should && hasMatch) {
103
+ return true;
104
+ }
105
+ }
106
+ return matchType !== MatchType.Should;
107
+ };
108
+ const matchBool = (context, filters) => {
109
+ const { must_not, should, must, filter } = filters;
110
+ if ((must === null || must === void 0 ? void 0 : must.length) && !matchFilters(context, must, MatchType.Must)) {
111
+ return false;
112
+ }
113
+ if ((filter === null || filter === void 0 ? void 0 : filter.length) && !matchFilters(context, filter, MatchType.Must)) {
114
+ return false;
115
+ }
116
+ if ((must_not === null || must_not === void 0 ? void 0 : must_not.length) && !matchFilters(context, must_not, MatchType.MustNot)) {
117
+ return false;
118
+ }
119
+ if ((should === null || should === void 0 ? void 0 : should.length) && !matchFilters(context, should, MatchType.Should)) {
120
+ return false;
121
+ }
122
+ return true;
123
+ };
124
+ export const memorySearchTheBadWay = () => ({ store, mappings }) => {
40
125
  return {
41
126
  // This method is intentionally stubbed because index deletion is not applicable for in-memory storage.
42
127
  deleteIndexIfExists: async () => undefined,
@@ -47,33 +132,12 @@ export const memorySearchTheBadWay = () => ({ store }) => {
47
132
  const results = (await store.loadAllDocumentsTheBadWay())
48
133
  .map(document => {
49
134
  let weight = 0;
50
- const matchFilters = (filters, matchType) => {
51
- for (const field of filters) {
52
- const filter = ('key' in field && 'value' in field)
53
- ? { terms: { [field.key]: field.value } }
54
- : field;
55
- let hasMatch;
56
- if ('terms' in filter)
57
- hasMatch = matchTerms(options, filter.terms, document);
58
- else if ('exists' in filter)
59
- hasMatch = matchExists(filter.exists, document);
60
- else
61
- throw new InvalidRequestError('invalid filter type');
62
- if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
63
- return false;
64
- }
65
- else if (matchType === MatchType.Should && hasMatch) {
66
- return true;
67
- }
68
- }
69
- return matchType !== MatchType.Should;
70
- };
71
135
  if (options.query !== undefined) {
72
136
  for (const field of options.fields) {
73
137
  if (field.type !== undefined && field.type !== 'text') {
74
138
  continue;
75
139
  }
76
- const value = resolveField(document, field.key);
140
+ const value = resolveField({ document, fields: mappings, scope: '' }, field.key);
77
141
  if (value === undefined || value === null) {
78
142
  continue;
79
143
  }
@@ -87,15 +151,7 @@ export const memorySearchTheBadWay = () => ({ store }) => {
87
151
  }
88
152
  }
89
153
  }
90
- const { must_not, should } = options;
91
- const must = 'filter' in options ? options.filter : options.must;
92
- if ((must === null || must === void 0 ? void 0 : must.length) && !matchFilters(must, MatchType.Must)) {
93
- return undefined;
94
- }
95
- if ((must_not === null || must_not === void 0 ? void 0 : must_not.length) && !matchFilters(must_not, MatchType.MustNot)) {
96
- return undefined;
97
- }
98
- if ((should === null || should === void 0 ? void 0 : should.length) && !matchFilters(should, MatchType.Should)) {
154
+ if (!matchBool({ document, fields: mappings, scope: '' }, options)) {
99
155
  return undefined;
100
156
  }
101
157
  return { document, weight };
@@ -104,8 +160,8 @@ export const memorySearchTheBadWay = () => ({ store }) => {
104
160
  .filter(r => !options.query || r.weight >= MIN_MATCH);
105
161
  results.sort((a, b) => {
106
162
  for (const sort of (options.sort || [])) {
107
- const aValue = resolveField(a.document, sort.key);
108
- const bValue = resolveField(b.document, sort.key);
163
+ const aValue = resolveField({ document: a.document, fields: mappings, scope: '' }, sort.key);
164
+ const bValue = resolveField({ document: b.document, fields: mappings, scope: '' }, sort.key);
109
165
  if (aValue < bValue) {
110
166
  return sort.order === 'asc' ? -1 : 1;
111
167
  }
@@ -1,5 +1,5 @@
1
1
  import { ConfigProviderForConfig } from '../../config';
2
- import { IndexOptions, SearchOptions } from '.';
2
+ import { FieldMappings, IndexOptions, SearchOptions } from '.';
3
3
  export declare type Config = {
4
4
  node: string;
5
5
  region: string;
@@ -9,7 +9,7 @@ export interface Initializer<C> {
9
9
  }
10
10
  export declare type IndexConfig = {
11
11
  name: string;
12
- mappings: Record<string, any>;
12
+ mappings: FieldMappings;
13
13
  pageSize?: number;
14
14
  };
15
15
  export declare const openSearchService: <C extends string = "deployed">(initializer?: Initializer<C>) => (configProvider: { [key in C]: {
@@ -72,23 +72,27 @@ export const openSearchService = (initializer = {}) => (configProvider) => {
72
72
  });
73
73
  };
74
74
  const search = async (options) => {
75
+ var _a;
75
76
  const body = {
76
77
  query: { bool: {} },
77
78
  track_total_hits: true,
78
79
  size: pageSize
79
80
  };
80
81
  if (options.query) {
81
- body.query.bool.must = {
82
- multi_match: {
83
- fields: options.fields.map((field) => 'weight' in field ? `${field.key}^${field.weight}` : field.key),
84
- query: options.query
85
- }
86
- };
82
+ body.query.bool.must = [{
83
+ multi_match: {
84
+ fields: options.fields.map((field) => 'weight' in field ? `${field.key}^${field.weight}` : field.key),
85
+ query: options.query
86
+ }
87
+ }];
88
+ }
89
+ const { must_not, should, must, filter } = options;
90
+ if (filter && filter.length > 0) {
91
+ body.query.bool.filter = filter.map(mapFilter);
87
92
  }
88
- const { must_not, should } = options;
89
- const must = 'filter' in options ? options.filter : options.must;
90
93
  if (must && must.length > 0) {
91
- body.query.bool.filter = must.map(mapFilter);
94
+ (_a = body.query.bool).must || (_a.must = []);
95
+ body.query.bool.must = body.query.bool.must.concat(must.map(mapFilter));
92
96
  }
93
97
  if (must_not && must_not.length > 0) {
94
98
  body.query.bool.must_not = must_not.map(mapFilter);