@natlibfi/melinda-record-matching 4.4.0-alpha.1 → 5.0.0-alpha.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 (123) hide show
  1. package/.github/workflows/melinda-node-tests.yml +13 -9
  2. package/LICENSE.txt +21 -165
  3. package/README.md +2 -2
  4. package/dist/candidate-search/candidate-search-utils.js +6 -43
  5. package/dist/candidate-search/candidate-search-utils.js.map +7 -1
  6. package/dist/candidate-search/choose-queries.js +75 -0
  7. package/dist/candidate-search/choose-queries.js.map +7 -0
  8. package/dist/candidate-search/index.js +89 -225
  9. package/dist/candidate-search/index.js.map +7 -1
  10. package/dist/candidate-search/index.test.js +92 -0
  11. package/dist/candidate-search/index.test.js.map +7 -0
  12. package/dist/candidate-search/query-list/bib.js +141 -320
  13. package/dist/candidate-search/query-list/bib.js.map +7 -1
  14. package/dist/candidate-search/query-list/bib.test.js +34 -0
  15. package/dist/candidate-search/query-list/bib.test.js.map +7 -0
  16. package/dist/candidate-search/query-list/component.js +87 -0
  17. package/dist/candidate-search/query-list/component.js.map +7 -0
  18. package/dist/candidate-search/query-list/index.js +39 -52
  19. package/dist/candidate-search/query-list/index.js.map +7 -1
  20. package/dist/cli.js +138 -0
  21. package/dist/cli.js.map +7 -0
  22. package/dist/index.js +87 -462
  23. package/dist/index.js.map +7 -1
  24. package/dist/index.test.js +69 -0
  25. package/dist/index.test.js.map +7 -0
  26. package/dist/match-detection/features/bib/all-source-ids.js +20 -91
  27. package/dist/match-detection/features/bib/all-source-ids.js.map +7 -1
  28. package/dist/match-detection/features/bib/authors.js +20 -76
  29. package/dist/match-detection/features/bib/authors.js.map +7 -1
  30. package/dist/match-detection/features/bib/bibliographic-level.js +4 -40
  31. package/dist/match-detection/features/bib/bibliographic-level.js.map +7 -1
  32. package/dist/match-detection/features/bib/host-component.js +5 -41
  33. package/dist/match-detection/features/bib/host-component.js.map +7 -1
  34. package/dist/match-detection/features/bib/index.js +17 -119
  35. package/dist/match-detection/features/bib/index.js.map +7 -1
  36. package/dist/match-detection/features/bib/index.test.js +40 -0
  37. package/dist/match-detection/features/bib/index.test.js.map +7 -0
  38. package/dist/match-detection/features/bib/isbn.js +13 -69
  39. package/dist/match-detection/features/bib/isbn.js.map +7 -1
  40. package/dist/match-detection/features/bib/issn.js +5 -50
  41. package/dist/match-detection/features/bib/issn.js.map +7 -1
  42. package/dist/match-detection/features/bib/language.js +25 -86
  43. package/dist/match-detection/features/bib/language.js.map +7 -1
  44. package/dist/match-detection/features/bib/media-type.js +12 -64
  45. package/dist/match-detection/features/bib/media-type.js.map +7 -1
  46. package/dist/match-detection/features/bib/melinda-id.js +5 -47
  47. package/dist/match-detection/features/bib/melinda-id.js.map +7 -1
  48. package/dist/match-detection/features/bib/melinda-identifier-factory.js +16 -67
  49. package/dist/match-detection/features/bib/melinda-identifier-factory.js.map +7 -1
  50. package/dist/match-detection/features/bib/other-standard-identifier.js +5 -51
  51. package/dist/match-detection/features/bib/other-standard-identifier.js.map +7 -1
  52. package/dist/match-detection/features/bib/publication-time-allow-cons-years-multi.js +49 -189
  53. package/dist/match-detection/features/bib/publication-time-allow-cons-years-multi.js.map +7 -1
  54. package/dist/match-detection/features/bib/publication-time-allow-cons-years.js +10 -53
  55. package/dist/match-detection/features/bib/publication-time-allow-cons-years.js.map +7 -1
  56. package/dist/match-detection/features/bib/publication-time.js +8 -46
  57. package/dist/match-detection/features/bib/publication-time.js.map +7 -1
  58. package/dist/match-detection/features/bib/record-type.js +4 -43
  59. package/dist/match-detection/features/bib/record-type.js.map +7 -1
  60. package/dist/match-detection/features/bib/standard-identifier-factory.js +47 -163
  61. package/dist/match-detection/features/bib/standard-identifier-factory.js.map +7 -1
  62. package/dist/match-detection/features/bib/title-version-original.js +11 -53
  63. package/dist/match-detection/features/bib/title-version-original.js.map +7 -1
  64. package/dist/match-detection/features/bib/title.js +20 -72
  65. package/dist/match-detection/features/bib/title.js.map +7 -1
  66. package/dist/match-detection/features/index.js +3 -11
  67. package/dist/match-detection/features/index.js.map +7 -1
  68. package/dist/match-detection/index.js +35 -150
  69. package/dist/match-detection/index.js.map +7 -1
  70. package/dist/match-detection/index.test.js +44 -0
  71. package/dist/match-detection/index.test.js.map +7 -0
  72. package/dist/matching-utils.js +38 -102
  73. package/dist/matching-utils.js.map +7 -1
  74. package/eslint.config.js +52 -0
  75. package/example.env +4 -0
  76. package/package.json +29 -78
  77. package/src/candidate-search/candidate-search-utils.js +1 -28
  78. package/src/candidate-search/choose-queries.js +93 -0
  79. package/src/candidate-search/index.js +85 -119
  80. package/src/candidate-search/{index.spec.js → index.test.js} +30 -43
  81. package/src/candidate-search/query-list/bib.js +54 -55
  82. package/src/candidate-search/query-list/bib.test.js +38 -0
  83. package/src/candidate-search/query-list/component.js +127 -0
  84. package/src/candidate-search/query-list/index.js +31 -35
  85. package/src/cli.js +177 -0
  86. package/src/index.js +27 -68
  87. package/src/{index.spec.js → index.test.js} +11 -42
  88. package/src/match-detection/features/bib/all-source-ids.js +2 -29
  89. package/src/match-detection/features/bib/authors.js +5 -30
  90. package/src/match-detection/features/bib/bibliographic-level.js +0 -27
  91. package/src/match-detection/features/bib/host-component.js +0 -27
  92. package/src/match-detection/features/bib/index.js +16 -43
  93. package/src/match-detection/features/bib/index.test.js +52 -0
  94. package/src/match-detection/features/bib/isbn.js +2 -29
  95. package/src/match-detection/features/bib/issn.js +1 -28
  96. package/src/match-detection/features/bib/language.js +5 -32
  97. package/src/match-detection/features/bib/media-type.js +2 -28
  98. package/src/match-detection/features/bib/melinda-id.js +1 -28
  99. package/src/match-detection/features/bib/melinda-identifier-factory.js +5 -32
  100. package/src/match-detection/features/bib/other-standard-identifier.js +1 -28
  101. package/src/match-detection/features/bib/publication-time-allow-cons-years-multi.js +7 -34
  102. package/src/match-detection/features/bib/publication-time-allow-cons-years.js +1 -28
  103. package/src/match-detection/features/bib/publication-time.js +2 -28
  104. package/src/match-detection/features/bib/record-type.js +0 -27
  105. package/src/match-detection/features/bib/standard-identifier-factory.js +1 -28
  106. package/src/match-detection/features/bib/title-version-original.js +2 -28
  107. package/src/match-detection/features/bib/title.js +6 -34
  108. package/src/match-detection/features/index.js +1 -28
  109. package/src/match-detection/index.js +1 -28
  110. package/src/match-detection/{index.spec.js → index.test.js} +9 -35
  111. package/src/matching-utils.js +8 -37
  112. package/dist/candidate-search/index.spec.js +0 -144
  113. package/dist/candidate-search/index.spec.js.map +0 -1
  114. package/dist/candidate-search/query-list/bib.spec.js +0 -64
  115. package/dist/candidate-search/query-list/bib.spec.js.map +0 -1
  116. package/dist/index.spec.js +0 -127
  117. package/dist/index.spec.js.map +0 -1
  118. package/dist/match-detection/features/bib/index.spec.js +0 -93
  119. package/dist/match-detection/features/bib/index.spec.js.map +0 -1
  120. package/dist/match-detection/index.spec.js +0 -91
  121. package/dist/match-detection/index.spec.js.map +0 -1
  122. package/src/candidate-search/query-list/bib.spec.js +0 -54
  123. package/src/match-detection/features/bib/index.spec.js +0 -78
@@ -1,254 +1,118 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = exports.CandidateSearchError = void 0;
7
- Object.defineProperty(exports, "searchTypes", {
8
- enumerable: true,
9
- get: function () {
10
- return _queryList.searchTypes;
11
- }
12
- });
13
- var _debug = _interopRequireDefault(require("debug"));
14
- var _sruClient = _interopRequireWildcard(require("@natlibfi/sru-client"));
15
- var _marcRecord = require("@natlibfi/marc-record");
16
- var _marcRecordSerializers = require("@natlibfi/marc-record-serializers");
17
- var _queryList = _interopRequireWildcard(require("./query-list"));
18
- var _melindaCommons = require("@natlibfi/melinda-commons");
19
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
20
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
21
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
- /**
23
- *
24
- * @licstart The following is the entire license notice for the JavaScript code in this file.
25
- *
26
- * Melinda record matching modules for Javascript
27
- *
28
- * Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)
29
- *
30
- * This file is part of melinda-record-matching-js
31
- *
32
- * melinda-record-matching-js program is free software: you can redistribute it and/or modify
33
- * it under the terms of the GNU Lesser General Public License as
34
- * published by the Free Software Foundation, either version 3 of the
35
- * License, or (at your option) any later version.
36
- *
37
- * melinda-record-matching-js is distributed in the hope that it will be useful,
38
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
39
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40
- * GNU Lesser General Public License for more details.
41
- *
42
- * You should have received a copy of the GNU Affero General Public License
43
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
44
- *
45
- * @licend The above is the entire license notice
46
- * for the JavaScript code in this file.
47
- *
48
- */
49
-
50
- class CandidateSearchError extends Error {}
51
-
52
- // serverMaxResults : maximum size of total search result available from the server, defaults to Aleph's 20000
53
- exports.CandidateSearchError = CandidateSearchError;
54
- var _default = ({
55
- record,
56
- searchSpec,
57
- url,
58
- maxCandidates,
59
- maxRecordsPerRequest = 50,
60
- serverMaxResult = 20000
61
- }) => {
62
- _marcRecord.MarcRecord.setValidationOptions({
63
- subfieldValues: false
64
- });
65
- const debug = (0, _debug.default)('@natlibfi/melinda-record-matching:candidate-search');
66
- const debugData = debug.extend('data');
1
+ import createDebugLogger from "debug";
2
+ import createClient, { SruSearchError } from "@natlibfi/sru-client";
3
+ import { MarcRecord } from "@natlibfi/marc-record";
4
+ import generateQueryList from "./query-list/index.js";
5
+ import chooseQueries from "./choose-queries.js";
6
+ export { searchTypes } from "./query-list/index.js";
7
+ export class CandidateSearchError extends Error {
8
+ }
9
+ export default async ({ record, searchSpec, url, maxCandidates, maxRecordsPerRequest = 50, serverMaxResult = 2e4 }) => {
10
+ MarcRecord.setValidationOptions({ subfieldValues: false });
11
+ const debug = createDebugLogger("@natlibfi/melinda-record-matching:candidate-search");
12
+ const debugData = debug.extend("data");
67
13
  debugData(`SearchSpec: ${JSON.stringify(searchSpec)}`);
68
14
  debugData(`Url: ${url}`);
69
15
  debugData(`MaxRecordsPerRequest ${maxRecordsPerRequest}`);
70
16
  debugData(`ServerMaxResult: ${serverMaxResult}`);
71
17
  debugData(`MaxCandidates: ${maxCandidates}`);
72
-
73
- // Do not retrieve more candidates than defined in maxCandidates
74
18
  const adjustedMaxRecordsPerRequest = maxRecordsPerRequest >= maxCandidates ? maxCandidates : maxRecordsPerRequest;
75
- const inputRecordId = getRecordId(record);
76
- const queryList = (0, _queryList.default)(record, searchSpec);
77
- const client = (0, _sruClient.default)({
19
+ const client = createClient({
78
20
  url,
79
21
  maxRecordsPerRequest: adjustedMaxRecordsPerRequest,
80
- version: '2.0',
81
- retrieveAll: false
22
+ version: "2.0",
23
+ retrieveAll: false,
24
+ metadataFormat: "marcJson"
82
25
  });
83
- debug(`Searching matches for ${inputRecordId}`);
84
- debug(`Generated queryList ${JSON.stringify(queryList)}`);
85
-
86
- // if generateQueryList errored we should throw 422
87
-
26
+ const inputRecordId = getRecordId(record);
27
+ const queryListResult = await generateQueryList(record, searchSpec, client);
28
+ const queryList = queryListResult[0]?.queryList ? queryListResult[0].queryList : queryListResult;
29
+ const queryListType = queryListResult[0]?.queryListType ? queryListResult[0].queryListType : void 0;
88
30
  if (queryList.length === 0) {
31
+ debug(`Empty list`);
89
32
  throw new CandidateSearchError(`Generated query list contains no queries`);
90
33
  }
91
-
92
- // state.totalRecords : amount of candidate records available to the current query (undefined, if there was no queries left)
93
- // state.query : current query (undefined if there was no queries left)
94
- // state.searchCounter : sequence for current search for current query (undefined, if there we no queries left)
95
- // state.queryCandidateCounter: amount of candidates (records+failures) retrieved from SRU for matching for current query, including the current record+failure set (undefined if there were no queries left)
96
- // state.queriesLeft : amount of queries left
97
- // state.queryCounter : sequence for current query
98
- // state.maxedQueries : queries that resulted in more than serverMaxResults hits
99
-
100
- return async ({
101
- queryOffset = 0,
102
- resultSetOffset = 1,
103
- totalRecords = 0,
104
- searchCounter = 0,
105
- queryCandidateCounter = 0,
106
- queryCounter = 0,
107
- maxedQueries = []
108
- }) => {
109
- const query = queryList[queryOffset];
34
+ if (queryListType && queryListType !== "alternates") {
35
+ debug(`Unknown queryListType`);
36
+ throw new CandidateSearchError(`Generated query list has invalid type`);
37
+ }
38
+ debug(`Searching matches for ${inputRecordId}`);
39
+ const chosenQueryList = await filterQueryList({ queryList, queryListType });
40
+ debug(`Chosen queries: ${JSON.stringify(chosenQueryList)}`);
41
+ async function filterQueryList({ queryList: queryList2, queryListType: queryListType2, maxCandidates: maxCandidates2 }) {
42
+ debug(`Generated queryList (type: ${queryListType2}) ${JSON.stringify(queryList2)}`);
43
+ if (queryListType2 === "alternates" && queryList2.length > 1) {
44
+ const queryListResult2 = await chooseQueries({ url, queryList: queryList2, queryListType: queryListType2, maxCandidates: maxCandidates2 });
45
+ debug(`queryListResult: ${JSON.stringify(queryListResult2)}`);
46
+ return queryListResult2.map((elem) => elem.query);
47
+ }
48
+ return queryList2;
49
+ }
50
+ return { search };
51
+ async function search({ queryOffset = 0, resultSetOffset = 1, totalRecords = 0, searchCounter = 0, queryCandidateCounter = 0, queryCounter = 0, maxedQueries = [] }) {
52
+ const query = chosenQueryList[queryOffset];
53
+ debug(`Running query ${JSON.stringify(query)} (${queryOffset})`);
110
54
  if (query) {
111
- const {
112
- records,
113
- failures,
114
- nextOffset,
115
- total
116
- } = await retrieveRecords();
117
-
118
- // If resultSetOffset === 1 this is the first search for the current query
55
+ const { records, nextOffset, total } = await retrieveRecords(client, query, resultSetOffset);
119
56
  debugData(`ResultSetOffset: ${resultSetOffset}`);
120
57
  const newTotalRecords = resultSetOffset === 1 ? total : totalRecords;
121
58
  const newQueryCounter = resultSetOffset === 1 ? queryCounter + 1 : queryCounter;
122
59
  const newSearchCounter = resultSetOffset === 1 ? 1 : searchCounter + 1;
123
- const newQueryCandidateCounter = resultSetOffset === 1 ? records.length + failures.length : queryCandidateCounter + records.length + failures.length;
124
- const maxedQuery = resultSetOffset === 1 ? checkMaxedQuery(query, total, serverMaxResult) : undefined;
60
+ const newQueryCandidateCounter = resultSetOffset === 1 ? records.length : queryCandidateCounter + records.length;
61
+ const maxedQuery = resultSetOffset === 1 ? checkMaxedQuery(query, total, serverMaxResult) : void 0;
125
62
  const newMaxedQueries = maxedQuery ? maxedQueries.concat(maxedQuery) : maxedQueries;
126
- if (typeof nextOffset === 'number') {
63
+ if (typeof nextOffset === "number") {
127
64
  debug(`Next search will be for query ${queryOffset} ${query}, starting from record ${nextOffset}`);
128
- return {
129
- records,
130
- failures,
131
- queryOffset,
132
- resultSetOffset: nextOffset,
133
- queriesLeft: queryList.length - (queryOffset + 1),
134
- totalRecords: newTotalRecords,
135
- query,
136
- searchCounter: newSearchCounter,
137
- queryCandidateCounter: newQueryCandidateCounter,
138
- queryCounter: newQueryCounter,
139
- maxedQueries: newMaxedQueries
140
- };
65
+ return { records, queryOffset, resultSetOffset: nextOffset, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries };
141
66
  }
142
67
  debug(`Query ${queryOffset} ${query} done.`);
143
68
  debug(`There are (${queryList.length - (queryOffset + 1)} queries left)`);
144
- return {
145
- records,
146
- failures,
147
- queryOffset: queryOffset + 1,
148
- queriesLeft: queryList.length - (queryOffset + 1),
149
- totalRecords: newTotalRecords,
150
- query,
151
- searchCounter: newSearchCounter,
152
- queryCandidateCounter: newQueryCandidateCounter,
153
- queryCounter: newQueryCounter,
154
- maxedQueries: newMaxedQueries
155
- };
69
+ return { records, queryOffset: queryOffset + 1, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries };
156
70
  }
157
71
  debug(`All ${queryList.length} queries done, there's no query for ${queryOffset}`);
158
- return {
159
- records: [],
160
- failures: [],
161
- queriesLeft: 0,
162
- queryCounter,
163
- maxedQueries
164
- };
165
- function retrieveRecords() {
166
- return new Promise((resolve, reject) => {
167
- const promises = [];
168
- // eslint-disable-next-line functional/no-let
169
- let totalRecords = 0;
170
- debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);
171
- client.searchRetrieve(query, {
172
- startRecord: resultSetOffset
173
- }).on('error', err => {
174
- // eslint-disable-next-line functional/no-conditional-statements
175
- if (err instanceof _sruClient.SruSearchError) {
176
- debug(`SRU SruSearchError for query: ${query}: ${err}`);
177
- reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));
178
- }
179
- debug(`SRU error for query: ${query}: ${err}`);
180
- reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));
181
- }).on('total', total => {
182
- debug(`Got total: ${total}`);
183
- totalRecords += total;
184
- }).on('end', async nextOffset => {
185
- try {
186
- const recordPromises = await Promise.allSettled(promises);
187
- debugData(`All recordPromises: ${JSON.stringify(recordPromises)}`);
188
- const filtered = recordPromises.filter(r => r.status === 'fulfilled').map(r => r.value);
189
- const failures = recordPromises.filter(r => r.status === 'rejected').map(r => ({
190
- status: r.reason.status,
191
- payload: r.reason.payload
192
- }));
193
- debug(`Found ${recordPromises.length} records`);
194
- debug(`Found ${filtered.length} convertable candidates`);
195
- debug(`Found ${failures.length} NON-convertable candidates`);
196
- debugData(`Converted: ${JSON.stringify(filtered)}.`);
197
- debugData(`Not converted: ${JSON.stringify(failures)}.`);
198
- resolve({
199
- nextOffset,
200
- records: filtered,
201
- failures,
202
- total: totalRecords
203
- });
204
- } catch (err) {
205
- debug(`Error caught on END`);
206
- reject(err);
207
- }
208
- }).on('record', recordXML => {
209
- promises.push(handleRecord()); // eslint-disable-line functional/immutable-data
210
-
211
- async function handleRecord() {
212
- try {
213
- const recordMarc = await _marcRecordSerializers.MARCXML.from(recordXML, {
214
- subfieldValues: false
215
- });
216
- const recordId = getRecordId(recordMarc);
217
- return {
218
- record: recordMarc,
219
- id: recordId
220
- };
221
- } catch (err) {
222
- // What should this do?
223
- const idFromXML = getRecordIdFromXML(recordXML);
224
- debugData(`Failed converting record: ${err.message}, id: ${idFromXML}, data: ${recordXML}`);
225
- //return {message: `Failed converting record: ${err.message}`, id: idFromXML, data: recordXML};
226
- throw new _melindaCommons.Error(422, {
227
- message: `Failed converting record: ${err.message}`,
228
- id: idFromXML || '000000000',
229
- data: recordXML
230
- });
231
- }
232
- }
233
- });
234
- });
235
- }
236
- };
237
- function checkMaxedQuery(query, total, serverMaxResult) {
238
- if (total >= serverMaxResult) {
239
- debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult}) `);
72
+ return { records: [], queriesLeft: 0, queryCounter, maxedQueries };
73
+ }
74
+ function checkMaxedQuery(query, total, serverMaxResult2) {
75
+ if (total >= serverMaxResult2) {
76
+ debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult2}) `);
240
77
  return query;
241
78
  }
242
79
  }
243
- function getRecordId(record) {
244
- const [field] = record.get(/^001$/u);
245
- return field ? field.value : '';
246
- }
247
- function getRecordIdFromXML(recordXML) {
248
- //<controlfield tag=\"001\">015376846</controlfield
249
- debug(`Cannot yet find possible database record id from recordXML (length ${recordXML.length})`);
250
- return undefined;
80
+ function getRecordId(record2) {
81
+ const [field] = record2.get(/^001$/u);
82
+ return field ? field.value : "";
251
83
  }
252
84
  };
253
- exports.default = _default;
254
- //# sourceMappingURL=index.js.map
85
+ export function retrieveRecords(client, query, resultSetOffset) {
86
+ const debug = createDebugLogger("@natlibfi/melinda-record-matching:candidate-search:retrieveRecords");
87
+ const debugData = debug.extend("data");
88
+ return new Promise((resolve, reject) => {
89
+ const records = [];
90
+ let totalRecords = 0;
91
+ debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);
92
+ client.searchRetrieve(query, { startRecord: resultSetOffset }).on("error", (err) => {
93
+ if (err instanceof SruSearchError) {
94
+ debug(`SRU SruSearchError for query: ${query}: ${err}`);
95
+ reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));
96
+ }
97
+ debug(`SRU error for query: ${query}: ${err}`);
98
+ reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));
99
+ }).on("total", (total) => {
100
+ debug(`Got total: ${total}`);
101
+ totalRecords += total;
102
+ }).on("end", (nextOffset) => {
103
+ try {
104
+ debug(`Found ${records.length} records`);
105
+ resolve({ nextOffset, records, total: totalRecords });
106
+ } catch (err) {
107
+ debug(`Error caught on END`);
108
+ reject(err);
109
+ }
110
+ }).on("record", (record) => {
111
+ const [field] = record.get(/^001$/u);
112
+ debug(field);
113
+ const id = field.value ? field.value : "";
114
+ records.push({ record, id });
115
+ });
116
+ });
117
+ }
118
+ //# sourceMappingURL=index.js.map
@@ -1 +1,7 @@
1
- {"version":3,"file":"index.js","names":["_debug","_interopRequireDefault","require","_sruClient","_interopRequireWildcard","_marcRecord","_marcRecordSerializers","_queryList","_melindaCommons","_getRequireWildcardCache","e","WeakMap","r","t","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","prototype","hasOwnProperty","call","i","set","obj","CandidateSearchError","Error","exports","_default","record","searchSpec","url","maxCandidates","maxRecordsPerRequest","serverMaxResult","MarcRecord","setValidationOptions","subfieldValues","debug","createDebugLogger","debugData","extend","JSON","stringify","adjustedMaxRecordsPerRequest","inputRecordId","getRecordId","queryList","generateQueryList","client","createClient","version","retrieveAll","length","queryOffset","resultSetOffset","totalRecords","searchCounter","queryCandidateCounter","queryCounter","maxedQueries","query","records","failures","nextOffset","total","retrieveRecords","newTotalRecords","newQueryCounter","newSearchCounter","newQueryCandidateCounter","maxedQuery","checkMaxedQuery","undefined","newMaxedQueries","concat","queriesLeft","Promise","resolve","reject","promises","searchRetrieve","startRecord","on","err","SruSearchError","recordPromises","allSettled","filtered","filter","status","map","value","reason","payload","recordXML","push","handleRecord","recordMarc","MARCXML","from","recordId","id","idFromXML","getRecordIdFromXML","message","MatchingError","data","field"],"sources":["../../src/candidate-search/index.js"],"sourcesContent":["/**\n*\n* @licstart The following is the entire license notice for the JavaScript code in this file.\n*\n* Melinda record matching modules for Javascript\n*\n* Copyright (C) 2020-2022 University Of Helsinki (The National Library Of Finland)\n*\n* This file is part of melinda-record-matching-js\n*\n* melinda-record-matching-js program is free software: you can redistribute it and/or modify\n* it under the terms of the GNU Lesser General Public License as\n* published by the Free Software Foundation, either version 3 of the\n* License, or (at your option) any later version.\n*\n* melinda-record-matching-js is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n* GNU Lesser General Public License for more details.\n*\n* You should have received a copy of the GNU Affero General Public License\n* along with this program. If not, see <http://www.gnu.org/licenses/>.\n*\n* @licend The above is the entire license notice\n* for the JavaScript code in this file.\n*\n*/\n\nimport createDebugLogger from 'debug';\nimport createClient, {SruSearchError} from '@natlibfi/sru-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {MARCXML} from '@natlibfi/marc-record-serializers';\nimport generateQueryList from './query-list';\nimport {Error as MatchingError} from '@natlibfi/melinda-commons';\n\nexport {searchTypes} from './query-list';\n\nexport class CandidateSearchError extends Error {}\n\n// serverMaxResults : maximum size of total search result available from the server, defaults to Aleph's 20000\n\nexport default ({record, searchSpec, url, maxCandidates, maxRecordsPerRequest = 50, serverMaxResult = 20000}) => {\n MarcRecord.setValidationOptions({subfieldValues: false});\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search');\n const debugData = debug.extend('data');\n\n debugData(`SearchSpec: ${JSON.stringify(searchSpec)}`);\n debugData(`Url: ${url}`);\n debugData(`MaxRecordsPerRequest ${maxRecordsPerRequest}`);\n debugData(`ServerMaxResult: ${serverMaxResult}`);\n debugData(`MaxCandidates: ${maxCandidates}`);\n\n // Do not retrieve more candidates than defined in maxCandidates\n const adjustedMaxRecordsPerRequest = maxRecordsPerRequest >= maxCandidates ? maxCandidates : maxRecordsPerRequest;\n\n const inputRecordId = getRecordId(record);\n const queryList = generateQueryList(record, searchSpec);\n const client = createClient({\n url,\n maxRecordsPerRequest: adjustedMaxRecordsPerRequest,\n version: '2.0',\n retrieveAll: false\n });\n\n debug(`Searching matches for ${inputRecordId}`);\n debug(`Generated queryList ${JSON.stringify(queryList)}`);\n\n\n // if generateQueryList errored we should throw 422\n\n if (queryList.length === 0) {\n throw new CandidateSearchError(`Generated query list contains no queries`);\n }\n\n // state.totalRecords : amount of candidate records available to the current query (undefined, if there was no queries left)\n // state.query : current query (undefined if there was no queries left)\n // state.searchCounter : sequence for current search for current query (undefined, if there we no queries left)\n // state.queryCandidateCounter: amount of candidates (records+failures) retrieved from SRU for matching for current query, including the current record+failure set (undefined if there were no queries left)\n // state.queriesLeft : amount of queries left\n // state.queryCounter : sequence for current query\n // state.maxedQueries : queries that resulted in more than serverMaxResults hits\n\n\n return async ({queryOffset = 0, resultSetOffset = 1, totalRecords = 0, searchCounter = 0, queryCandidateCounter = 0, queryCounter = 0, maxedQueries = []}) => {\n const query = queryList[queryOffset];\n\n if (query) {\n const {records, failures, nextOffset, total} = await retrieveRecords();\n\n // If resultSetOffset === 1 this is the first search for the current query\n debugData(`ResultSetOffset: ${resultSetOffset}`);\n const newTotalRecords = resultSetOffset === 1 ? total : totalRecords;\n const newQueryCounter = resultSetOffset === 1 ? queryCounter + 1 : queryCounter;\n const newSearchCounter = resultSetOffset === 1 ? 1 : searchCounter + 1;\n const newQueryCandidateCounter = resultSetOffset === 1 ? records.length + failures.length : queryCandidateCounter + records.length + failures.length;\n\n const maxedQuery = resultSetOffset === 1 ? checkMaxedQuery(query, total, serverMaxResult) : undefined;\n const newMaxedQueries = maxedQuery ? maxedQueries.concat(maxedQuery) : maxedQueries;\n\n if (typeof nextOffset === 'number') {\n debug(`Next search will be for query ${queryOffset} ${query}, starting from record ${nextOffset}`);\n return {records, failures, queryOffset, resultSetOffset: nextOffset, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries};\n }\n debug(`Query ${queryOffset} ${query} done.`);\n debug(`There are (${queryList.length - (queryOffset + 1)} queries left)`);\n return {records, failures, queryOffset: queryOffset + 1, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries};\n }\n\n debug(`All ${queryList.length} queries done, there's no query for ${queryOffset}`);\n return {records: [], failures: [], queriesLeft: 0, queryCounter, maxedQueries};\n\n function retrieveRecords() {\n return new Promise((resolve, reject) => {\n const promises = [];\n // eslint-disable-next-line functional/no-let\n let totalRecords = 0;\n\n debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);\n\n client.searchRetrieve(query, {startRecord: resultSetOffset})\n .on('error', err => {\n // eslint-disable-next-line functional/no-conditional-statements\n if (err instanceof SruSearchError) {\n debug(`SRU SruSearchError for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));\n }\n debug(`SRU error for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));\n })\n .on('total', total => {\n debug(`Got total: ${total}`);\n totalRecords += total;\n })\n .on('end', async nextOffset => {\n try {\n const recordPromises = await Promise.allSettled(promises);\n debugData(`All recordPromises: ${JSON.stringify(recordPromises)}`);\n const filtered = recordPromises.filter(r => r.status === 'fulfilled').map(r => r.value);\n const failures = recordPromises.filter(r => r.status === 'rejected').map(r => ({status: r.reason.status, payload: r.reason.payload}));\n\n debug(`Found ${recordPromises.length} records`);\n debug(`Found ${filtered.length} convertable candidates`);\n debug(`Found ${failures.length} NON-convertable candidates`);\n debugData(`Converted: ${JSON.stringify(filtered)}.`);\n debugData(`Not converted: ${JSON.stringify(failures)}.`);\n\n\n resolve({nextOffset, records: filtered, failures, total: totalRecords});\n } catch (err) {\n debug(`Error caught on END`);\n reject(err);\n }\n })\n .on('record', recordXML => {\n promises.push(handleRecord()); // eslint-disable-line functional/immutable-data\n\n async function handleRecord() {\n try {\n const recordMarc = await MARCXML.from(recordXML, {subfieldValues: false});\n const recordId = getRecordId(recordMarc);\n\n return {record: recordMarc, id: recordId};\n } catch (err) {\n // What should this do?\n const idFromXML = getRecordIdFromXML(recordXML);\n debugData(`Failed converting record: ${err.message}, id: ${idFromXML}, data: ${recordXML}`);\n //return {message: `Failed converting record: ${err.message}`, id: idFromXML, data: recordXML};\n throw new MatchingError(422, {message: `Failed converting record: ${err.message}`, id: idFromXML || '000000000', data: recordXML});\n }\n }\n });\n });\n }\n };\n\n function checkMaxedQuery(query, total, serverMaxResult) {\n if (total >= serverMaxResult) {\n debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult}) `);\n return query;\n }\n }\n\n\n function getRecordId(record) {\n const [field] = record.get(/^001$/u);\n return field ? field.value : '';\n }\n\n function getRecordIdFromXML(recordXML) {\n //<controlfield tag=\\\"001\\\">015376846</controlfield\n debug(`Cannot yet find possible database record id from recordXML (length ${recordXML.length})`);\n return undefined;\n }\n\n};\n"],"mappings":"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"}
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/candidate-search/index.js"],
4
+ "sourcesContent": ["import createDebugLogger from 'debug';\nimport createClient, {SruSearchError} from '@natlibfi/sru-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\n\nimport generateQueryList from './query-list/index.js';\nimport chooseQueries from './choose-queries.js';\n\nexport {searchTypes} from './query-list/index.js';\n\nexport class CandidateSearchError extends Error { }\n\n// serverMaxResults : maximum size of total search result available from the server, defaults to Aleph's 20000\n\nexport default async ({record, searchSpec, url, maxCandidates, maxRecordsPerRequest = 50, serverMaxResult = 20000}) => {\n MarcRecord.setValidationOptions({subfieldValues: false});\n\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search');\n const debugData = debug.extend('data');\n\n debugData(`SearchSpec: ${JSON.stringify(searchSpec)}`);\n debugData(`Url: ${url}`);\n debugData(`MaxRecordsPerRequest ${maxRecordsPerRequest}`);\n debugData(`ServerMaxResult: ${serverMaxResult}`);\n debugData(`MaxCandidates: ${maxCandidates}`);\n\n // Do not retrieve more candidates than defined in maxCandidates\n const adjustedMaxRecordsPerRequest = maxRecordsPerRequest >= maxCandidates ? maxCandidates : maxRecordsPerRequest;\n\n const client = createClient({\n url,\n maxRecordsPerRequest: adjustedMaxRecordsPerRequest,\n version: '2.0',\n retrieveAll: false,\n metadataFormat: 'marcJson'\n });\n\n const inputRecordId = getRecordId(record);\n const queryListResult = await generateQueryList(record, searchSpec, client);\n const queryList = queryListResult[0]?.queryList ? queryListResult[0].queryList : queryListResult;\n const queryListType = queryListResult[0]?.queryListType ? queryListResult[0].queryListType : undefined;\n\n // if generateQueryList errored we should throw 422\n if (queryList.length === 0) {\n debug(`Empty list`);\n throw new CandidateSearchError(`Generated query list contains no queries`);\n }\n if (queryListType && queryListType !== 'alternates') {\n debug(`Unknown queryListType`);\n throw new CandidateSearchError(`Generated query list has invalid type`);\n }\n\n debug(`Searching matches for ${inputRecordId}`);\n const chosenQueryList = await filterQueryList({queryList, queryListType});\n debug(`Chosen queries: ${JSON.stringify(chosenQueryList)}`);\n\n async function filterQueryList({queryList, queryListType, maxCandidates}) {\n debug(`Generated queryList (type: ${queryListType}) ${JSON.stringify(queryList)}`);\n\n if (queryListType === 'alternates' && queryList.length > 1) {\n const queryListResult = await chooseQueries({url, queryList, queryListType, maxCandidates});\n debug(`queryListResult: ${JSON.stringify(queryListResult)}`);\n return queryListResult.map(elem => elem.query);\n }\n return queryList;\n }\n // state.totalRecords : amount of candidate records available to the current query (undefined, if there was no queries left)\n // state.query : current query (undefined if there was no queries left)\n // state.searchCounter : sequence for current search for current query (undefined, if there we no queries left)\n // state.queryCandidateCounter: amount of candidates (records+failures) retrieved from SRU for matching for current query, including the current record+failure set (undefined if there were no queries left)\n // state.queriesLeft : amount of queries left\n // state.queryCounter : sequence for current query\n // state.maxedQueries : queries that resulted in more than serverMaxResults hits\n\n return {search};\n\n // eslint-disable-next-line max-statements\n async function search({queryOffset = 0, resultSetOffset = 1, totalRecords = 0, searchCounter = 0, queryCandidateCounter = 0, queryCounter = 0, maxedQueries = []}) {\n const query = chosenQueryList[queryOffset];\n debug(`Running query ${JSON.stringify(query)} (${queryOffset})`);\n\n if (query) {\n const {records, nextOffset, total} = await retrieveRecords(client, query, resultSetOffset);\n\n // If resultSetOffset === 1 this is the first search for the current query\n debugData(`ResultSetOffset: ${resultSetOffset}`);\n const newTotalRecords = resultSetOffset === 1 ? total : totalRecords;\n const newQueryCounter = resultSetOffset === 1 ? queryCounter + 1 : queryCounter;\n const newSearchCounter = resultSetOffset === 1 ? 1 : searchCounter + 1;\n const newQueryCandidateCounter = resultSetOffset === 1 ? records.length : queryCandidateCounter + records.length;\n\n const maxedQuery = resultSetOffset === 1 ? checkMaxedQuery(query, total, serverMaxResult) : undefined;\n const newMaxedQueries = maxedQuery ? maxedQueries.concat(maxedQuery) : maxedQueries;\n\n if (typeof nextOffset === 'number') {\n debug(`Next search will be for query ${queryOffset} ${query}, starting from record ${nextOffset}`);\n return {records, queryOffset, resultSetOffset: nextOffset, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries};\n }\n debug(`Query ${queryOffset} ${query} done.`);\n debug(`There are (${queryList.length - (queryOffset + 1)} queries left)`);\n return {records, queryOffset: queryOffset + 1, queriesLeft: queryList.length - (queryOffset + 1), totalRecords: newTotalRecords, query, searchCounter: newSearchCounter, queryCandidateCounter: newQueryCandidateCounter, queryCounter: newQueryCounter, maxedQueries: newMaxedQueries};\n }\n\n debug(`All ${queryList.length} queries done, there's no query for ${queryOffset}`);\n return {records: [], queriesLeft: 0, queryCounter, maxedQueries};\n }\n\n function checkMaxedQuery(query, total, serverMaxResult) {\n if (total >= serverMaxResult) {\n debug(`WARNING: Query ${query} resulted in ${total} hits which meets the serverMaxResult (${serverMaxResult}) `);\n return query;\n }\n }\n\n function getRecordId(record) {\n const [field] = record.get(/^001$/u);\n return field ? field.value : '';\n }\n};\n\nexport function retrieveRecords(client, query, resultSetOffset) {\n const debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:retrieveRecords');\n // eslint-disable-next-line no-unused-vars\n const debugData = debug.extend('data');\n\n return new Promise((resolve, reject) => {\n const records = [];\n let totalRecords = 0;\n\n debug(`Searching for candidates with query: ${query} (Offset ${resultSetOffset})`);\n\n client.searchRetrieve(query, {startRecord: resultSetOffset})\n .on('error', err => {\n if (err instanceof SruSearchError) {\n debug(`SRU SruSearchError for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU SruSearchError for query: ${query}: ${err}`));\n }\n\n debug(`SRU error for query: ${query}: ${err}`);\n reject(new CandidateSearchError(`SRU error for query: ${query}: ${err}`));\n })\n .on('total', total => {\n debug(`Got total: ${total}`);\n totalRecords += total;\n })\n .on('end', nextOffset => {\n try {\n debug(`Found ${records.length} records`);\n\n resolve({nextOffset, records, total: totalRecords});\n } catch (err) {\n debug(`Error caught on END`);\n reject(err);\n }\n })\n .on('record', record => {\n const [field] = record.get(/^001$/u);\n debug(field);\n const id = field.value ? field.value : '';\n records.push({record, id});\n });\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["queryList", "queryListType", "maxCandidates", "queryListResult", "serverMaxResult", "record"]
7
+ }
@@ -0,0 +1,92 @@
1
+ import assert from "node:assert";
2
+ import { describe } from "node:test";
3
+ import { READERS } from "@natlibfi/fixura";
4
+ import generateTests from "@natlibfi/fixugen-http-client";
5
+ import { MarcRecord } from "@natlibfi/marc-record";
6
+ import { Error as MatchingError } from "@natlibfi/melinda-commons";
7
+ import createSearchInterface, { CandidateSearchError } from "./index.js";
8
+ import createDebugLogger from "debug";
9
+ const debug = createDebugLogger("@natlibfi/melinda-record-matching:candidate-search:test");
10
+ describe("candidate-search", () => {
11
+ generateTests({
12
+ callback,
13
+ path: [import.meta.dirname, "..", "..", "test-fixtures", "candidate-search", "index"],
14
+ recurse: false,
15
+ fixura: {
16
+ reader: READERS.JSON
17
+ }
18
+ });
19
+ async function callback({ getFixture, factoryOptions, searchOptions, expectedFactoryError = false, expectedSearchError = false, enabled = true }) {
20
+ const url = "http://foo.bar";
21
+ if (!enabled) {
22
+ return;
23
+ }
24
+ if (expectedFactoryError) {
25
+ debug(`We're expecting an error`);
26
+ if (expectedFactoryError.isCandidateSearchError) {
27
+ try {
28
+ const result = await createSearchInterface({ ...formatFactoryOptions(), url });
29
+ debug(result);
30
+ } catch (err) {
31
+ assert.equal(err instanceof CandidateSearchError, true);
32
+ assert.match(err.message, new RegExp(expectedFactoryError.message));
33
+ } finally {
34
+ return;
35
+ }
36
+ }
37
+ try {
38
+ const result = await createSearchInterface({ ...formatFactoryOptions(), url });
39
+ debug(result);
40
+ } catch (err) {
41
+ assert.equal(err instanceof Error, true);
42
+ assert.match(err.message, new RegExp(expectedFactoryError.message));
43
+ } finally {
44
+ return;
45
+ }
46
+ }
47
+ const { search } = await createSearchInterface({ ...formatFactoryOptions(), url });
48
+ await iterate({ searchOptions, expectedSearchError });
49
+ function formatFactoryOptions() {
50
+ debug(`Using factoryOptions: ${JSON.stringify(factoryOptions)}`);
51
+ return {
52
+ ...factoryOptions,
53
+ maxRecordsPerRequest: factoryOptions.maxRecordsPerRequest || 1,
54
+ maxServerResults: factoryOptions.maxServerResults || void 0,
55
+ record: new MarcRecord(factoryOptions.record, { subfieldValues: false })
56
+ };
57
+ }
58
+ async function iterate({ searchOptions: searchOptions2, expectedSearchError: expectedSearchError2, expectedErrorStatus, count = 1 }) {
59
+ const expectedResults = getFixture(`expectedResults${count}.json`);
60
+ if (expectedSearchError2) {
61
+ try {
62
+ await search(searchOptions2);
63
+ throw new Error("Expected an error");
64
+ } catch (err) {
65
+ debug(`Got an error: ${err}`);
66
+ assert(err instanceof Error);
67
+ const errorMessage = err instanceof MatchingError ? err.payload.message : err.message;
68
+ const errorStatus = err instanceof MatchingError ? err.status : void 0;
69
+ debug(`errorMessage: ${errorMessage}, errorStatus: ${errorStatus}`);
70
+ assert.match(errorMessage, new RegExp(expectedSearchError2, "u"));
71
+ if (expectedErrorStatus) {
72
+ assert.equal(errorStatus, expectedErrorStatus);
73
+ return;
74
+ }
75
+ return;
76
+ }
77
+ }
78
+ if (!expectedSearchError2) {
79
+ const results = await search(searchOptions2);
80
+ assert.deepStrictEqual(formatResults(results), expectedResults);
81
+ }
82
+ function formatResults(results) {
83
+ debug(results);
84
+ return {
85
+ ...results,
86
+ records: results.records.map(({ record, id }) => ({ id, record: record.toObject() }))
87
+ };
88
+ }
89
+ }
90
+ }
91
+ });
92
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/candidate-search/index.test.js"],
4
+ "sourcesContent": ["import assert from 'node:assert';\nimport {describe} from 'node:test';\nimport {READERS} from '@natlibfi/fixura';\nimport generateTests from '@natlibfi/fixugen-http-client';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {Error as MatchingError} from '@natlibfi/melinda-commons';\nimport createSearchInterface, {CandidateSearchError} from './index.js';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/melinda-record-matching:candidate-search:test');\n\ndescribe('candidate-search', () => {\n generateTests({\n callback,\n path: [import.meta.dirname, '..', '..', 'test-fixtures', 'candidate-search', 'index'],\n recurse: false,\n fixura: {\n reader: READERS.JSON\n }\n });\n\n // eslint-disable-next-line max-statements\n async function callback({getFixture, factoryOptions, searchOptions, expectedFactoryError = false, expectedSearchError = false, enabled = true}) {\n const url = 'http://foo.bar';\n\n if (!enabled) {\n return;\n }\n\n if (expectedFactoryError) {\n debug(`We're expecting an error`);\n if (expectedFactoryError.isCandidateSearchError) {\n try {\n const result = await createSearchInterface({...formatFactoryOptions(), url});\n debug(result);\n } catch (err) {\n assert.equal(err instanceof CandidateSearchError, true);\n assert.match(err.message, new RegExp(expectedFactoryError.message));\n } finally {\n return;\n }\n }\n\n try {\n const result = await createSearchInterface({...formatFactoryOptions(), url});\n debug(result);\n } catch (err) {\n assert.equal(err instanceof Error, true);\n assert.match(err.message, new RegExp(expectedFactoryError.message));\n } finally {\n return;\n }\n }\n\n const {search} = await createSearchInterface({...formatFactoryOptions(), url});\n await iterate({searchOptions, expectedSearchError});\n\n function formatFactoryOptions() {\n debug(`Using factoryOptions: ${JSON.stringify(factoryOptions)}`);\n return {\n ...factoryOptions,\n maxRecordsPerRequest: factoryOptions.maxRecordsPerRequest || 1,\n maxServerResults: factoryOptions.maxServerResults || undefined,\n record: new MarcRecord(factoryOptions.record, {subfieldValues: false})\n };\n }\n\n async function iterate({searchOptions, expectedSearchError, expectedErrorStatus, count = 1}) {\n const expectedResults = getFixture(`expectedResults${count}.json`);\n\n if (expectedSearchError) {\n try {\n await search(searchOptions);\n throw new Error('Expected an error');\n } catch (err) {\n debug(`Got an error: ${err}`);\n assert(err instanceof Error);\n const errorMessage = err instanceof MatchingError ? err.payload.message : err.message;\n const errorStatus = err instanceof MatchingError ? err.status : undefined;\n debug(`errorMessage: ${errorMessage}, errorStatus: ${errorStatus}`);\n assert.match(errorMessage, new RegExp(expectedSearchError, 'u'));\n\n if (expectedErrorStatus) {\n assert.equal(errorStatus, expectedErrorStatus);\n return;\n }\n return;\n }\n }\n\n if (!expectedSearchError) {\n const results = await search(searchOptions);\n assert.deepStrictEqual(formatResults(results), expectedResults);\n }\n\n function formatResults(results) {\n debug(results);\n return {\n ...results,\n records: results.records.map(({record, id}) => ({id, record: record.toObject()}))\n };\n }\n }\n }\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["searchOptions", "expectedSearchError"]
7
+ }