@backstage/plugin-search-backend-module-pg 0.5.55-next.0 → 0.5.55
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @backstage/plugin-search-backend-module-pg
|
|
2
2
|
|
|
3
|
+
## 0.5.55
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e9b78e9: Removed the `uuid` dependency and replaced usage with the built-in `crypto.randomUUID()`.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/backend-plugin-api@1.9.1
|
|
10
|
+
- @backstage/plugin-search-backend-node@1.4.4
|
|
11
|
+
- @backstage/config@1.3.8
|
|
12
|
+
- @backstage/plugin-search-common@1.2.24
|
|
13
|
+
|
|
14
|
+
## 0.5.55-next.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- e9b78e9: Removed the `uuid` dependency and replaced usage with the built-in `crypto.randomUUID()`.
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @backstage/plugin-search-backend-node@1.4.4-next.1
|
|
21
|
+
|
|
3
22
|
## 0.5.55-next.0
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var PgSearchEngineIndexer = require('./PgSearchEngineIndexer.cjs.js');
|
|
4
4
|
var DatabaseDocumentStore = require('../database/DatabaseDocumentStore.cjs.js');
|
|
5
|
-
var
|
|
5
|
+
var node_crypto = require('node:crypto');
|
|
6
6
|
|
|
7
7
|
class PgSearchEngine {
|
|
8
8
|
logger;
|
|
@@ -15,7 +15,7 @@ class PgSearchEngine {
|
|
|
15
15
|
*/
|
|
16
16
|
constructor(databaseStore, config, logger) {
|
|
17
17
|
this.databaseStore = databaseStore;
|
|
18
|
-
const uuidTag =
|
|
18
|
+
const uuidTag = node_crypto.randomUUID();
|
|
19
19
|
const highlightConfig = config.getOptionalConfig(
|
|
20
20
|
"search.pg.highlightOptions"
|
|
21
21
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PgSearchEngine.cjs.js","sources":["../../src/PgSearchEngine/PgSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SearchEngine } from '@backstage/plugin-search-backend-node';\nimport {\n SearchQuery,\n IndexableResultSet,\n IndexableResult,\n} from '@backstage/plugin-search-common';\nimport { PgSearchEngineIndexer } from './PgSearchEngineIndexer';\nimport {\n DatabaseDocumentStore,\n DatabaseStore,\n PgSearchQuery,\n} from '../database';\nimport { v4 as uuid } from 'uuid';\nimport { Config } from '@backstage/config';\nimport { DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\n\n/**\n * Search query that the Postgres search engine understands.\n * @public\n */\nexport type ConcretePgSearchQuery = {\n pgQuery: PgSearchQuery;\n pageSize: number;\n};\n\n/**\n * Options available for the Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslatorOptions = {\n highlightOptions: PgSearchHighlightOptions;\n normalization?: number;\n};\n\n/**\n * Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslator = (\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n) => ConcretePgSearchQuery;\n\n/**\n * Options to instantiate PgSearchEngine\n * @public\n */\nexport type PgSearchOptions = {\n database: DatabaseService;\n logger?: LoggerService;\n};\n\n/**\n * Options for highlighting search terms\n * @public\n */\nexport type PgSearchHighlightOptions = {\n useHighlight?: boolean;\n maxWords?: number;\n minWords?: number;\n shortWord?: number;\n highlightAll?: boolean;\n maxFragments?: number;\n fragmentDelimiter?: string;\n preTag: string;\n postTag: string;\n};\n\n/** @public */\nexport class PgSearchEngine implements SearchEngine {\n private readonly logger?: LoggerService;\n private readonly highlightOptions: PgSearchHighlightOptions;\n private readonly indexerBatchSize: number;\n private readonly normalization: number;\n private readonly databaseStore: DatabaseStore;\n\n /**\n * @deprecated This will be marked as private in a future release, please us fromConfig instead\n */\n constructor(\n databaseStore: DatabaseStore,\n config: Config,\n logger?: LoggerService,\n ) {\n this.databaseStore = databaseStore;\n const uuidTag = uuid();\n const highlightConfig = config.getOptionalConfig(\n 'search.pg.highlightOptions',\n );\n\n const highlightOptions: PgSearchHighlightOptions = {\n preTag: `<${uuidTag}>`,\n postTag: `</${uuidTag}>`,\n useHighlight: highlightConfig?.getOptionalBoolean('useHighlight') ?? true,\n maxWords: highlightConfig?.getOptionalNumber('maxWords') ?? 35,\n minWords: highlightConfig?.getOptionalNumber('minWords') ?? 15,\n shortWord: highlightConfig?.getOptionalNumber('shortWord') ?? 3,\n highlightAll:\n highlightConfig?.getOptionalBoolean('highlightAll') ?? false,\n maxFragments: highlightConfig?.getOptionalNumber('maxFragments') ?? 0,\n fragmentDelimiter:\n highlightConfig?.getOptionalString('fragmentDelimiter') ?? ' ... ',\n };\n this.highlightOptions = highlightOptions;\n this.indexerBatchSize =\n config.getOptionalNumber('search.pg.indexerBatchSize') ?? 1000;\n\n this.normalization = this.getNormalizationValue(config);\n this.logger = logger;\n }\n\n private getNormalizationValue(config: Config) {\n const normalizationConfig =\n config.getOptional('search.pg.normalization') ?? 0;\n if (typeof normalizationConfig === 'number') {\n return normalizationConfig;\n } else if (typeof normalizationConfig === 'string') {\n return this.evaluateBitwiseOrExpression(normalizationConfig);\n }\n this.logger?.error(\n `Unknown normalization configuration: ${normalizationConfig}`,\n );\n\n return 0;\n }\n\n private evaluateBitwiseOrExpression(expression: string) {\n const tokens = expression.split('|').map(token => token.trim());\n\n const numbers = tokens.map(token => {\n const num = parseInt(token, 10);\n if (isNaN(num)) {\n this.logger?.error(\n `Unknown expression for normalization: ${expression}`,\n );\n return 0;\n }\n return num;\n });\n return numbers.reduce((acc, num) => acc | num, 0);\n }\n\n /**\n * @deprecated This will be removed in a future release, please use fromConfig instead\n */\n static async from(options: {\n database: DatabaseService;\n config: Config;\n logger?: LoggerService;\n }): Promise<PgSearchEngine> {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n options.config,\n options.logger,\n );\n }\n\n static async fromConfig(config: Config, options: PgSearchOptions) {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n config,\n options.logger,\n );\n }\n\n static async supported(database: DatabaseService): Promise<boolean> {\n return await DatabaseDocumentStore.supported(await database.getClient());\n }\n\n translator(\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n ): ConcretePgSearchQuery {\n const pageSize = query.pageLimit || 25;\n const { page } = decodePageCursor(query.pageCursor);\n const offset = page * pageSize;\n // We request more result to know whether there is another page\n const limit = pageSize + 1;\n const normalization = options.normalization || 0;\n\n return {\n pgQuery: {\n pgTerm: query.term\n .split(/\\s/)\n .map(p => p.replace(/[\\0()|&:*!<]/g, '').trim())\n .filter(p => p !== '')\n .map(p => `(${JSON.stringify(p)} | ${JSON.stringify(p)}:*)`)\n .join('&'),\n fields: query.filters as Record<string, string | string[]>,\n types: query.types,\n offset,\n limit,\n normalization,\n options: options.highlightOptions,\n },\n pageSize,\n };\n }\n\n setTranslator(translator: PgSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async getIndexer(type: string) {\n return new PgSearchEngineIndexer({\n batchSize: this.indexerBatchSize,\n type,\n databaseStore: this.databaseStore,\n logger: this.logger?.child({ documentType: type }),\n });\n }\n\n async query(query: SearchQuery): Promise<IndexableResultSet> {\n const { pgQuery, pageSize } = this.translator(query, {\n highlightOptions: this.highlightOptions,\n normalization: this.normalization,\n });\n\n const rows = await this.databaseStore.transaction(async tx =>\n this.databaseStore.query(tx, pgQuery),\n );\n\n // We requested one result more than the page size to know whether there is\n // another page.\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = rows.length > pageSize;\n const hasPreviousPage = page > 0;\n const pageRows = rows.slice(0, pageSize);\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n const results = pageRows.map(\n ({ type, document, highlight }, index): IndexableResult => ({\n type,\n document,\n rank: page * pageSize + index + 1,\n highlight: {\n preTag: pgQuery.options.preTag,\n postTag: pgQuery.options.postTag,\n fields: highlight\n ? {\n text: highlight.text,\n title: highlight.title,\n location: highlight.location,\n path: '',\n }\n : {},\n },\n }),\n );\n\n return {\n results,\n numberOfResults: rows.length > 0 ? parseInt(rows[0].total_count, 10) : 0,\n nextPageCursor,\n previousPageCursor,\n };\n }\n}\n\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["uuid","DatabaseDocumentStore","PgSearchEngineIndexer"],"mappings":";;;;;;AAqFO,MAAM,cAAA,CAAuC;AAAA,EACjC,MAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,WAAA,CACE,aAAA,EACA,MAAA,EACA,MAAA,EACA;AACA,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AACrB,IAAA,MAAM,UAAUA,OAAA,EAAK;AACrB,IAAA,MAAM,kBAAkB,MAAA,CAAO,iBAAA;AAAA,MAC7B;AAAA,KACF;AAEA,IAAA,MAAM,gBAAA,GAA6C;AAAA,MACjD,MAAA,EAAQ,IAAI,OAAO,CAAA,CAAA,CAAA;AAAA,MACnB,OAAA,EAAS,KAAK,OAAO,CAAA,CAAA,CAAA;AAAA,MACrB,YAAA,EAAc,eAAA,EAAiB,kBAAA,CAAmB,cAAc,CAAA,IAAK,IAAA;AAAA,MACrE,QAAA,EAAU,eAAA,EAAiB,iBAAA,CAAkB,UAAU,CAAA,IAAK,EAAA;AAAA,MAC5D,QAAA,EAAU,eAAA,EAAiB,iBAAA,CAAkB,UAAU,CAAA,IAAK,EAAA;AAAA,MAC5D,SAAA,EAAW,eAAA,EAAiB,iBAAA,CAAkB,WAAW,CAAA,IAAK,CAAA;AAAA,MAC9D,YAAA,EACE,eAAA,EAAiB,kBAAA,CAAmB,cAAc,CAAA,IAAK,KAAA;AAAA,MACzD,YAAA,EAAc,eAAA,EAAiB,iBAAA,CAAkB,cAAc,CAAA,IAAK,CAAA;AAAA,MACpE,iBAAA,EACE,eAAA,EAAiB,iBAAA,CAAkB,mBAAmB,CAAA,IAAK;AAAA,KAC/D;AACA,IAAA,IAAA,CAAK,gBAAA,GAAmB,gBAAA;AACxB,IAAA,IAAA,CAAK,gBAAA,GACH,MAAA,CAAO,iBAAA,CAAkB,4BAA4B,CAAA,IAAK,GAAA;AAE5D,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA,CAAK,qBAAA,CAAsB,MAAM,CAAA;AACtD,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEQ,sBAAsB,MAAA,EAAgB;AAC5C,IAAA,MAAM,mBAAA,GACJ,MAAA,CAAO,WAAA,CAAY,yBAAyB,CAAA,IAAK,CAAA;AACnD,IAAA,IAAI,OAAO,wBAAwB,QAAA,EAAU;AAC3C,MAAA,OAAO,mBAAA;AAAA,IACT,CAAA,MAAA,IAAW,OAAO,mBAAA,KAAwB,QAAA,EAAU;AAClD,MAAA,OAAO,IAAA,CAAK,4BAA4B,mBAAmB,CAAA;AAAA,IAC7D;AACA,IAAA,IAAA,CAAK,MAAA,EAAQ,KAAA;AAAA,MACX,wCAAwC,mBAAmB,CAAA;AAAA,KAC7D;AAEA,IAAA,OAAO,CAAA;AAAA,EACT;AAAA,EAEQ,4BAA4B,UAAA,EAAoB;AACtD,IAAA,MAAM,MAAA,GAAS,WAAW,KAAA,CAAM,GAAG,EAAE,GAAA,CAAI,CAAA,KAAA,KAAS,KAAA,CAAM,IAAA,EAAM,CAAA;AAE9D,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAClC,MAAA,MAAM,GAAA,GAAM,QAAA,CAAS,KAAA,EAAO,EAAE,CAAA;AAC9B,MAAA,IAAI,KAAA,CAAM,GAAG,CAAA,EAAG;AACd,QAAA,IAAA,CAAK,MAAA,EAAQ,KAAA;AAAA,UACX,yCAAyC,UAAU,CAAA;AAAA,SACrD;AACA,QAAA,OAAO,CAAA;AAAA,MACT;AACA,MAAA,OAAO,GAAA;AAAA,IACT,CAAC,CAAA;AACD,IAAA,OAAO,QAAQ,MAAA,CAAO,CAAC,KAAK,GAAA,KAAQ,GAAA,GAAM,KAAK,CAAC,CAAA;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,KAAK,OAAA,EAIU;AAC1B,IAAA,OAAO,IAAI,cAAA;AAAA,MACT,MAAMC,2CAAA,CAAsB,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA;AAAA,MACnD,OAAA,CAAQ,MAAA;AAAA,MACR,OAAA,CAAQ;AAAA,KACV;AAAA,EACF;AAAA,EAEA,aAAa,UAAA,CAAW,MAAA,EAAgB,OAAA,EAA0B;AAChE,IAAA,OAAO,IAAI,cAAA;AAAA,MACT,MAAMA,2CAAA,CAAsB,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA;AAAA,MACnD,MAAA;AAAA,MACA,OAAA,CAAQ;AAAA,KACV;AAAA,EACF;AAAA,EAEA,aAAa,UAAU,QAAA,EAA6C;AAClE,IAAA,OAAO,MAAMA,2CAAA,CAAsB,SAAA,CAAU,MAAM,QAAA,CAAS,WAAW,CAAA;AAAA,EACzE;AAAA,EAEA,UAAA,CACE,OACA,OAAA,EACuB;AACvB,IAAA,MAAM,QAAA,GAAW,MAAM,SAAA,IAAa,EAAA;AACpC,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,gBAAA,CAAiB,MAAM,UAAU,CAAA;AAClD,IAAA,MAAM,SAAS,IAAA,GAAO,QAAA;AAEtB,IAAA,MAAM,QAAQ,QAAA,GAAW,CAAA;AACzB,IAAA,MAAM,aAAA,GAAgB,QAAQ,aAAA,IAAiB,CAAA;AAE/C,IAAA,OAAO;AAAA,MACL,OAAA,EAAS;AAAA,QACP,QAAQ,KAAA,CAAM,IAAA,CACX,KAAA,CAAM,IAAI,EACV,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,OAAA,CAAQ,iBAAiB,EAAE,CAAA,CAAE,IAAA,EAAM,EAC9C,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,KAAM,EAAE,EACpB,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAA,EAAI,IAAA,CAAK,UAAU,CAAC,CAAC,CAAA,GAAA,EAAM,IAAA,CAAK,UAAU,CAAC,CAAC,CAAA,GAAA,CAAK,CAAA,CAC1D,KAAK,GAAG,CAAA;AAAA,QACX,QAAQ,KAAA,CAAM,OAAA;AAAA,QACd,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,MAAA;AAAA,QACA,KAAA;AAAA,QACA,aAAA;AAAA,QACA,SAAS,OAAA,CAAQ;AAAA,OACnB;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA,EAEA,cAAc,UAAA,EAAqC;AACjD,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAAA,EACpB;AAAA,EAEA,MAAM,WAAW,IAAA,EAAc;AAC7B,IAAA,OAAO,IAAIC,2CAAA,CAAsB;AAAA,MAC/B,WAAW,IAAA,CAAK,gBAAA;AAAA,MAChB,IAAA;AAAA,MACA,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,QAAQ,IAAA,CAAK,MAAA,EAAQ,MAAM,EAAE,YAAA,EAAc,MAAM;AAAA,KAClD,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,KAAA,EAAiD;AAC3D,IAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAS,GAAI,IAAA,CAAK,WAAW,KAAA,EAAO;AAAA,MACnD,kBAAkB,IAAA,CAAK,gBAAA;AAAA,MACvB,eAAe,IAAA,CAAK;AAAA,KACrB,CAAA;AAED,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,aAAA,CAAc,WAAA;AAAA,MAAY,OAAM,EAAA,KACtD,IAAA,CAAK,aAAA,CAAc,KAAA,CAAM,IAAI,OAAO;AAAA,KACtC;AAIA,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,gBAAA,CAAiB,MAAM,UAAU,CAAA;AAClD,IAAA,MAAM,WAAA,GAAc,KAAK,MAAA,GAAS,QAAA;AAClC,IAAA,MAAM,kBAAkB,IAAA,GAAO,CAAA;AAC/B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA;AACvC,IAAA,MAAM,cAAA,GAAiB,cACnB,gBAAA,CAAiB,EAAE,MAAM,IAAA,GAAO,CAAA,EAAG,CAAA,GACnC,MAAA;AACJ,IAAA,MAAM,kBAAA,GAAqB,kBACvB,gBAAA,CAAiB,EAAE,MAAM,IAAA,GAAO,CAAA,EAAG,CAAA,GACnC,MAAA;AAEJ,IAAA,MAAM,UAAU,QAAA,CAAS,GAAA;AAAA,MACvB,CAAC,EAAE,IAAA,EAAM,QAAA,EAAU,SAAA,IAAa,KAAA,MAA4B;AAAA,QAC1D,IAAA;AAAA,QACA,QAAA;AAAA,QACA,IAAA,EAAM,IAAA,GAAO,QAAA,GAAW,KAAA,GAAQ,CAAA;AAAA,QAChC,SAAA,EAAW;AAAA,UACT,MAAA,EAAQ,QAAQ,OAAA,CAAQ,MAAA;AAAA,UACxB,OAAA,EAAS,QAAQ,OAAA,CAAQ,OAAA;AAAA,UACzB,QAAQ,SAAA,GACJ;AAAA,YACE,MAAM,SAAA,CAAU,IAAA;AAAA,YAChB,OAAO,SAAA,CAAU,KAAA;AAAA,YACjB,UAAU,SAAA,CAAU,QAAA;AAAA,YACpB,IAAA,EAAM;AAAA,cAER;AAAC;AACP,OACF;AAAA,KACF;AAEA,IAAA,OAAO;AAAA,MACL,OAAA;AAAA,MACA,eAAA,EAAiB,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,QAAA,CAAS,KAAK,CAAC,CAAA,CAAE,WAAA,EAAa,EAAE,CAAA,GAAI,CAAA;AAAA,MACvE,cAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;AAEO,SAAS,iBAAiB,UAAA,EAAuC;AACtE,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAO,EAAE,MAAM,CAAA,EAAE;AAAA,EACnB;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAO,MAAA,CAAO,IAAA,CAAK,YAAY,QAAQ,CAAA,CAAE,QAAA,CAAS,OAAO,CAAC;AAAA,GAClE;AACF;AAEO,SAAS,gBAAA,CAAiB,EAAE,IAAA,EAAK,EAA6B;AACnE,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA,EAAG,IAAI,IAAI,OAAO,CAAA,CAAE,SAAS,QAAQ,CAAA;AAC1D;;;;;;"}
|
|
1
|
+
{"version":3,"file":"PgSearchEngine.cjs.js","sources":["../../src/PgSearchEngine/PgSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SearchEngine } from '@backstage/plugin-search-backend-node';\nimport {\n SearchQuery,\n IndexableResultSet,\n IndexableResult,\n} from '@backstage/plugin-search-common';\nimport { PgSearchEngineIndexer } from './PgSearchEngineIndexer';\nimport {\n DatabaseDocumentStore,\n DatabaseStore,\n PgSearchQuery,\n} from '../database';\nimport { randomUUID as uuid } from 'node:crypto';\nimport { Config } from '@backstage/config';\nimport { DatabaseService, LoggerService } from '@backstage/backend-plugin-api';\n\n/**\n * Search query that the Postgres search engine understands.\n * @public\n */\nexport type ConcretePgSearchQuery = {\n pgQuery: PgSearchQuery;\n pageSize: number;\n};\n\n/**\n * Options available for the Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslatorOptions = {\n highlightOptions: PgSearchHighlightOptions;\n normalization?: number;\n};\n\n/**\n * Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslator = (\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n) => ConcretePgSearchQuery;\n\n/**\n * Options to instantiate PgSearchEngine\n * @public\n */\nexport type PgSearchOptions = {\n database: DatabaseService;\n logger?: LoggerService;\n};\n\n/**\n * Options for highlighting search terms\n * @public\n */\nexport type PgSearchHighlightOptions = {\n useHighlight?: boolean;\n maxWords?: number;\n minWords?: number;\n shortWord?: number;\n highlightAll?: boolean;\n maxFragments?: number;\n fragmentDelimiter?: string;\n preTag: string;\n postTag: string;\n};\n\n/** @public */\nexport class PgSearchEngine implements SearchEngine {\n private readonly logger?: LoggerService;\n private readonly highlightOptions: PgSearchHighlightOptions;\n private readonly indexerBatchSize: number;\n private readonly normalization: number;\n private readonly databaseStore: DatabaseStore;\n\n /**\n * @deprecated This will be marked as private in a future release, please us fromConfig instead\n */\n constructor(\n databaseStore: DatabaseStore,\n config: Config,\n logger?: LoggerService,\n ) {\n this.databaseStore = databaseStore;\n const uuidTag = uuid();\n const highlightConfig = config.getOptionalConfig(\n 'search.pg.highlightOptions',\n );\n\n const highlightOptions: PgSearchHighlightOptions = {\n preTag: `<${uuidTag}>`,\n postTag: `</${uuidTag}>`,\n useHighlight: highlightConfig?.getOptionalBoolean('useHighlight') ?? true,\n maxWords: highlightConfig?.getOptionalNumber('maxWords') ?? 35,\n minWords: highlightConfig?.getOptionalNumber('minWords') ?? 15,\n shortWord: highlightConfig?.getOptionalNumber('shortWord') ?? 3,\n highlightAll:\n highlightConfig?.getOptionalBoolean('highlightAll') ?? false,\n maxFragments: highlightConfig?.getOptionalNumber('maxFragments') ?? 0,\n fragmentDelimiter:\n highlightConfig?.getOptionalString('fragmentDelimiter') ?? ' ... ',\n };\n this.highlightOptions = highlightOptions;\n this.indexerBatchSize =\n config.getOptionalNumber('search.pg.indexerBatchSize') ?? 1000;\n\n this.normalization = this.getNormalizationValue(config);\n this.logger = logger;\n }\n\n private getNormalizationValue(config: Config) {\n const normalizationConfig =\n config.getOptional('search.pg.normalization') ?? 0;\n if (typeof normalizationConfig === 'number') {\n return normalizationConfig;\n } else if (typeof normalizationConfig === 'string') {\n return this.evaluateBitwiseOrExpression(normalizationConfig);\n }\n this.logger?.error(\n `Unknown normalization configuration: ${normalizationConfig}`,\n );\n\n return 0;\n }\n\n private evaluateBitwiseOrExpression(expression: string) {\n const tokens = expression.split('|').map(token => token.trim());\n\n const numbers = tokens.map(token => {\n const num = parseInt(token, 10);\n if (isNaN(num)) {\n this.logger?.error(\n `Unknown expression for normalization: ${expression}`,\n );\n return 0;\n }\n return num;\n });\n return numbers.reduce((acc, num) => acc | num, 0);\n }\n\n /**\n * @deprecated This will be removed in a future release, please use fromConfig instead\n */\n static async from(options: {\n database: DatabaseService;\n config: Config;\n logger?: LoggerService;\n }): Promise<PgSearchEngine> {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n options.config,\n options.logger,\n );\n }\n\n static async fromConfig(config: Config, options: PgSearchOptions) {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n config,\n options.logger,\n );\n }\n\n static async supported(database: DatabaseService): Promise<boolean> {\n return await DatabaseDocumentStore.supported(await database.getClient());\n }\n\n translator(\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n ): ConcretePgSearchQuery {\n const pageSize = query.pageLimit || 25;\n const { page } = decodePageCursor(query.pageCursor);\n const offset = page * pageSize;\n // We request more result to know whether there is another page\n const limit = pageSize + 1;\n const normalization = options.normalization || 0;\n\n return {\n pgQuery: {\n pgTerm: query.term\n .split(/\\s/)\n .map(p => p.replace(/[\\0()|&:*!<]/g, '').trim())\n .filter(p => p !== '')\n .map(p => `(${JSON.stringify(p)} | ${JSON.stringify(p)}:*)`)\n .join('&'),\n fields: query.filters as Record<string, string | string[]>,\n types: query.types,\n offset,\n limit,\n normalization,\n options: options.highlightOptions,\n },\n pageSize,\n };\n }\n\n setTranslator(translator: PgSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async getIndexer(type: string) {\n return new PgSearchEngineIndexer({\n batchSize: this.indexerBatchSize,\n type,\n databaseStore: this.databaseStore,\n logger: this.logger?.child({ documentType: type }),\n });\n }\n\n async query(query: SearchQuery): Promise<IndexableResultSet> {\n const { pgQuery, pageSize } = this.translator(query, {\n highlightOptions: this.highlightOptions,\n normalization: this.normalization,\n });\n\n const rows = await this.databaseStore.transaction(async tx =>\n this.databaseStore.query(tx, pgQuery),\n );\n\n // We requested one result more than the page size to know whether there is\n // another page.\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = rows.length > pageSize;\n const hasPreviousPage = page > 0;\n const pageRows = rows.slice(0, pageSize);\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n const results = pageRows.map(\n ({ type, document, highlight }, index): IndexableResult => ({\n type,\n document,\n rank: page * pageSize + index + 1,\n highlight: {\n preTag: pgQuery.options.preTag,\n postTag: pgQuery.options.postTag,\n fields: highlight\n ? {\n text: highlight.text,\n title: highlight.title,\n location: highlight.location,\n path: '',\n }\n : {},\n },\n }),\n );\n\n return {\n results,\n numberOfResults: rows.length > 0 ? parseInt(rows[0].total_count, 10) : 0,\n nextPageCursor,\n previousPageCursor,\n };\n }\n}\n\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["uuid","DatabaseDocumentStore","PgSearchEngineIndexer"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-search-backend-module-pg",
|
|
3
|
-
"version": "0.5.55
|
|
3
|
+
"version": "0.5.55",
|
|
4
4
|
"description": "A module for the search backend that implements search using PostgreSQL",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin-module",
|
|
@@ -63,17 +63,16 @@
|
|
|
63
63
|
"test": "backstage-cli package test"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@backstage/backend-plugin-api": "1.9.1
|
|
67
|
-
"@backstage/config": "1.3.8
|
|
68
|
-
"@backstage/plugin-search-backend-node": "1.4.4
|
|
69
|
-
"@backstage/plugin-search-common": "1.2.24
|
|
66
|
+
"@backstage/backend-plugin-api": "^1.9.1",
|
|
67
|
+
"@backstage/config": "^1.3.8",
|
|
68
|
+
"@backstage/plugin-search-backend-node": "^1.4.4",
|
|
69
|
+
"@backstage/plugin-search-common": "^1.2.24",
|
|
70
70
|
"knex": "^3.0.0",
|
|
71
|
-
"lodash": "^4.17.21"
|
|
72
|
-
"uuid": "^11.0.0"
|
|
71
|
+
"lodash": "^4.17.21"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
75
|
-
"@backstage/backend-test-utils": "1.11.3
|
|
76
|
-
"@backstage/cli": "0.36.2
|
|
74
|
+
"@backstage/backend-test-utils": "^1.11.3",
|
|
75
|
+
"@backstage/cli": "^0.36.2"
|
|
77
76
|
},
|
|
78
77
|
"configSchema": "config.d.ts"
|
|
79
78
|
}
|