Socket
Socket
Sign inDemoInstall

@backstage/plugin-search-backend-node

Package Overview
Dependencies
Maintainers
4
Versions
1041
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@backstage/plugin-search-backend-node - npm Package Compare versions

Comparing version 0.2.2 to 0.3.0

21

CHANGELOG.md
# @backstage/plugin-search-backend-node
## 0.3.0
### Minor Changes
- 9f3ecb555: Build search queries using the query builder in `LunrSearchEngine`. This removes
the support for specifying custom queries with the lunr query syntax, but makes
sure that inputs are properly escaped. Supporting the full lunr syntax is still
possible by setting a custom query translator.
The interface of `LunrSearchEngine.setTranslator()` is changed to support
building lunr queries.
### Patch Changes
- 9f3ecb555: Enhance the search results of `LunrSearchEngine` to support a more natural
search experience. This is done by allowing typos (by using fuzzy search) and
supporting typeahead search (using wildcard queries to match incomplete words).
- 4176a60e5: Change search scheduler from starting indexing in a fixed interval (for example
every 60 seconds), to wait a fixed time between index runs.
This makes sure that no second index process for the same document type is
started when the previous one is still running.
## 0.2.2

@@ -4,0 +25,0 @@

145

dist/index.cjs.js

@@ -77,5 +77,30 @@ 'use strict';

function runPeriodically(fn, delayMs) {
let cancel;
let cancelled = false;
const cancellationPromise = new Promise((resolve) => {
cancel = () => {
resolve();
cancelled = true;
};
});
const startRefresh = async () => {
while (!cancelled) {
try {
await fn();
} catch {
}
await Promise.race([
new Promise((resolve) => setTimeout(resolve, delayMs)),
cancellationPromise
]);
}
};
startRefresh();
return cancel;
}
class Scheduler {
constructor({logger}) {
this.intervalTimeouts = [];
this.runningTasks = [];
this.logger = logger;

@@ -85,3 +110,3 @@ this.schedule = [];

addToSchedule(task, interval) {
if (this.intervalTimeouts.length) {
if (this.runningTasks.length) {
throw new Error("Cannot add task to schedule that has already been started.");

@@ -94,6 +119,3 @@ }

this.schedule.forEach(({task, interval}) => {
task();
this.intervalTimeouts.push(setInterval(() => {
task();
}, interval));
this.runningTasks.push(runPeriodically(() => task(), interval));
});

@@ -103,6 +125,6 @@ }

this.logger.info("Stopping all scheduled search tasks.");
this.intervalTimeouts.forEach((timeout) => {
clearInterval(timeout);
this.runningTasks.forEach((cancel) => {
cancel();
});
this.intervalTimeouts = [];
this.runningTasks = [];
}

@@ -119,25 +141,42 @@ }

}) => {
let lunrQueryFilters;
const lunrTerm = term ? `+${term}` : "";
if (filters) {
lunrQueryFilters = Object.entries(filters).map(([field, value]) => {
if (["string", "number", "boolean"].includes(typeof value)) {
if (typeof value === "string") {
return ` +${field}:${value.replace(":", "\\:")}`;
}
return ` +${field}:${value}`;
return {
lunrQueryBuilder: (q) => {
const termToken = lunr__default['default'].tokenizer(term);
q.term(termToken, {
usePipeline: true,
boost: 100
});
q.term(termToken, {
usePipeline: false,
boost: 10,
wildcard: lunr__default['default'].Query.wildcard.TRAILING
});
q.term(termToken, {
usePipeline: false,
editDistance: 2,
boost: 1
});
if (filters) {
Object.entries(filters).forEach(([field, value]) => {
if (!q.allFields.includes(field)) {
throw new Error(`unrecognised field ${field}`);
}
if (["string", "number", "boolean"].includes(typeof value)) {
q.term(lunr__default['default'].tokenizer(value == null ? void 0 : value.toString()), {
presence: lunr__default['default'].Query.presence.REQUIRED,
fields: [field]
});
} else if (Array.isArray(value)) {
this.logger.warn(`Non-scalar filter value used for field ${field}. Consider using a different Search Engine for better results.`);
q.term(lunr__default['default'].tokenizer(value), {
presence: lunr__default['default'].Query.presence.OPTIONAL,
fields: [field]
});
} else {
this.logger.warn(`Unknown filter type used on field ${field}`);
}
});
}
if (Array.isArray(value)) {
this.logger.warn(`Non-scalar filter value used for field ${field}. Consider using a different Search Engine for better results.`);
return ` ${value.map((v) => {
return `${field}:${v}`;
})}`;
}
this.logger.warn(`Unknown filter type used on field ${field}`);
return "";
}).join("");
}
return {
lunrQueryString: `${lunrTerm}${lunrQueryFilters || ""}`,
documentTypes: types || ["*"]
},
documentTypes: types
};

@@ -166,33 +205,19 @@ };

query(query) {
const {lunrQueryString, documentTypes} = this.translator(query);
const {lunrQueryBuilder, documentTypes} = this.translator(query);
const results = [];
if (documentTypes.length === 1 && documentTypes[0] === "*") {
Object.keys(this.lunrIndices).forEach((type) => {
try {
results.push(...this.lunrIndices[type].search(lunrQueryString).map((result) => {
return {
result,
type
};
}));
} catch (err) {
if (err instanceof lunr__default['default'].QueryParseError && err.message.startsWith("unrecognised field"))
return;
Object.keys(this.lunrIndices).filter((type) => !documentTypes || documentTypes.includes(type)).forEach((type) => {
try {
results.push(...this.lunrIndices[type].query(lunrQueryBuilder).map((result) => {
return {
result,
type
};
}));
} catch (err) {
if (err instanceof Error && err.message.startsWith("unrecognised field")) {
return;
}
});
} else {
Object.keys(this.lunrIndices).filter((type) => documentTypes.includes(type)).forEach((type) => {
try {
results.push(...this.lunrIndices[type].search(lunrQueryString).map((result) => {
return {
result,
type
};
}));
} catch (err) {
if (err instanceof lunr__default['default'].QueryParseError && err.message.startsWith("unrecognised field"))
return;
}
});
}
throw err;
}
});
results.sort((doc1, doc2) => {

@@ -199,0 +224,0 @@ return doc2.result.score - doc1.result.score;

@@ -89,3 +89,3 @@ import { Logger } from 'winston';

private schedule;
private intervalTimeouts;
private runningTasks;
constructor({ logger }: {

@@ -95,4 +95,5 @@ logger: Logger;

/**
* Adds each task and interval to the schedule
*
* Adds each task and interval to the schedule.
* When running the tasks, the scheduler waits at least for the time specified
* in the interval once the task was completed, before running it again.
*/

@@ -111,4 +112,4 @@ addToSchedule(task: Function, interval: number): void;

declare type ConcreteLunrQuery = {
lunrQueryString: string;
documentTypes: string[];
lunrQueryBuilder: lunr.Index.QueryBuilder;
documentTypes?: string[];
};

@@ -115,0 +116,0 @@ declare type LunrQueryTranslator = (query: SearchQuery) => ConcreteLunrQuery;

{
"name": "@backstage/plugin-search-backend-node",
"version": "0.2.2",
"version": "0.3.0",
"main": "dist/index.cjs.js",

@@ -28,3 +28,3 @@ "types": "dist/index.d.ts",

"devDependencies": {
"@backstage/backend-common": "^0.8.3",
"@backstage/backend-common": "^0.8.5",
"@backstage/cli": "^0.7.2"

@@ -35,3 +35,3 @@ },

],
"gitHead": "5c2550a6d4485e86d5ad9ecec82fb5fab74c6ca7"
"gitHead": "6cebb9d587224c055516d1ab2958f34bd3659c43"
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc