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