@openstax/ts-utils 1.26.1 → 1.27.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 (31) hide show
  1. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +1 -1
  2. package/dist/cjs/services/authProvider/decryption.js +3 -1
  3. package/dist/cjs/services/authProvider/index.d.ts +2 -1
  4. package/dist/cjs/services/authProvider/index.js +4 -1
  5. package/dist/cjs/services/authProvider/subrequest.js +2 -0
  6. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +4 -0
  7. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +31 -0
  8. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +4 -0
  9. package/dist/cjs/services/documentStore/unversioned/file-system.js +19 -7
  10. package/dist/cjs/services/searchProvider/index.d.ts +26 -7
  11. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +6 -3
  12. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +98 -41
  13. package/dist/cjs/services/searchProvider/openSearch.d.ts +3 -2
  14. package/dist/cjs/services/searchProvider/openSearch.js +25 -10
  15. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  16. package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +1 -1
  17. package/dist/esm/services/authProvider/decryption.js +3 -1
  18. package/dist/esm/services/authProvider/index.d.ts +2 -1
  19. package/dist/esm/services/authProvider/index.js +4 -1
  20. package/dist/esm/services/authProvider/subrequest.js +2 -0
  21. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +4 -0
  22. package/dist/esm/services/documentStore/unversioned/dynamodb.js +32 -1
  23. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +4 -0
  24. package/dist/esm/services/documentStore/unversioned/file-system.js +19 -7
  25. package/dist/esm/services/searchProvider/index.d.ts +26 -7
  26. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +6 -3
  27. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +98 -41
  28. package/dist/esm/services/searchProvider/openSearch.d.ts +3 -2
  29. package/dist/esm/services/searchProvider/openSearch.js +25 -10
  30. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  31. package/package.json +1 -1
@@ -21,7 +21,7 @@ const createLambdaCorsResponseMiddleware = (config) => () => (responsePromise, {
21
21
  'Access-Control-Allow-Origin': request.headers.origin,
22
22
  'Access-Control-Allow-Credentials': 'true',
23
23
  'Access-Control-Allow-Headers': 'content-type, x-request-id',
24
- 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
24
+ 'Access-Control-Allow-Methods': 'POST, GET, PUT, PATCH, DELETE, OPTIONS',
25
25
  };
26
26
  }
27
27
  };
@@ -14,6 +14,7 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
14
14
  const signaturePublicKey = (0, helpers_1.once)(() => (0, resolveConfigValue_1.resolveConfigValue)(config.signaturePublicKey));
15
15
  return ({ request, logger }) => {
16
16
  let user;
17
+ const getAuthToken = async () => (0, _1.getAuthTokenOrCookie)(request, await cookieName())[0];
17
18
  const getAuthorizedFetchConfig = async () => {
18
19
  const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
19
20
  if (!token) {
@@ -22,7 +23,7 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
22
23
  return { headers };
23
24
  };
24
25
  const getPayload = async (tokenString) => {
25
- const token = tokenString !== null && tokenString !== void 0 ? tokenString : (0, _1.getAuthTokenOrCookie)(request, await cookieName())[0];
26
+ const token = tokenString !== null && tokenString !== void 0 ? tokenString : await getAuthToken();
26
27
  if (!token) {
27
28
  return undefined;
28
29
  }
@@ -43,6 +44,7 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
43
44
  return undefined;
44
45
  };
45
46
  return {
47
+ getAuthToken,
46
48
  getAuthorizedFetchConfig,
47
49
  getTokenExpiration: async (tokenString) => {
48
50
  var _a;
@@ -37,6 +37,7 @@ export interface ApiUser extends TokenUser {
37
37
  }
38
38
  export declare type User = TokenUser | ApiUser;
39
39
  export declare type AuthProvider = {
40
+ getAuthToken: () => Promise<string | null>;
40
41
  getUser: () => Promise<User | undefined>;
41
42
  /**
42
43
  * gets second argument for `fetch` that has authentication token or cookie
@@ -58,4 +59,4 @@ export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest,
58
59
  Authorization: string;
59
60
  }] | [string, {
60
61
  cookie: string;
61
- }];
62
+ }] | [null, {}];
@@ -8,6 +8,7 @@ const cookie_1 = __importDefault(require("cookie"));
8
8
  const helpers_1 = require("../../misc/helpers");
9
9
  const helpers_2 = require("../../routing/helpers");
10
10
  const stubAuthProvider = (user) => ({
11
+ getAuthToken: () => Promise.resolve('authToken'),
11
12
  getUser: () => Promise.resolve(user),
12
13
  getAuthorizedFetchConfig: () => Promise.resolve(user ? { headers: { Authorization: user.uuid } } : {})
13
14
  });
@@ -21,6 +22,8 @@ const getAuthTokenOrCookie = (request, cookieName, queryKey = 'auth') => {
21
22
  ? (0, helpers_1.tuple)(authParam, { Authorization: `Bearer ${authParam}` })
22
23
  : authHeader && authHeader.length >= 8 && authHeader.startsWith('Bearer ')
23
24
  ? (0, helpers_1.tuple)(authHeader.slice(7), { Authorization: authHeader })
24
- : (0, helpers_1.tuple)(cookieValue, { cookie: cookie_1.default.serialize(cookieName, cookieValue) });
25
+ : cookieValue
26
+ ? (0, helpers_1.tuple)(cookieValue, { cookie: cookie_1.default.serialize(cookieName, cookieValue) })
27
+ : (0, helpers_1.tuple)(null, {});
25
28
  };
26
29
  exports.getAuthTokenOrCookie = getAuthTokenOrCookie;
@@ -15,6 +15,7 @@ const subrequestAuthProvider = (initializer) => (configProvider) => {
15
15
  const accountsBase = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsBase));
16
16
  return ({ request, logger }) => {
17
17
  let user;
18
+ const getAuthToken = async () => (0, _1.getAuthTokenOrCookie)(request, await cookieName())[0];
18
19
  const getAuthorizedFetchConfig = async () => {
19
20
  const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
20
21
  if (!token) {
@@ -37,6 +38,7 @@ const subrequestAuthProvider = (initializer) => (configProvider) => {
37
38
  return user;
38
39
  };
39
40
  return {
41
+ getAuthToken,
40
42
  getAuthorizedFetchConfig,
41
43
  getUser: async () => {
42
44
  if (!user) {
@@ -9,6 +9,10 @@ export declare const dynamoUnversionedDocumentStore: <C extends string = "dynamo
9
9
  afterWrite?: ((item: T) => void | Promise<void>) | undefined;
10
10
  } | undefined) => {
11
11
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
12
+ getItemsByField: (key: keyof T, value: T[K], pageKey?: string | undefined) => Promise<{
13
+ items: T[];
14
+ nextPageToken: string | undefined;
15
+ }>;
12
16
  batchGetItem: (ids: T[K][]) => Promise<T[]>;
13
17
  getItem: (id: T[K]) => Promise<T | undefined>;
14
18
  incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
@@ -25,6 +25,37 @@ const dynamoUnversionedDocumentStore = (initializer) => () => (configProvider) =
25
25
  };
26
26
  return loadAllResults();
27
27
  },
28
+ /*
29
+ * requires that a global secondary index exist on the table
30
+ */
31
+ getItemsByField: async (key, value, pageKey) => {
32
+ var _a, _b;
33
+ const ExclusiveStartKey = pageKey
34
+ ? JSON.parse(Buffer.from(pageKey, 'base64').toString('utf-8'))
35
+ : undefined;
36
+ const table = await tableName();
37
+ const cmd = new client_dynamodb_1.QueryCommand({
38
+ TableName: table,
39
+ IndexName: key.toString(),
40
+ ExclusiveStartKey,
41
+ KeyConditionExpression: '#hk = :hkv',
42
+ ExpressionAttributeValues: {
43
+ ':hkv': (0, dynamoEncoding_1.encodeDynamoAttribute)(value),
44
+ },
45
+ ExpressionAttributeNames: {
46
+ '#hk': key.toString(),
47
+ },
48
+ });
49
+ const response = await dynamodb().send(cmd);
50
+ const items = (_b = (_a = response.Items) === null || _a === void 0 ? void 0 : _a.map((item) => (0, dynamoEncoding_1.decodeDynamoDocument)(item))) !== null && _b !== void 0 ? _b : [];
51
+ const nextPageToken = response.LastEvaluatedKey;
52
+ return {
53
+ items,
54
+ nextPageToken: nextPageToken
55
+ ? Buffer.from(JSON.stringify(nextPageToken)).toString('base64')
56
+ : undefined,
57
+ };
58
+ },
28
59
  batchGetItem: async (ids) => {
29
60
  const table = await tableName();
30
61
  const key = hashKey.toString();
@@ -9,6 +9,10 @@ export declare const fileSystemUnversionedDocumentStore: <C extends string = "fi
9
9
  tableName: import("../../../config").ConfigValueProvider<string>;
10
10
  }; }) => <K extends keyof T>(_: {}, hashKey: K) => {
11
11
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
12
+ getItemsByField: (key: keyof T, value: T[K], pageKey?: string | undefined) => Promise<{
13
+ items: T[];
14
+ nextPageToken: string | undefined;
15
+ }>;
12
16
  batchGetItem: (ids: T[K][]) => Promise<Exclude<Awaited<T>, undefined>[]>;
13
17
  getItem: (id: T[K]) => Promise<T | undefined>;
14
18
  incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
@@ -60,14 +60,26 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
60
60
  });
61
61
  });
62
62
  };
63
+ const loadAllDocumentsTheBadWay = async () => {
64
+ const path = await tablePath;
65
+ await mkTableDir;
66
+ return new Promise((resolve, reject) => readdir(path, (err, files) => err ?
67
+ reject(err) :
68
+ Promise.all(files.map((file) => load(file)))
69
+ .then((allData) => resolve(allData.filter(guards_1.isDefined)), (err) => reject(err))));
70
+ };
63
71
  return {
64
- loadAllDocumentsTheBadWay: async () => {
65
- const path = await tablePath;
66
- await mkTableDir;
67
- return new Promise((resolve, reject) => readdir(path, (err, files) => err ?
68
- reject(err) :
69
- Promise.all(files.map((file) => load(file)))
70
- .then((allData) => resolve(allData.filter(guards_1.isDefined)), (err) => reject(err))));
72
+ loadAllDocumentsTheBadWay,
73
+ getItemsByField: async (key, value, pageKey) => {
74
+ const pageSize = 10;
75
+ const items = await loadAllDocumentsTheBadWay();
76
+ const filteredItems = items.filter((item) => item[key] === value);
77
+ const startIndex = pageKey ? parseInt(pageKey, 10) : 0;
78
+ const paginatedItems = filteredItems.slice(startIndex, startIndex + pageSize);
79
+ const nextPageToken = startIndex + pageSize < filteredItems.length
80
+ ? (startIndex + pageSize).toString()
81
+ : undefined;
82
+ return { items: paginatedItems, nextPageToken };
71
83
  },
72
84
  batchGetItem: async (ids) => {
73
85
  const items = await Promise.all(ids.map((id) => load(hashFilename(id))));
@@ -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,12 +1,15 @@
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>;
12
+ bulkIndex: (_items: IndexOptions<T>[]) => Promise<undefined>;
10
13
  search: (options: SearchOptions) => Promise<{
11
14
  items: T[];
12
15
  pageSize: number;
@@ -12,16 +12,58 @@ 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.split('.').reduce((result, key) => result[key], document);
16
- const matchExists = (exists, document) => {
17
- const value = resolveField(document, exists.field);
15
+ const resolveField = (context, field) => {
16
+ const path = field.startsWith(context.scope + '.')
17
+ ? field.slice(context.scope.length + 1)
18
+ : field;
19
+ return path.split('.').reduce((result, key) => {
20
+ if (result === undefined) {
21
+ throw new Error(`Key "${key}" not found in document resolving ${field}`);
22
+ }
23
+ return result[key];
24
+ }, context.document);
25
+ };
26
+ const getFieldDefinition = (context, field) => {
27
+ let current = context.fields;
28
+ const parts = field.split('.');
29
+ const head = parts.slice(0, -1);
30
+ const tail = parts[parts.length - 1];
31
+ for (const part of head) {
32
+ const currentDefinition = current[part];
33
+ if (!currentDefinition || !('properties' in currentDefinition)) {
34
+ throw new Error(`Field "${field}" not found in mappings`);
35
+ }
36
+ current = currentDefinition.properties;
37
+ }
38
+ if (!current[tail]) {
39
+ throw new Error(`Field "${field}" not found in mappings`);
40
+ }
41
+ return current[tail];
42
+ };
43
+ const getFieldType = (context, field) => {
44
+ const definition = getFieldDefinition(context, field);
45
+ return definition.type;
46
+ };
47
+ const matchNested = (context, nested) => {
48
+ const nestedPath = nested.path;
49
+ const nestedDocuments = resolveField(context, nestedPath);
50
+ if (!Array.isArray(nestedDocuments)) {
51
+ throw new errors_1.InvalidRequestError(`Nested path "${nestedPath}" is not an array`);
52
+ }
53
+ return nestedDocuments.some(nestedDocument => matchClause({
54
+ ...context,
55
+ scope: nestedPath,
56
+ document: nestedDocument
57
+ }, nested.query));
58
+ };
59
+ const matchExists = (context, exists) => {
60
+ const value = resolveField(context, exists.field);
18
61
  return value !== undefined && value !== null;
19
62
  };
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; };
63
+ const matchTerms = (context, terms) => {
22
64
  for (const key in terms) {
23
- const docValues = (0, __1.coerceArray)(resolveField(document, key));
24
- const coerceValue = getFieldType(key) === 'boolean'
65
+ const docValues = (0, __1.coerceArray)(resolveField(context, key));
66
+ const coerceValue = getFieldType(context, key) === 'boolean'
25
67
  ? (input) => {
26
68
  if ([true, 'true', '1', 1].includes(input)) {
27
69
  return true;
@@ -39,43 +81,66 @@ const matchTerms = (options, terms, document) => {
39
81
  }
40
82
  return true;
41
83
  };
42
- const memorySearchTheBadWay = () => ({ store }) => {
84
+ const matchClause = (context, clause) => {
85
+ const filter = ('key' in clause && 'value' in clause)
86
+ ? { terms: { [clause.key]: clause.value } }
87
+ : clause;
88
+ if ('terms' in filter)
89
+ return matchTerms(context, filter.terms);
90
+ else if ('exists' in filter)
91
+ return matchExists(context, filter.exists);
92
+ else if ('bool' in filter)
93
+ return matchBool(context, filter.bool);
94
+ else if ('nested' in filter)
95
+ return matchNested(context, filter.nested);
96
+ else
97
+ throw new errors_1.InvalidRequestError('invalid filter type');
98
+ };
99
+ const matchFilters = (context, filters, matchType) => {
100
+ for (const field of filters) {
101
+ const hasMatch = matchClause(context, field);
102
+ if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
103
+ return false;
104
+ }
105
+ else if (matchType === MatchType.Should && hasMatch) {
106
+ return true;
107
+ }
108
+ }
109
+ return matchType !== MatchType.Should;
110
+ };
111
+ const matchBool = (context, filters) => {
112
+ const { must_not, should, must, filter } = filters;
113
+ if ((must === null || must === void 0 ? void 0 : must.length) && !matchFilters(context, must, MatchType.Must)) {
114
+ return false;
115
+ }
116
+ if ((filter === null || filter === void 0 ? void 0 : filter.length) && !matchFilters(context, filter, MatchType.Must)) {
117
+ return false;
118
+ }
119
+ if ((must_not === null || must_not === void 0 ? void 0 : must_not.length) && !matchFilters(context, must_not, MatchType.MustNot)) {
120
+ return false;
121
+ }
122
+ if ((should === null || should === void 0 ? void 0 : should.length) && !matchFilters(context, should, MatchType.Should)) {
123
+ return false;
124
+ }
125
+ return true;
126
+ };
127
+ const memorySearchTheBadWay = () => ({ store, mappings }) => {
43
128
  return {
44
129
  // This method is intentionally stubbed because index deletion is not applicable for in-memory storage.
45
130
  deleteIndexIfExists: async () => undefined,
46
131
  ensureIndexCreated: async () => undefined,
47
132
  index: async (_options) => undefined,
133
+ bulkIndex: async (_items) => undefined,
48
134
  search: async (options) => {
49
135
  const results = (await store.loadAllDocumentsTheBadWay())
50
136
  .map(document => {
51
137
  let weight = 0;
52
- const matchFilters = (filters, matchType) => {
53
- for (const field of filters) {
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');
64
- if ((matchType === MatchType.Must && !hasMatch) || (matchType === MatchType.MustNot && hasMatch)) {
65
- return false;
66
- }
67
- else if (matchType === MatchType.Should && hasMatch) {
68
- return true;
69
- }
70
- }
71
- return matchType !== MatchType.Should;
72
- };
73
138
  if (options.query !== undefined) {
74
139
  for (const field of options.fields) {
75
140
  if (field.type !== undefined && field.type !== 'text') {
76
141
  continue;
77
142
  }
78
- const value = resolveField(document, field.key);
143
+ const value = resolveField({ document, fields: mappings, scope: '' }, field.key);
79
144
  if (value === undefined || value === null) {
80
145
  continue;
81
146
  }
@@ -89,15 +154,7 @@ const memorySearchTheBadWay = () => ({ store }) => {
89
154
  }
90
155
  }
91
156
  }
92
- const { must_not, should } = options;
93
- const must = 'filter' in options ? options.filter : options.must;
94
- if ((must === null || must === void 0 ? void 0 : must.length) && !matchFilters(must, MatchType.Must)) {
95
- return undefined;
96
- }
97
- if ((must_not === null || must_not === void 0 ? void 0 : must_not.length) && !matchFilters(must_not, MatchType.MustNot)) {
98
- return undefined;
99
- }
100
- if ((should === null || should === void 0 ? void 0 : should.length) && !matchFilters(should, MatchType.Should)) {
157
+ if (!matchBool({ document, fields: mappings, scope: '' }, options)) {
101
158
  return undefined;
102
159
  }
103
160
  return { document, weight };
@@ -106,8 +163,8 @@ const memorySearchTheBadWay = () => ({ store }) => {
106
163
  .filter(r => !options.query || r.weight >= MIN_MATCH);
107
164
  results.sort((a, b) => {
108
165
  for (const sort of (options.sort || [])) {
109
- const aValue = resolveField(a.document, sort.key);
110
- const bValue = resolveField(b.document, sort.key);
166
+ const aValue = resolveField({ document: a.document, fields: mappings, scope: '' }, sort.key);
167
+ const bValue = resolveField({ document: b.document, fields: mappings, scope: '' }, sort.key);
111
168
  if (aValue < bValue) {
112
169
  return sort.order === 'asc' ? -1 : 1;
113
170
  }
@@ -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]: {
@@ -19,6 +19,7 @@ export declare const openSearchService: <C extends string = "deployed">(initiali
19
19
  ensureIndexCreated: () => Promise<void>;
20
20
  deleteIndexIfExists: () => Promise<void>;
21
21
  index: (params: IndexOptions<T>) => Promise<void>;
22
+ bulkIndex: (items: IndexOptions<T>[]) => Promise<void>;
22
23
  search: (options: SearchOptions) => Promise<{
23
24
  items: Exclude<T, undefined>[];
24
25
  pageSize: number;
@@ -63,24 +63,39 @@ const openSearchService = (initializer = {}) => (configProvider) => {
63
63
  refresh: true
64
64
  });
65
65
  };
66
+ const bulkIndex = async (items) => {
67
+ const openSearchClient = await client();
68
+ await openSearchClient.bulk({
69
+ index: indexConfig.name,
70
+ body: items.flatMap((item) => [
71
+ { index: { _id: item.id } },
72
+ item.body
73
+ ]),
74
+ refresh: true
75
+ });
76
+ };
66
77
  const search = async (options) => {
78
+ var _a;
67
79
  const body = {
68
80
  query: { bool: {} },
69
81
  track_total_hits: true,
70
82
  size: pageSize
71
83
  };
72
84
  if (options.query) {
73
- body.query.bool.must = {
74
- multi_match: {
75
- fields: options.fields.map((field) => 'weight' in field ? `${field.key}^${field.weight}` : field.key),
76
- query: options.query
77
- }
78
- };
85
+ body.query.bool.must = [{
86
+ multi_match: {
87
+ fields: options.fields.map((field) => 'weight' in field ? `${field.key}^${field.weight}` : field.key),
88
+ query: options.query
89
+ }
90
+ }];
91
+ }
92
+ const { must_not, should, must, filter } = options;
93
+ if (filter && filter.length > 0) {
94
+ body.query.bool.filter = filter.map(mapFilter);
79
95
  }
80
- const { must_not, should } = options;
81
- const must = 'filter' in options ? options.filter : options.must;
82
96
  if (must && must.length > 0) {
83
- body.query.bool.filter = must.map(mapFilter);
97
+ (_a = body.query.bool).must || (_a.must = []);
98
+ body.query.bool.must = body.query.bool.must.concat(must.map(mapFilter));
84
99
  }
85
100
  if (must_not && must_not.length > 0) {
86
101
  body.query.bool.must_not = must_not.map(mapFilter);
@@ -112,7 +127,7 @@ const openSearchService = (initializer = {}) => (configProvider) => {
112
127
  const totalPages = Math.ceil(totalItems / pageSize) || 1;
113
128
  return { items, pageSize, currentPage, totalItems, totalPages };
114
129
  };
115
- return { ensureIndexCreated, deleteIndexIfExists, index, search };
130
+ return { ensureIndexCreated, deleteIndexIfExists, index, bulkIndex, search };
116
131
  };
117
132
  };
118
133
  exports.openSearchService = openSearchService;