@backstage/catalog-client 1.16.2-next.1 → 1.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @backstage/catalog-client
2
2
 
3
+ ## 1.16.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 736d84e: Use locale-insensitive Unicode casing for consistent string handling across environments.
8
+ - Updated dependencies
9
+ - @backstage/plugin-catalog-common@1.2.0
10
+ - @backstage/catalog-model@1.10.1
11
+ - @backstage/filter-predicates@0.1.5
12
+
3
13
  ## 1.16.2-next.1
4
14
 
5
15
  ### Patch Changes
@@ -31,7 +31,7 @@ function traverse(root) {
31
31
  visit(path, item);
32
32
  if (typeof item === "string") {
33
33
  const pathKey = `${path}.${item}`;
34
- const lowerKey = pathKey.toLocaleLowerCase("en-US");
34
+ const lowerKey = pathKey.toLowerCase();
35
35
  if (!seenPathKeys.has(lowerKey)) {
36
36
  seenPathKeys.add(lowerKey);
37
37
  output.push({ key: pathKey, value: true });
@@ -1 +1 @@
1
- {"version":3,"file":"buildEntitySearch.cjs.js","sources":["../../../../../../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model';\nimport { InputError } from '@backstage/errors';\nimport { DbSearchRow } from '../../tables';\nimport { NULL_SENTINEL } from './util';\n\n// These are excluded in the generic loop, either because they do not make sense\n// to index, or because they are special-case always inserted whether they are\n// null or not\nconst SPECIAL_KEYS = [\n 'attachments',\n 'relations',\n 'status',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.uid',\n 'metadata.etag',\n];\n\n// The maximum length allowed for search values. These columns are indexed, and\n// database engines do not like to index on massive values. For example,\n// postgres will balk after 8191 byte line sizes.\nconst MAX_KEY_LENGTH = 200;\nconst MAX_VALUE_LENGTH = 200;\n\ntype Kv = {\n key: string;\n value: unknown;\n};\n\n// Helper for traversing through a nested structure and outputting a list of\n// path->value entries of the leaves.\n//\n// For example, this yaml structure\n//\n// a: 1\n// b:\n// c: null\n// e: [f, g]\n// h:\n// - i: 1\n// j: k\n// - i: 2\n// j: l\n//\n// will result in\n//\n// \"a\", 1\n// \"b.c\", null\n// \"b.e\": \"f\"\n// \"b.e.f\": true\n// \"b.e\": \"g\"\n// \"b.e.g\": true\n// \"h.i\": 1\n// \"h.j\": \"k\"\n// \"h.i\": 2\n// \"h.j\": \"l\"\nexport function traverse(root: unknown): Kv[] {\n const output: Kv[] = [];\n // Use a Set for O(1) case-insensitive duplicate detection of synthetic\n // boolean path keys (e.g. \"metadata.tags.java\"), instead of the previous\n // O(n) Array.some() linear scan which caused O(n²) overall complexity\n // and severe event loop blocking for entities with large arrays.\n const seenPathKeys = new Set<string>();\n\n function visit(path: string, current: unknown) {\n if (SPECIAL_KEYS.includes(path)) {\n return;\n }\n\n // empty or scalar\n if (\n current === undefined ||\n current === null ||\n ['string', 'number', 'boolean'].includes(typeof current)\n ) {\n output.push({ key: path, value: current });\n return;\n }\n\n // unknown\n if (typeof current !== 'object') {\n return;\n }\n\n // array\n if (Array.isArray(current)) {\n for (const item of current) {\n // NOTE(freben): The reason that these are output in two different ways,\n // is to support use cases where you want to express that MORE than one\n // tag is present in a list. Since the EntityFilters structure is a\n // record, you can't have several entries of the same key. Therefore\n // you will have to match on\n //\n // { \"a.b\": [\"true\"], \"a.c\": [\"true\"] }\n //\n // rather than\n //\n // { \"a\": [\"b\", \"c\"] }\n //\n // because the latter means EITHER b or c has to be present.\n visit(path, item);\n if (typeof item === 'string') {\n const pathKey = `${path}.${item}`;\n const lowerKey = pathKey.toLocaleLowerCase('en-US');\n if (!seenPathKeys.has(lowerKey)) {\n seenPathKeys.add(lowerKey);\n output.push({ key: pathKey, value: true });\n }\n }\n }\n return;\n }\n\n // object\n for (const [key, value] of Object.entries(current!)) {\n visit(path ? `${path}.${key}` : key, value);\n }\n }\n\n visit('', root);\n\n return output;\n}\n\n// Translates a number of raw data rows to search table rows\nexport function mapToRows(input: Kv[], entityId: string): DbSearchRow[] {\n const result: DbSearchRow[] = [];\n\n for (const { key: rawKey, value: rawValue } of input) {\n const key = rawKey.toLocaleLowerCase('en-US');\n if (key.length > MAX_KEY_LENGTH) {\n continue;\n }\n if (rawValue === undefined || rawValue === null) {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n } else {\n const value = String(rawValue).toLocaleLowerCase('en-US');\n if (value.length <= MAX_VALUE_LENGTH) {\n result.push({\n entity_id: entityId,\n key,\n original_value: String(rawValue),\n value: value,\n });\n } else {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generates all of the search rows that are relevant for this entity.\n *\n * @param entityId - The uid of the entity\n * @param entity - The entity\n * @returns A list of entity search rows\n */\nexport function buildEntitySearch(\n entityId: string,\n entity: Entity,\n): DbSearchRow[] {\n // Visit the base structure recursively\n const raw = traverse(entity);\n\n // Start with some special keys that are always present because you want to\n // be able to easily search for null specifically\n raw.push({ key: 'metadata.name', value: entity.metadata.name });\n raw.push({ key: 'metadata.namespace', value: entity.metadata.namespace });\n raw.push({ key: 'metadata.uid', value: entity.metadata.uid });\n\n // Namespace not specified has the default value \"default\", so we want to\n // match on that as well\n if (!entity.metadata.namespace) {\n raw.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n raw.push({\n key: `relations.${relation.type}`,\n value: relation.targetRef,\n });\n }\n\n // This validates that there are no keys that vary only in casing, such\n // as `spec.foo` and `spec.Foo`.\n const keys = new Set(raw.map(r => r.key));\n const lowerKeys = new Set(raw.map(r => r.key.toLocaleLowerCase('en-US')));\n if (keys.size !== lowerKeys.size) {\n const difference = [];\n for (const key of keys) {\n const lower = key.toLocaleLowerCase('en-US');\n if (!lowerKeys.delete(lower)) {\n difference.push(lower);\n }\n }\n const badKeys = `'${difference.join(\"', '\")}'`;\n throw new InputError(\n `Entity has duplicate keys that vary only in casing, ${badKeys}`,\n );\n }\n\n const rows = mapToRows(raw, entityId);\n\n // Deduplicate by (key, value). Duplicate array values in the entity data\n // (e.g. tags: ['java', 'java']) produce identical search rows which would\n // violate the unique constraint on (entity_id, key, value).\n const seen = new Set<string>();\n return rows.filter(row => {\n const k = `${row.key}\\0${row.value === null ? NULL_SENTINEL : row.value}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n}\n"],"names":[],"mappings":";;;;;AAwBA,MAAM,YAAA,GAAe;AAAA,EACnB,aAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAA;AAwCO,SAAS,SAAS,IAAA,EAAqB;AAC5C,EAAA,MAAM,SAAe,EAAC;AAKtB,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AAErC,EAAA,SAAS,KAAA,CAAM,MAAc,OAAA,EAAkB;AAC7C,IAAA,IAAI,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA,EAAG;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IACE,OAAA,KAAY,MAAA,IACZ,OAAA,KAAY,IAAA,IACZ,CAAC,QAAA,EAAU,QAAA,EAAU,SAAS,CAAA,CAAE,QAAA,CAAS,OAAO,OAAO,CAAA,EACvD;AACA,MAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,MAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAc1B,QAAA,KAAA,CAAM,MAAM,IAAI,CAAA;AAChB,QAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,UAAA,MAAM,OAAA,GAAU,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAC/B,UAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,iBAAA,CAAkB,OAAO,CAAA;AAClD,UAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC/B,YAAA,YAAA,CAAa,IAAI,QAAQ,CAAA;AACzB,YAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,MAAM,CAAA;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAGA,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAQ,CAAA,EAAG;AACnD,MAAA,KAAA,CAAM,OAAO,CAAA,EAAG,IAAI,IAAI,GAAG,CAAA,CAAA,GAAK,KAAK,KAAK,CAAA;AAAA,IAC5C;AAAA,EACF;AAEA,EAAA,KAAA,CAAM,IAAI,IAAI,CAAA;AAEd,EAAA,OAAO,MAAA;AACT;;;;"}
1
+ {"version":3,"file":"buildEntitySearch.cjs.js","sources":["../../../../../../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model';\nimport { InputError } from '@backstage/errors';\nimport { DbSearchRow } from '../../tables';\nimport { NULL_SENTINEL } from './util';\n\n// These are excluded in the generic loop, either because they do not make sense\n// to index, or because they are special-case always inserted whether they are\n// null or not\nconst SPECIAL_KEYS = [\n 'attachments',\n 'relations',\n 'status',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.uid',\n 'metadata.etag',\n];\n\n// The maximum length allowed for search values. These columns are indexed, and\n// database engines do not like to index on massive values. For example,\n// postgres will balk after 8191 byte line sizes.\nconst MAX_KEY_LENGTH = 200;\nconst MAX_VALUE_LENGTH = 200;\n\ntype Kv = {\n key: string;\n value: unknown;\n};\n\n// Helper for traversing through a nested structure and outputting a list of\n// path->value entries of the leaves.\n//\n// For example, this yaml structure\n//\n// a: 1\n// b:\n// c: null\n// e: [f, g]\n// h:\n// - i: 1\n// j: k\n// - i: 2\n// j: l\n//\n// will result in\n//\n// \"a\", 1\n// \"b.c\", null\n// \"b.e\": \"f\"\n// \"b.e.f\": true\n// \"b.e\": \"g\"\n// \"b.e.g\": true\n// \"h.i\": 1\n// \"h.j\": \"k\"\n// \"h.i\": 2\n// \"h.j\": \"l\"\nexport function traverse(root: unknown): Kv[] {\n const output: Kv[] = [];\n // Use a Set for O(1) case-insensitive duplicate detection of synthetic\n // boolean path keys (e.g. \"metadata.tags.java\"), instead of the previous\n // O(n) Array.some() linear scan which caused O(n²) overall complexity\n // and severe event loop blocking for entities with large arrays.\n const seenPathKeys = new Set<string>();\n\n function visit(path: string, current: unknown) {\n if (SPECIAL_KEYS.includes(path)) {\n return;\n }\n\n // empty or scalar\n if (\n current === undefined ||\n current === null ||\n ['string', 'number', 'boolean'].includes(typeof current)\n ) {\n output.push({ key: path, value: current });\n return;\n }\n\n // unknown\n if (typeof current !== 'object') {\n return;\n }\n\n // array\n if (Array.isArray(current)) {\n for (const item of current) {\n // NOTE(freben): The reason that these are output in two different ways,\n // is to support use cases where you want to express that MORE than one\n // tag is present in a list. Since the EntityFilters structure is a\n // record, you can't have several entries of the same key. Therefore\n // you will have to match on\n //\n // { \"a.b\": [\"true\"], \"a.c\": [\"true\"] }\n //\n // rather than\n //\n // { \"a\": [\"b\", \"c\"] }\n //\n // because the latter means EITHER b or c has to be present.\n visit(path, item);\n if (typeof item === 'string') {\n const pathKey = `${path}.${item}`;\n const lowerKey = pathKey.toLowerCase();\n if (!seenPathKeys.has(lowerKey)) {\n seenPathKeys.add(lowerKey);\n output.push({ key: pathKey, value: true });\n }\n }\n }\n return;\n }\n\n // object\n for (const [key, value] of Object.entries(current!)) {\n visit(path ? `${path}.${key}` : key, value);\n }\n }\n\n visit('', root);\n\n return output;\n}\n\n// Translates a number of raw data rows to search table rows\nexport function mapToRows(input: Kv[], entityId: string): DbSearchRow[] {\n const result: DbSearchRow[] = [];\n\n for (const { key: rawKey, value: rawValue } of input) {\n const key = rawKey.toLowerCase();\n if (key.length > MAX_KEY_LENGTH) {\n continue;\n }\n if (rawValue === undefined || rawValue === null) {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n } else {\n const value = String(rawValue).toLowerCase();\n if (value.length <= MAX_VALUE_LENGTH) {\n result.push({\n entity_id: entityId,\n key,\n original_value: String(rawValue),\n value: value,\n });\n } else {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generates all of the search rows that are relevant for this entity.\n *\n * @param entityId - The uid of the entity\n * @param entity - The entity\n * @returns A list of entity search rows\n */\nexport function buildEntitySearch(\n entityId: string,\n entity: Entity,\n): DbSearchRow[] {\n // Visit the base structure recursively\n const raw = traverse(entity);\n\n // Start with some special keys that are always present because you want to\n // be able to easily search for null specifically\n raw.push({ key: 'metadata.name', value: entity.metadata.name });\n raw.push({ key: 'metadata.namespace', value: entity.metadata.namespace });\n raw.push({ key: 'metadata.uid', value: entity.metadata.uid });\n\n // Namespace not specified has the default value \"default\", so we want to\n // match on that as well\n if (!entity.metadata.namespace) {\n raw.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n raw.push({\n key: `relations.${relation.type}`,\n value: relation.targetRef,\n });\n }\n\n // This validates that there are no keys that vary only in casing, such\n // as `spec.foo` and `spec.Foo`.\n const keys = new Set(raw.map(r => r.key));\n const lowerKeys = new Set(raw.map(r => r.key.toLowerCase()));\n if (keys.size !== lowerKeys.size) {\n const difference = [];\n for (const key of keys) {\n const lower = key.toLowerCase();\n if (!lowerKeys.delete(lower)) {\n difference.push(lower);\n }\n }\n const badKeys = `'${difference.join(\"', '\")}'`;\n throw new InputError(\n `Entity has duplicate keys that vary only in casing, ${badKeys}`,\n );\n }\n\n const rows = mapToRows(raw, entityId);\n\n // Deduplicate by (key, value). Duplicate array values in the entity data\n // (e.g. tags: ['java', 'java']) produce identical search rows which would\n // violate the unique constraint on (entity_id, key, value).\n const seen = new Set<string>();\n return rows.filter(row => {\n const k = `${row.key}\\0${row.value === null ? NULL_SENTINEL : row.value}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n}\n"],"names":[],"mappings":";;;;;AAwBA,MAAM,YAAA,GAAe;AAAA,EACnB,aAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAA;AAwCO,SAAS,SAAS,IAAA,EAAqB;AAC5C,EAAA,MAAM,SAAe,EAAC;AAKtB,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AAErC,EAAA,SAAS,KAAA,CAAM,MAAc,OAAA,EAAkB;AAC7C,IAAA,IAAI,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA,EAAG;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IACE,OAAA,KAAY,MAAA,IACZ,OAAA,KAAY,IAAA,IACZ,CAAC,QAAA,EAAU,QAAA,EAAU,SAAS,CAAA,CAAE,QAAA,CAAS,OAAO,OAAO,CAAA,EACvD;AACA,MAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,MAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAc1B,QAAA,KAAA,CAAM,MAAM,IAAI,CAAA;AAChB,QAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,UAAA,MAAM,OAAA,GAAU,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAC/B,UAAA,MAAM,QAAA,GAAW,QAAQ,WAAA,EAAY;AACrC,UAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC/B,YAAA,YAAA,CAAa,IAAI,QAAQ,CAAA;AACzB,YAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,MAAM,CAAA;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAGA,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAQ,CAAA,EAAG;AACnD,MAAA,KAAA,CAAM,OAAO,CAAA,EAAG,IAAI,IAAI,GAAG,CAAA,CAAA,GAAK,KAAK,KAAK,CAAA;AAAA,IAC5C;AAAA,EACF;AAEA,EAAA,KAAA,CAAM,IAAI,IAAI,CAAA;AAEd,EAAA,OAAO,MAAA;AACT;;;;"}
@@ -29,7 +29,7 @@ function traverse(root) {
29
29
  visit(path, item);
30
30
  if (typeof item === "string") {
31
31
  const pathKey = `${path}.${item}`;
32
- const lowerKey = pathKey.toLocaleLowerCase("en-US");
32
+ const lowerKey = pathKey.toLowerCase();
33
33
  if (!seenPathKeys.has(lowerKey)) {
34
34
  seenPathKeys.add(lowerKey);
35
35
  output.push({ key: pathKey, value: true });
@@ -1 +1 @@
1
- {"version":3,"file":"buildEntitySearch.esm.js","sources":["../../../../../../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model';\nimport { InputError } from '@backstage/errors';\nimport { DbSearchRow } from '../../tables';\nimport { NULL_SENTINEL } from './util';\n\n// These are excluded in the generic loop, either because they do not make sense\n// to index, or because they are special-case always inserted whether they are\n// null or not\nconst SPECIAL_KEYS = [\n 'attachments',\n 'relations',\n 'status',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.uid',\n 'metadata.etag',\n];\n\n// The maximum length allowed for search values. These columns are indexed, and\n// database engines do not like to index on massive values. For example,\n// postgres will balk after 8191 byte line sizes.\nconst MAX_KEY_LENGTH = 200;\nconst MAX_VALUE_LENGTH = 200;\n\ntype Kv = {\n key: string;\n value: unknown;\n};\n\n// Helper for traversing through a nested structure and outputting a list of\n// path->value entries of the leaves.\n//\n// For example, this yaml structure\n//\n// a: 1\n// b:\n// c: null\n// e: [f, g]\n// h:\n// - i: 1\n// j: k\n// - i: 2\n// j: l\n//\n// will result in\n//\n// \"a\", 1\n// \"b.c\", null\n// \"b.e\": \"f\"\n// \"b.e.f\": true\n// \"b.e\": \"g\"\n// \"b.e.g\": true\n// \"h.i\": 1\n// \"h.j\": \"k\"\n// \"h.i\": 2\n// \"h.j\": \"l\"\nexport function traverse(root: unknown): Kv[] {\n const output: Kv[] = [];\n // Use a Set for O(1) case-insensitive duplicate detection of synthetic\n // boolean path keys (e.g. \"metadata.tags.java\"), instead of the previous\n // O(n) Array.some() linear scan which caused O(n²) overall complexity\n // and severe event loop blocking for entities with large arrays.\n const seenPathKeys = new Set<string>();\n\n function visit(path: string, current: unknown) {\n if (SPECIAL_KEYS.includes(path)) {\n return;\n }\n\n // empty or scalar\n if (\n current === undefined ||\n current === null ||\n ['string', 'number', 'boolean'].includes(typeof current)\n ) {\n output.push({ key: path, value: current });\n return;\n }\n\n // unknown\n if (typeof current !== 'object') {\n return;\n }\n\n // array\n if (Array.isArray(current)) {\n for (const item of current) {\n // NOTE(freben): The reason that these are output in two different ways,\n // is to support use cases where you want to express that MORE than one\n // tag is present in a list. Since the EntityFilters structure is a\n // record, you can't have several entries of the same key. Therefore\n // you will have to match on\n //\n // { \"a.b\": [\"true\"], \"a.c\": [\"true\"] }\n //\n // rather than\n //\n // { \"a\": [\"b\", \"c\"] }\n //\n // because the latter means EITHER b or c has to be present.\n visit(path, item);\n if (typeof item === 'string') {\n const pathKey = `${path}.${item}`;\n const lowerKey = pathKey.toLocaleLowerCase('en-US');\n if (!seenPathKeys.has(lowerKey)) {\n seenPathKeys.add(lowerKey);\n output.push({ key: pathKey, value: true });\n }\n }\n }\n return;\n }\n\n // object\n for (const [key, value] of Object.entries(current!)) {\n visit(path ? `${path}.${key}` : key, value);\n }\n }\n\n visit('', root);\n\n return output;\n}\n\n// Translates a number of raw data rows to search table rows\nexport function mapToRows(input: Kv[], entityId: string): DbSearchRow[] {\n const result: DbSearchRow[] = [];\n\n for (const { key: rawKey, value: rawValue } of input) {\n const key = rawKey.toLocaleLowerCase('en-US');\n if (key.length > MAX_KEY_LENGTH) {\n continue;\n }\n if (rawValue === undefined || rawValue === null) {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n } else {\n const value = String(rawValue).toLocaleLowerCase('en-US');\n if (value.length <= MAX_VALUE_LENGTH) {\n result.push({\n entity_id: entityId,\n key,\n original_value: String(rawValue),\n value: value,\n });\n } else {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generates all of the search rows that are relevant for this entity.\n *\n * @param entityId - The uid of the entity\n * @param entity - The entity\n * @returns A list of entity search rows\n */\nexport function buildEntitySearch(\n entityId: string,\n entity: Entity,\n): DbSearchRow[] {\n // Visit the base structure recursively\n const raw = traverse(entity);\n\n // Start with some special keys that are always present because you want to\n // be able to easily search for null specifically\n raw.push({ key: 'metadata.name', value: entity.metadata.name });\n raw.push({ key: 'metadata.namespace', value: entity.metadata.namespace });\n raw.push({ key: 'metadata.uid', value: entity.metadata.uid });\n\n // Namespace not specified has the default value \"default\", so we want to\n // match on that as well\n if (!entity.metadata.namespace) {\n raw.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n raw.push({\n key: `relations.${relation.type}`,\n value: relation.targetRef,\n });\n }\n\n // This validates that there are no keys that vary only in casing, such\n // as `spec.foo` and `spec.Foo`.\n const keys = new Set(raw.map(r => r.key));\n const lowerKeys = new Set(raw.map(r => r.key.toLocaleLowerCase('en-US')));\n if (keys.size !== lowerKeys.size) {\n const difference = [];\n for (const key of keys) {\n const lower = key.toLocaleLowerCase('en-US');\n if (!lowerKeys.delete(lower)) {\n difference.push(lower);\n }\n }\n const badKeys = `'${difference.join(\"', '\")}'`;\n throw new InputError(\n `Entity has duplicate keys that vary only in casing, ${badKeys}`,\n );\n }\n\n const rows = mapToRows(raw, entityId);\n\n // Deduplicate by (key, value). Duplicate array values in the entity data\n // (e.g. tags: ['java', 'java']) produce identical search rows which would\n // violate the unique constraint on (entity_id, key, value).\n const seen = new Set<string>();\n return rows.filter(row => {\n const k = `${row.key}\\0${row.value === null ? NULL_SENTINEL : row.value}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n}\n"],"names":[],"mappings":";;;AAwBA,MAAM,YAAA,GAAe;AAAA,EACnB,aAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAA;AAwCO,SAAS,SAAS,IAAA,EAAqB;AAC5C,EAAA,MAAM,SAAe,EAAC;AAKtB,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AAErC,EAAA,SAAS,KAAA,CAAM,MAAc,OAAA,EAAkB;AAC7C,IAAA,IAAI,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA,EAAG;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IACE,OAAA,KAAY,MAAA,IACZ,OAAA,KAAY,IAAA,IACZ,CAAC,QAAA,EAAU,QAAA,EAAU,SAAS,CAAA,CAAE,QAAA,CAAS,OAAO,OAAO,CAAA,EACvD;AACA,MAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,MAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAc1B,QAAA,KAAA,CAAM,MAAM,IAAI,CAAA;AAChB,QAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,UAAA,MAAM,OAAA,GAAU,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAC/B,UAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,iBAAA,CAAkB,OAAO,CAAA;AAClD,UAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC/B,YAAA,YAAA,CAAa,IAAI,QAAQ,CAAA;AACzB,YAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,MAAM,CAAA;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAGA,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAQ,CAAA,EAAG;AACnD,MAAA,KAAA,CAAM,OAAO,CAAA,EAAG,IAAI,IAAI,GAAG,CAAA,CAAA,GAAK,KAAK,KAAK,CAAA;AAAA,IAC5C;AAAA,EACF;AAEA,EAAA,KAAA,CAAM,IAAI,IAAI,CAAA;AAEd,EAAA,OAAO,MAAA;AACT;;;;"}
1
+ {"version":3,"file":"buildEntitySearch.esm.js","sources":["../../../../../../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model';\nimport { InputError } from '@backstage/errors';\nimport { DbSearchRow } from '../../tables';\nimport { NULL_SENTINEL } from './util';\n\n// These are excluded in the generic loop, either because they do not make sense\n// to index, or because they are special-case always inserted whether they are\n// null or not\nconst SPECIAL_KEYS = [\n 'attachments',\n 'relations',\n 'status',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.uid',\n 'metadata.etag',\n];\n\n// The maximum length allowed for search values. These columns are indexed, and\n// database engines do not like to index on massive values. For example,\n// postgres will balk after 8191 byte line sizes.\nconst MAX_KEY_LENGTH = 200;\nconst MAX_VALUE_LENGTH = 200;\n\ntype Kv = {\n key: string;\n value: unknown;\n};\n\n// Helper for traversing through a nested structure and outputting a list of\n// path->value entries of the leaves.\n//\n// For example, this yaml structure\n//\n// a: 1\n// b:\n// c: null\n// e: [f, g]\n// h:\n// - i: 1\n// j: k\n// - i: 2\n// j: l\n//\n// will result in\n//\n// \"a\", 1\n// \"b.c\", null\n// \"b.e\": \"f\"\n// \"b.e.f\": true\n// \"b.e\": \"g\"\n// \"b.e.g\": true\n// \"h.i\": 1\n// \"h.j\": \"k\"\n// \"h.i\": 2\n// \"h.j\": \"l\"\nexport function traverse(root: unknown): Kv[] {\n const output: Kv[] = [];\n // Use a Set for O(1) case-insensitive duplicate detection of synthetic\n // boolean path keys (e.g. \"metadata.tags.java\"), instead of the previous\n // O(n) Array.some() linear scan which caused O(n²) overall complexity\n // and severe event loop blocking for entities with large arrays.\n const seenPathKeys = new Set<string>();\n\n function visit(path: string, current: unknown) {\n if (SPECIAL_KEYS.includes(path)) {\n return;\n }\n\n // empty or scalar\n if (\n current === undefined ||\n current === null ||\n ['string', 'number', 'boolean'].includes(typeof current)\n ) {\n output.push({ key: path, value: current });\n return;\n }\n\n // unknown\n if (typeof current !== 'object') {\n return;\n }\n\n // array\n if (Array.isArray(current)) {\n for (const item of current) {\n // NOTE(freben): The reason that these are output in two different ways,\n // is to support use cases where you want to express that MORE than one\n // tag is present in a list. Since the EntityFilters structure is a\n // record, you can't have several entries of the same key. Therefore\n // you will have to match on\n //\n // { \"a.b\": [\"true\"], \"a.c\": [\"true\"] }\n //\n // rather than\n //\n // { \"a\": [\"b\", \"c\"] }\n //\n // because the latter means EITHER b or c has to be present.\n visit(path, item);\n if (typeof item === 'string') {\n const pathKey = `${path}.${item}`;\n const lowerKey = pathKey.toLowerCase();\n if (!seenPathKeys.has(lowerKey)) {\n seenPathKeys.add(lowerKey);\n output.push({ key: pathKey, value: true });\n }\n }\n }\n return;\n }\n\n // object\n for (const [key, value] of Object.entries(current!)) {\n visit(path ? `${path}.${key}` : key, value);\n }\n }\n\n visit('', root);\n\n return output;\n}\n\n// Translates a number of raw data rows to search table rows\nexport function mapToRows(input: Kv[], entityId: string): DbSearchRow[] {\n const result: DbSearchRow[] = [];\n\n for (const { key: rawKey, value: rawValue } of input) {\n const key = rawKey.toLowerCase();\n if (key.length > MAX_KEY_LENGTH) {\n continue;\n }\n if (rawValue === undefined || rawValue === null) {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n } else {\n const value = String(rawValue).toLowerCase();\n if (value.length <= MAX_VALUE_LENGTH) {\n result.push({\n entity_id: entityId,\n key,\n original_value: String(rawValue),\n value: value,\n });\n } else {\n result.push({\n entity_id: entityId,\n key,\n original_value: null,\n value: null,\n });\n }\n }\n }\n\n return result;\n}\n\n/**\n * Generates all of the search rows that are relevant for this entity.\n *\n * @param entityId - The uid of the entity\n * @param entity - The entity\n * @returns A list of entity search rows\n */\nexport function buildEntitySearch(\n entityId: string,\n entity: Entity,\n): DbSearchRow[] {\n // Visit the base structure recursively\n const raw = traverse(entity);\n\n // Start with some special keys that are always present because you want to\n // be able to easily search for null specifically\n raw.push({ key: 'metadata.name', value: entity.metadata.name });\n raw.push({ key: 'metadata.namespace', value: entity.metadata.namespace });\n raw.push({ key: 'metadata.uid', value: entity.metadata.uid });\n\n // Namespace not specified has the default value \"default\", so we want to\n // match on that as well\n if (!entity.metadata.namespace) {\n raw.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n raw.push({\n key: `relations.${relation.type}`,\n value: relation.targetRef,\n });\n }\n\n // This validates that there are no keys that vary only in casing, such\n // as `spec.foo` and `spec.Foo`.\n const keys = new Set(raw.map(r => r.key));\n const lowerKeys = new Set(raw.map(r => r.key.toLowerCase()));\n if (keys.size !== lowerKeys.size) {\n const difference = [];\n for (const key of keys) {\n const lower = key.toLowerCase();\n if (!lowerKeys.delete(lower)) {\n difference.push(lower);\n }\n }\n const badKeys = `'${difference.join(\"', '\")}'`;\n throw new InputError(\n `Entity has duplicate keys that vary only in casing, ${badKeys}`,\n );\n }\n\n const rows = mapToRows(raw, entityId);\n\n // Deduplicate by (key, value). Duplicate array values in the entity data\n // (e.g. tags: ['java', 'java']) produce identical search rows which would\n // violate the unique constraint on (entity_id, key, value).\n const seen = new Set<string>();\n return rows.filter(row => {\n const k = `${row.key}\\0${row.value === null ? NULL_SENTINEL : row.value}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n}\n"],"names":[],"mappings":";;;AAwBA,MAAM,YAAA,GAAe;AAAA,EACnB,aAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAA;AAwCO,SAAS,SAAS,IAAA,EAAqB;AAC5C,EAAA,MAAM,SAAe,EAAC;AAKtB,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AAErC,EAAA,SAAS,KAAA,CAAM,MAAc,OAAA,EAAkB;AAC7C,IAAA,IAAI,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA,EAAG;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IACE,OAAA,KAAY,MAAA,IACZ,OAAA,KAAY,IAAA,IACZ,CAAC,QAAA,EAAU,QAAA,EAAU,SAAS,CAAA,CAAE,QAAA,CAAS,OAAO,OAAO,CAAA,EACvD;AACA,MAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,IAAA,EAAM,KAAA,EAAO,SAAS,CAAA;AACzC,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,MAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAc1B,QAAA,KAAA,CAAM,MAAM,IAAI,CAAA;AAChB,QAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,UAAA,MAAM,OAAA,GAAU,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA;AAC/B,UAAA,MAAM,QAAA,GAAW,QAAQ,WAAA,EAAY;AACrC,UAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC/B,YAAA,YAAA,CAAa,IAAI,QAAQ,CAAA;AACzB,YAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,MAAM,CAAA;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAGA,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAQ,CAAA,EAAG;AACnD,MAAA,KAAA,CAAM,OAAO,CAAA,EAAG,IAAI,IAAI,GAAG,CAAA,CAAA,GAAK,KAAK,KAAK,CAAA;AAAA,IAC5C;AAAA,EACF;AAEA,EAAA,KAAA,CAAM,IAAI,IAAI,CAAA;AAEd,EAAA,OAAO,MAAA;AACT;;;;"}
@@ -61,19 +61,19 @@ function buildEntitySearch(entity) {
61
61
  if (entity.metadata?.name) {
62
62
  rows.push({
63
63
  key: "metadata.name",
64
- value: entity.metadata.name.toLocaleLowerCase("en-US")
64
+ value: entity.metadata.name.toLowerCase()
65
65
  });
66
66
  }
67
67
  if (entity.metadata?.namespace) {
68
68
  rows.push({
69
69
  key: "metadata.namespace",
70
- value: entity.metadata.namespace.toLocaleLowerCase("en-US")
70
+ value: entity.metadata.namespace.toLowerCase()
71
71
  });
72
72
  }
73
73
  if (entity.metadata?.uid) {
74
74
  rows.push({
75
75
  key: "metadata.uid",
76
- value: entity.metadata.uid.toLocaleLowerCase("en-US")
76
+ value: entity.metadata.uid.toLowerCase()
77
77
  });
78
78
  }
79
79
  if (!entity.metadata.namespace) {
@@ -81,8 +81,8 @@ function buildEntitySearch(entity) {
81
81
  }
82
82
  for (const relation of entity.relations ?? []) {
83
83
  rows.push({
84
- key: `relations.${relation.type.toLocaleLowerCase("en-US")}`,
85
- value: relation.targetRef.toLocaleLowerCase("en-US")
84
+ key: `relations.${relation.type.toLowerCase()}`,
85
+ value: relation.targetRef.toLowerCase()
86
86
  });
87
87
  }
88
88
  return rows;
@@ -96,7 +96,7 @@ function createFilter(filterOrFilters) {
96
96
  const rows = buildEntitySearch(entity);
97
97
  return filters.some((filter) => {
98
98
  for (const [key, expectedValue] of Object.entries(filter)) {
99
- const searchValues = rows.filter((row) => row.key === key.toLocaleLowerCase("en-US")).map((row) => row.value?.toString().toLocaleLowerCase("en-US"));
99
+ const searchValues = rows.filter((row) => row.key === key.toLowerCase()).map((row) => row.value?.toString().toLowerCase());
100
100
  if (searchValues.length === 0) {
101
101
  return false;
102
102
  }
@@ -105,12 +105,10 @@ function createFilter(filterOrFilters) {
105
105
  }
106
106
  if (Array.isArray(expectedValue)) {
107
107
  return expectedValue.some(
108
- (value) => searchValues?.includes(String(value).toLocaleLowerCase("en-US"))
108
+ (value) => searchValues?.includes(String(value).toLowerCase())
109
109
  );
110
110
  }
111
- if (!searchValues?.includes(
112
- String(expectedValue).toLocaleLowerCase("en-US")
113
- )) {
111
+ if (!searchValues?.includes(String(expectedValue).toLowerCase())) {
114
112
  return false;
115
113
  }
116
114
  }
@@ -161,11 +159,11 @@ function applyOrdering(entities, order) {
161
159
  const aRows = searchMap.get(a);
162
160
  const bRows = searchMap.get(b);
163
161
  for (const { field, order: dir } of orders) {
164
- const key = field.toLocaleLowerCase("en-US");
165
- const aRow = aRows.find((r) => r.key.toLocaleLowerCase("en-US") === key);
166
- const bRow = bRows.find((r) => r.key.toLocaleLowerCase("en-US") === key);
167
- const aValue = aRow?.value !== null && aRow?.value !== void 0 ? String(aRow.value).toLocaleLowerCase("en-US") : null;
168
- const bValue = bRow?.value !== null && bRow?.value !== void 0 ? String(bRow.value).toLocaleLowerCase("en-US") : null;
162
+ const key = field.toLowerCase();
163
+ const aRow = aRows.find((r) => r.key.toLowerCase() === key);
164
+ const bRow = bRows.find((r) => r.key.toLowerCase() === key);
165
+ const aValue = aRow?.value !== null && aRow?.value !== void 0 ? String(aRow.value).toLowerCase() : null;
166
+ const bValue = bRow?.value !== null && bRow?.value !== void 0 ? String(bRow.value).toLowerCase() : null;
169
167
  if (aValue === null && bValue === null) continue;
170
168
  if (aValue === null) return 1;
171
169
  if (bValue === null) return -1;
@@ -183,16 +181,16 @@ function applyFullTextFilter(entities, fullTextFilter) {
183
181
  if (!fullTextFilter?.term?.trim()) {
184
182
  return entities;
185
183
  }
186
- const term = fullTextFilter.term.trim().toLocaleLowerCase("en-US");
187
- const fields = fullTextFilter.fields?.map((f) => f.toLocaleLowerCase("en-US"));
184
+ const term = fullTextFilter.term.trim().toLowerCase();
185
+ const fields = fullTextFilter.fields?.map((f) => f.toLowerCase());
188
186
  return entities.filter((entity) => {
189
187
  const rows = buildEntitySearch(entity);
190
188
  return rows.some((row) => {
191
- if (fields?.length && !fields.includes(row.key.toLocaleLowerCase("en-US"))) {
189
+ if (fields?.length && !fields.includes(row.key.toLowerCase())) {
192
190
  return false;
193
191
  }
194
192
  if (row.value === null || row.value === void 0) return false;
195
- const value = String(row.value).toLocaleLowerCase("en-US");
193
+ const value = String(row.value).toLowerCase();
196
194
  return value.includes(term);
197
195
  });
198
196
  });
@@ -353,9 +351,7 @@ class InMemoryCatalogClient {
353
351
  for (const entity of filteredEntities) {
354
352
  const rows = buildEntitySearch(entity);
355
353
  const uniqueValues = new Set(
356
- rows.filter(
357
- (row) => row.key.toLocaleLowerCase("en-US") === facet.toLocaleLowerCase("en-US")
358
- ).map((row) => row.value).filter((v) => v !== null && v !== void 0).map((v) => String(v))
354
+ rows.filter((row) => row.key.toLowerCase() === facet.toLowerCase()).map((row) => row.value).filter((v) => v !== null && v !== void 0).map((v) => String(v))
359
355
  );
360
356
  for (const value of uniqueValues) {
361
357
  facetValues.set(value, (facetValues.get(value) ?? 0) + 1);
@@ -1 +1 @@
1
- {"version":3,"file":"InMemoryCatalogClient.cjs.js","sources":["../../src/testUtils/InMemoryCatalogClient.ts"],"sourcesContent":["/*\n * Copyright 2024 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 {\n AddLocationRequest,\n AddLocationResponse,\n CATALOG_FILTER_EXISTS,\n CatalogApi,\n EntityFilterQuery,\n EntityOrderQuery,\n GetEntitiesByRefsRequest,\n GetEntitiesByRefsResponse,\n GetEntitiesRequest,\n GetEntitiesResponse,\n GetEntityAncestorsRequest,\n GetEntityAncestorsResponse,\n GetEntityFacetsRequest,\n GetEntityFacetsResponse,\n GetLocationsResponse,\n Location,\n QueryEntitiesRequest,\n QueryEntitiesResponse,\n QueryLocationsInitialRequest,\n QueryLocationsRequest,\n QueryLocationsResponse,\n StreamEntitiesRequest,\n ValidateEntityResponse,\n} from '@backstage/catalog-client';\nimport {\n CompoundEntityRef,\n DEFAULT_NAMESPACE,\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n InputError,\n NotFoundError,\n NotImplementedError,\n} from '@backstage/errors';\nimport {\n FilterPredicate,\n filterPredicateToFilterFunction,\n} from '@backstage/filter-predicates';\nimport lodash from 'lodash';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { traverse } from '../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch';\nimport type {\n AnalyzeLocationRequest,\n AnalyzeLocationResponse,\n} from '@backstage/plugin-catalog-common';\nimport { DEFAULT_STREAM_ENTITIES_LIMIT } from '../constants.ts';\n\nfunction makeCursor(data: Record<string, unknown>): string {\n return btoa(JSON.stringify(data));\n}\n\nfunction parseCursor(cursor: string): Record<string, unknown> {\n return JSON.parse(atob(cursor));\n}\n\n// CATALOG_FILTER_EXISTS is a Symbol that doesn't survive JSON serialization,\n// so we swap it with a sentinel string when encoding/decoding cursors.\nconst FILTER_EXISTS_SENTINEL = '\\0CATALOG_FILTER_EXISTS';\n\nfunction serializeFilterValue(v: unknown): unknown {\n if (v === CATALOG_FILTER_EXISTS) return FILTER_EXISTS_SENTINEL;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === CATALOG_FILTER_EXISTS ? FILTER_EXISTS_SENTINEL : x,\n );\n }\n return v;\n}\n\nfunction deserializeFilterValue(v: unknown): unknown {\n if (v === FILTER_EXISTS_SENTINEL) return CATALOG_FILTER_EXISTS;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === FILTER_EXISTS_SENTINEL ? CATALOG_FILTER_EXISTS : x,\n );\n }\n return v;\n}\n\nfunction serializeFilter(filter?: EntityFilterQuery): any[] | undefined {\n if (!filter) return undefined;\n return [filter]\n .flat()\n .map(f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]) => [k, serializeFilterValue(v)]),\n ),\n );\n}\n\nfunction deserializeFilter(filter?: any[]): EntityFilterQuery | undefined {\n if (!filter) return undefined;\n return filter.map(\n f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]: [string, any]) => [\n k,\n deserializeFilterValue(v),\n ]),\n ) as Record<string, string | symbol | (string | symbol)[]>,\n );\n}\n\nfunction buildEntitySearch(entity: Entity) {\n const rows = traverse(entity);\n\n if (entity.metadata?.name) {\n rows.push({\n key: 'metadata.name',\n value: entity.metadata.name.toLocaleLowerCase('en-US'),\n });\n }\n if (entity.metadata?.namespace) {\n rows.push({\n key: 'metadata.namespace',\n value: entity.metadata.namespace.toLocaleLowerCase('en-US'),\n });\n }\n if (entity.metadata?.uid) {\n rows.push({\n key: 'metadata.uid',\n value: entity.metadata.uid.toLocaleLowerCase('en-US'),\n });\n }\n\n if (!entity.metadata.namespace) {\n rows.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n rows.push({\n key: `relations.${relation.type.toLocaleLowerCase('en-US')}`,\n value: relation.targetRef.toLocaleLowerCase('en-US'),\n });\n }\n\n return rows;\n}\n\nfunction createFilter(\n filterOrFilters?: EntityFilterQuery,\n): (entity: Entity) => boolean {\n if (!filterOrFilters) {\n return () => true;\n }\n\n const filters = [filterOrFilters].flat();\n\n return entity => {\n const rows = buildEntitySearch(entity);\n\n return filters.some(filter => {\n for (const [key, expectedValue] of Object.entries(filter)) {\n const searchValues = rows\n .filter(row => row.key === key.toLocaleLowerCase('en-US'))\n .map(row => row.value?.toString().toLocaleLowerCase('en-US'));\n\n if (searchValues.length === 0) {\n return false;\n }\n if (expectedValue === CATALOG_FILTER_EXISTS) {\n continue;\n }\n if (Array.isArray(expectedValue)) {\n return expectedValue.some(value =>\n searchValues?.includes(String(value).toLocaleLowerCase('en-US')),\n );\n }\n if (\n !searchValues?.includes(\n String(expectedValue).toLocaleLowerCase('en-US'),\n )\n ) {\n return false;\n }\n }\n return true;\n });\n };\n}\n\n// Resolves a dot-separated field path against an entity, handling keys that\n// themselves contain dots (e.g. annotation keys like \"backstage.io/orphan\").\n// This matches the backend's parseEntityTransformParams implementation.\nfunction getPathArrayAndValue(\n input: Entity,\n field: string,\n): [string[], unknown] {\n return field.split('.').reduce(\n ([pathArray, inputSubset], pathPart, index, fieldParts) => {\n if (lodash.hasIn(inputSubset, pathPart)) {\n return [pathArray.concat(pathPart), (inputSubset as any)[pathPart]];\n } else if (fieldParts[index + 1] !== undefined) {\n fieldParts[index + 1] = `${pathPart}.${fieldParts[index + 1]}`;\n return [pathArray, inputSubset];\n }\n return [pathArray, undefined];\n },\n [[] as string[], input as unknown],\n );\n}\n\nfunction applyFieldsFilter(entity: Entity, fields?: string[]): Entity {\n if (!fields?.length) {\n return entity;\n }\n const output: Record<string, any> = {};\n for (const field of fields) {\n const [pathArray, value] = getPathArrayAndValue(entity, field);\n if (value !== undefined) {\n lodash.set(output, pathArray, value);\n }\n }\n return output as Entity;\n}\n\nfunction applyOrdering(entities: Entity[], order?: EntityOrderQuery): Entity[] {\n if (!order) {\n return entities;\n }\n const orders = [order].flat();\n if (orders.length === 0) {\n return entities;\n }\n\n const searchMap = new Map<Entity, Array<{ key: string; value: unknown }>>();\n for (const entity of entities) {\n searchMap.set(entity, buildEntitySearch(entity));\n }\n\n return [...entities].sort((a, b) => {\n const aRows = searchMap.get(a)!;\n const bRows = searchMap.get(b)!;\n\n for (const { field, order: dir } of orders) {\n const key = field.toLocaleLowerCase('en-US');\n const aRow = aRows.find(r => r.key.toLocaleLowerCase('en-US') === key);\n const bRow = bRows.find(r => r.key.toLocaleLowerCase('en-US') === key);\n const aValue =\n aRow?.value !== null && aRow?.value !== undefined\n ? String(aRow.value).toLocaleLowerCase('en-US')\n : null;\n const bValue =\n bRow?.value !== null && bRow?.value !== undefined\n ? String(bRow.value).toLocaleLowerCase('en-US')\n : null;\n\n if (aValue === null && bValue === null) continue;\n if (aValue === null) return 1;\n if (bValue === null) return -1;\n\n if (aValue < bValue) return dir === 'asc' ? -1 : 1;\n if (aValue > bValue) return dir === 'asc' ? 1 : -1;\n }\n\n // Stable tie-breaker by entity ref, matching backend behavior\n const aRef = stringifyEntityRef(a);\n const bRef = stringifyEntityRef(b);\n if (aRef < bRef) return -1;\n if (aRef > bRef) return 1;\n return 0;\n });\n}\n\nfunction applyFullTextFilter(\n entities: Entity[],\n fullTextFilter?: { term: string; fields?: string[] },\n): Entity[] {\n if (!fullTextFilter?.term?.trim()) {\n return entities;\n }\n const term = fullTextFilter.term.trim().toLocaleLowerCase('en-US');\n const fields = fullTextFilter.fields?.map(f => f.toLocaleLowerCase('en-US'));\n\n return entities.filter(entity => {\n const rows = buildEntitySearch(entity);\n return rows.some(row => {\n if (\n fields?.length &&\n !fields.includes(row.key.toLocaleLowerCase('en-US'))\n ) {\n return false;\n }\n if (row.value === null || row.value === undefined) return false;\n const value = String(row.value).toLocaleLowerCase('en-US');\n return value.includes(term);\n });\n });\n}\n\n/**\n * Implements a fake catalog client that stores entities in memory.\n * Supports filtering, ordering, pagination, full-text search, and field\n * projection for entity query methods. Location and validation methods\n * throw {@link @backstage/errors#NotImplementedError}.\n *\n * @public\n */\nexport class InMemoryCatalogClient implements CatalogApi {\n #entities: Entity[];\n\n constructor(options?: { entities?: Entity[] }) {\n this.#entities = options?.entities?.slice() ?? [];\n }\n\n async getEntities(\n request?: GetEntitiesRequest,\n ): Promise<GetEntitiesResponse> {\n const filter = createFilter(request?.filter);\n let items = this.#entities.filter(filter);\n items = applyOrdering(items, request?.order);\n\n let offset = request?.offset ?? 0;\n let limit = request?.limit;\n\n if (request?.after) {\n try {\n const cursor = parseCursor(request.after);\n if (cursor.offset !== undefined) offset = cursor.offset as number;\n if (cursor.limit !== undefined) limit = cursor.limit as number;\n } catch {\n throw new InputError('Invalid cursor');\n }\n }\n\n if (offset > 0) {\n items = items.slice(offset);\n }\n if (limit !== undefined) {\n items = items.slice(0, limit);\n }\n\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request.fields))\n : items,\n };\n }\n\n async getEntitiesByRefs(\n request: GetEntitiesByRefsRequest,\n ): Promise<GetEntitiesByRefsResponse> {\n const filter = createFilter(request.filter);\n const queryFilter = request.query\n ? filterPredicateToFilterFunction(request.query)\n : undefined;\n const refMap = this.#createEntityRefMap();\n const items = request.entityRefs\n .map(ref => refMap.get(ref))\n .map(e =>\n e && filter(e) && (!queryFilter || queryFilter(e)) ? e : undefined,\n );\n return {\n items: request.fields\n ? items.map(e => (e ? applyFieldsFilter(e, request.fields) : undefined))\n : items,\n };\n }\n\n async queryEntities(\n request?: QueryEntitiesRequest,\n ): Promise<QueryEntitiesResponse> {\n // Decode query parameters from cursor or from the request directly\n let filter: EntityFilterQuery | undefined;\n let query: FilterPredicate | undefined;\n let orderFields: EntityOrderQuery | undefined;\n let fullTextFilter: { term: string; fields?: string[] } | undefined;\n let offset: number;\n let limit: number | undefined;\n\n if (request && 'cursor' in request) {\n let c: Record<string, unknown>;\n try {\n c = parseCursor(request.cursor);\n } catch {\n throw new InputError('Invalid cursor');\n }\n filter = deserializeFilter(c.filter as any[]);\n query = c.query as FilterPredicate | undefined;\n orderFields = c.orderFields as EntityOrderQuery | undefined;\n fullTextFilter = c.fullTextFilter as typeof fullTextFilter;\n offset = c.offset as number;\n limit = request.limit;\n } else {\n filter = request?.filter;\n query = request?.query;\n orderFields = request?.orderFields;\n fullTextFilter = request?.fullTextFilter;\n offset = request?.offset ?? 0;\n limit = request?.limit;\n }\n\n // Apply filter\n let items = this.#entities.filter(createFilter(filter));\n\n // Apply predicate-based query filter\n if (query) {\n items = items.filter(filterPredicateToFilterFunction(query));\n }\n\n // Apply full-text filter, defaulting to the sort field or metadata.uid\n if (fullTextFilter) {\n const orderFieldsList = orderFields ? [orderFields].flat() : [];\n items = applyFullTextFilter(items, {\n ...fullTextFilter,\n fields: fullTextFilter.fields ?? [\n orderFieldsList[0]?.field ?? 'metadata.uid',\n ],\n });\n }\n\n items = applyOrdering(items, orderFields);\n\n const totalItems = items.length;\n\n // No pagination requested, return all items\n if (limit === undefined && offset === 0) {\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request?.fields))\n : items,\n pageInfo: {},\n totalItems,\n };\n }\n\n const effectiveLimit = limit ?? totalItems;\n const pageItems = items.slice(offset, offset + effectiveLimit);\n\n const cursorBase = {\n filter: serializeFilter(filter),\n query,\n orderFields,\n fullTextFilter,\n totalItems,\n };\n const pageInfo: QueryEntitiesResponse['pageInfo'] = {};\n if (offset + effectiveLimit < totalItems) {\n pageInfo.nextCursor = makeCursor({\n ...cursorBase,\n offset: offset + effectiveLimit,\n });\n }\n if (offset > 0) {\n pageInfo.prevCursor = makeCursor({\n ...cursorBase,\n offset: Math.max(0, offset - effectiveLimit),\n });\n }\n\n return {\n items: request?.fields\n ? pageItems.map(e => applyFieldsFilter(e, request?.fields))\n : pageItems,\n totalItems,\n pageInfo,\n };\n }\n\n async getEntityAncestors(\n request: GetEntityAncestorsRequest,\n ): Promise<GetEntityAncestorsResponse> {\n const entity = this.#createEntityRefMap().get(request.entityRef);\n if (!entity) {\n throw new NotFoundError(`Entity with ref ${request.entityRef} not found`);\n }\n return {\n items: [{ entity, parentEntityRefs: [] }],\n rootEntityRef: request.entityRef,\n };\n }\n\n async getEntityByRef(\n entityRef: string | CompoundEntityRef,\n ): Promise<Entity | undefined> {\n return this.#createEntityRefMap().get(\n stringifyEntityRef(parseEntityRef(entityRef)),\n );\n }\n\n async removeEntityByUid(uid: string): Promise<void> {\n const index = this.#entities.findIndex(e => e.metadata.uid === uid);\n if (index !== -1) {\n this.#entities.splice(index, 1);\n }\n }\n\n async refreshEntity(_entityRef: string): Promise<void> {}\n\n async getEntityFacets(\n request: GetEntityFacetsRequest,\n ): Promise<GetEntityFacetsResponse> {\n const filter = createFilter(request.filter);\n let filteredEntities = this.#entities.filter(filter);\n if (request.query) {\n filteredEntities = filteredEntities.filter(\n filterPredicateToFilterFunction(request.query),\n );\n }\n const facets = Object.fromEntries(\n request.facets.map(facet => {\n const facetValues = new Map<string, number>();\n for (const entity of filteredEntities) {\n const rows = buildEntitySearch(entity);\n // Use a Set to count each distinct value once per entity,\n // matching the backend's count(DISTINCT entity_id) behavior\n const uniqueValues = new Set(\n rows\n .filter(\n row =>\n row.key.toLocaleLowerCase('en-US') ===\n facet.toLocaleLowerCase('en-US'),\n )\n .map(row => row.value)\n .filter(v => v !== null && v !== undefined)\n .map(v => String(v)),\n );\n for (const value of uniqueValues) {\n facetValues.set(value, (facetValues.get(value) ?? 0) + 1);\n }\n }\n const counts = Array.from(facetValues.entries()).map(\n ([value, count]) => ({ value, count }),\n );\n return [facet, counts];\n }),\n );\n return {\n facets,\n };\n }\n\n async getLocations(_request?: {}): Promise<GetLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async queryLocations(\n _request?: QueryLocationsRequest,\n ): Promise<QueryLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamLocations(\n _request?: QueryLocationsInitialRequest,\n ): AsyncIterable<Location[]> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationById(_id: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByRef(_locationRef: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async addLocation(\n _location: AddLocationRequest,\n ): Promise<AddLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async removeLocationById(_id: string): Promise<void> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async updateLocation(\n _id: string,\n _location: { type?: string; target: string },\n ): Promise<Location> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByEntity(\n _entityRef: string | CompoundEntityRef,\n ): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async validateEntity(\n _entity: Entity,\n _locationRef: string,\n ): Promise<ValidateEntityResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async analyzeLocation(\n _location: AnalyzeLocationRequest,\n ): Promise<AnalyzeLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamEntities(\n request?: StreamEntitiesRequest,\n ): AsyncIterable<Entity[]> {\n let cursor: string | undefined = undefined;\n const limit = request?.pageSize ?? DEFAULT_STREAM_ENTITIES_LIMIT;\n do {\n const res = await this.queryEntities(\n cursor ? { ...request, limit, cursor } : { ...request, limit },\n );\n\n yield res.items;\n\n cursor = res.pageInfo.nextCursor;\n } while (cursor);\n }\n\n #createEntityRefMap() {\n return new Map(this.#entities.map(e => [stringifyEntityRef(e), e]));\n }\n}\n"],"names":["CATALOG_FILTER_EXISTS","traverse","DEFAULT_NAMESPACE","lodash","stringifyEntityRef","InputError","filterPredicateToFilterFunction","NotFoundError","parseEntityRef","NotImplementedError","DEFAULT_STREAM_ENTITIES_LIMIT"],"mappings":"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"}
1
+ {"version":3,"file":"InMemoryCatalogClient.cjs.js","sources":["../../src/testUtils/InMemoryCatalogClient.ts"],"sourcesContent":["/*\n * Copyright 2024 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 {\n AddLocationRequest,\n AddLocationResponse,\n CATALOG_FILTER_EXISTS,\n CatalogApi,\n EntityFilterQuery,\n EntityOrderQuery,\n GetEntitiesByRefsRequest,\n GetEntitiesByRefsResponse,\n GetEntitiesRequest,\n GetEntitiesResponse,\n GetEntityAncestorsRequest,\n GetEntityAncestorsResponse,\n GetEntityFacetsRequest,\n GetEntityFacetsResponse,\n GetLocationsResponse,\n Location,\n QueryEntitiesRequest,\n QueryEntitiesResponse,\n QueryLocationsInitialRequest,\n QueryLocationsRequest,\n QueryLocationsResponse,\n StreamEntitiesRequest,\n ValidateEntityResponse,\n} from '@backstage/catalog-client';\nimport {\n CompoundEntityRef,\n DEFAULT_NAMESPACE,\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n InputError,\n NotFoundError,\n NotImplementedError,\n} from '@backstage/errors';\nimport {\n FilterPredicate,\n filterPredicateToFilterFunction,\n} from '@backstage/filter-predicates';\nimport lodash from 'lodash';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { traverse } from '../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch';\nimport type {\n AnalyzeLocationRequest,\n AnalyzeLocationResponse,\n} from '@backstage/plugin-catalog-common';\nimport { DEFAULT_STREAM_ENTITIES_LIMIT } from '../constants.ts';\n\nfunction makeCursor(data: Record<string, unknown>): string {\n return btoa(JSON.stringify(data));\n}\n\nfunction parseCursor(cursor: string): Record<string, unknown> {\n return JSON.parse(atob(cursor));\n}\n\n// CATALOG_FILTER_EXISTS is a Symbol that doesn't survive JSON serialization,\n// so we swap it with a sentinel string when encoding/decoding cursors.\nconst FILTER_EXISTS_SENTINEL = '\\0CATALOG_FILTER_EXISTS';\n\nfunction serializeFilterValue(v: unknown): unknown {\n if (v === CATALOG_FILTER_EXISTS) return FILTER_EXISTS_SENTINEL;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === CATALOG_FILTER_EXISTS ? FILTER_EXISTS_SENTINEL : x,\n );\n }\n return v;\n}\n\nfunction deserializeFilterValue(v: unknown): unknown {\n if (v === FILTER_EXISTS_SENTINEL) return CATALOG_FILTER_EXISTS;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === FILTER_EXISTS_SENTINEL ? CATALOG_FILTER_EXISTS : x,\n );\n }\n return v;\n}\n\nfunction serializeFilter(filter?: EntityFilterQuery): any[] | undefined {\n if (!filter) return undefined;\n return [filter]\n .flat()\n .map(f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]) => [k, serializeFilterValue(v)]),\n ),\n );\n}\n\nfunction deserializeFilter(filter?: any[]): EntityFilterQuery | undefined {\n if (!filter) return undefined;\n return filter.map(\n f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]: [string, any]) => [\n k,\n deserializeFilterValue(v),\n ]),\n ) as Record<string, string | symbol | (string | symbol)[]>,\n );\n}\n\nfunction buildEntitySearch(entity: Entity) {\n const rows = traverse(entity);\n\n if (entity.metadata?.name) {\n rows.push({\n key: 'metadata.name',\n value: entity.metadata.name.toLowerCase(),\n });\n }\n if (entity.metadata?.namespace) {\n rows.push({\n key: 'metadata.namespace',\n value: entity.metadata.namespace.toLowerCase(),\n });\n }\n if (entity.metadata?.uid) {\n rows.push({\n key: 'metadata.uid',\n value: entity.metadata.uid.toLowerCase(),\n });\n }\n\n if (!entity.metadata.namespace) {\n rows.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n rows.push({\n key: `relations.${relation.type.toLowerCase()}`,\n value: relation.targetRef.toLowerCase(),\n });\n }\n\n return rows;\n}\n\nfunction createFilter(\n filterOrFilters?: EntityFilterQuery,\n): (entity: Entity) => boolean {\n if (!filterOrFilters) {\n return () => true;\n }\n\n const filters = [filterOrFilters].flat();\n\n return entity => {\n const rows = buildEntitySearch(entity);\n\n return filters.some(filter => {\n for (const [key, expectedValue] of Object.entries(filter)) {\n const searchValues = rows\n .filter(row => row.key === key.toLowerCase())\n .map(row => row.value?.toString().toLowerCase());\n\n if (searchValues.length === 0) {\n return false;\n }\n if (expectedValue === CATALOG_FILTER_EXISTS) {\n continue;\n }\n if (Array.isArray(expectedValue)) {\n return expectedValue.some(value =>\n searchValues?.includes(String(value).toLowerCase()),\n );\n }\n if (!searchValues?.includes(String(expectedValue).toLowerCase())) {\n return false;\n }\n }\n return true;\n });\n };\n}\n\n// Resolves a dot-separated field path against an entity, handling keys that\n// themselves contain dots (e.g. annotation keys like \"backstage.io/orphan\").\n// This matches the backend's parseEntityTransformParams implementation.\nfunction getPathArrayAndValue(\n input: Entity,\n field: string,\n): [string[], unknown] {\n return field.split('.').reduce(\n ([pathArray, inputSubset], pathPart, index, fieldParts) => {\n if (lodash.hasIn(inputSubset, pathPart)) {\n return [pathArray.concat(pathPart), (inputSubset as any)[pathPart]];\n } else if (fieldParts[index + 1] !== undefined) {\n fieldParts[index + 1] = `${pathPart}.${fieldParts[index + 1]}`;\n return [pathArray, inputSubset];\n }\n return [pathArray, undefined];\n },\n [[] as string[], input as unknown],\n );\n}\n\nfunction applyFieldsFilter(entity: Entity, fields?: string[]): Entity {\n if (!fields?.length) {\n return entity;\n }\n const output: Record<string, any> = {};\n for (const field of fields) {\n const [pathArray, value] = getPathArrayAndValue(entity, field);\n if (value !== undefined) {\n lodash.set(output, pathArray, value);\n }\n }\n return output as Entity;\n}\n\nfunction applyOrdering(entities: Entity[], order?: EntityOrderQuery): Entity[] {\n if (!order) {\n return entities;\n }\n const orders = [order].flat();\n if (orders.length === 0) {\n return entities;\n }\n\n const searchMap = new Map<Entity, Array<{ key: string; value: unknown }>>();\n for (const entity of entities) {\n searchMap.set(entity, buildEntitySearch(entity));\n }\n\n return [...entities].sort((a, b) => {\n const aRows = searchMap.get(a)!;\n const bRows = searchMap.get(b)!;\n\n for (const { field, order: dir } of orders) {\n const key = field.toLowerCase();\n const aRow = aRows.find(r => r.key.toLowerCase() === key);\n const bRow = bRows.find(r => r.key.toLowerCase() === key);\n const aValue =\n aRow?.value !== null && aRow?.value !== undefined\n ? String(aRow.value).toLowerCase()\n : null;\n const bValue =\n bRow?.value !== null && bRow?.value !== undefined\n ? String(bRow.value).toLowerCase()\n : null;\n\n if (aValue === null && bValue === null) continue;\n if (aValue === null) return 1;\n if (bValue === null) return -1;\n\n if (aValue < bValue) return dir === 'asc' ? -1 : 1;\n if (aValue > bValue) return dir === 'asc' ? 1 : -1;\n }\n\n // Stable tie-breaker by entity ref, matching backend behavior\n const aRef = stringifyEntityRef(a);\n const bRef = stringifyEntityRef(b);\n if (aRef < bRef) return -1;\n if (aRef > bRef) return 1;\n return 0;\n });\n}\n\nfunction applyFullTextFilter(\n entities: Entity[],\n fullTextFilter?: { term: string; fields?: string[] },\n): Entity[] {\n if (!fullTextFilter?.term?.trim()) {\n return entities;\n }\n const term = fullTextFilter.term.trim().toLowerCase();\n const fields = fullTextFilter.fields?.map(f => f.toLowerCase());\n\n return entities.filter(entity => {\n const rows = buildEntitySearch(entity);\n return rows.some(row => {\n if (fields?.length && !fields.includes(row.key.toLowerCase())) {\n return false;\n }\n if (row.value === null || row.value === undefined) return false;\n const value = String(row.value).toLowerCase();\n return value.includes(term);\n });\n });\n}\n\n/**\n * Implements a fake catalog client that stores entities in memory.\n * Supports filtering, ordering, pagination, full-text search, and field\n * projection for entity query methods. Location and validation methods\n * throw {@link @backstage/errors#NotImplementedError}.\n *\n * @public\n */\nexport class InMemoryCatalogClient implements CatalogApi {\n #entities: Entity[];\n\n constructor(options?: { entities?: Entity[] }) {\n this.#entities = options?.entities?.slice() ?? [];\n }\n\n async getEntities(\n request?: GetEntitiesRequest,\n ): Promise<GetEntitiesResponse> {\n const filter = createFilter(request?.filter);\n let items = this.#entities.filter(filter);\n items = applyOrdering(items, request?.order);\n\n let offset = request?.offset ?? 0;\n let limit = request?.limit;\n\n if (request?.after) {\n try {\n const cursor = parseCursor(request.after);\n if (cursor.offset !== undefined) offset = cursor.offset as number;\n if (cursor.limit !== undefined) limit = cursor.limit as number;\n } catch {\n throw new InputError('Invalid cursor');\n }\n }\n\n if (offset > 0) {\n items = items.slice(offset);\n }\n if (limit !== undefined) {\n items = items.slice(0, limit);\n }\n\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request.fields))\n : items,\n };\n }\n\n async getEntitiesByRefs(\n request: GetEntitiesByRefsRequest,\n ): Promise<GetEntitiesByRefsResponse> {\n const filter = createFilter(request.filter);\n const queryFilter = request.query\n ? filterPredicateToFilterFunction(request.query)\n : undefined;\n const refMap = this.#createEntityRefMap();\n const items = request.entityRefs\n .map(ref => refMap.get(ref))\n .map(e =>\n e && filter(e) && (!queryFilter || queryFilter(e)) ? e : undefined,\n );\n return {\n items: request.fields\n ? items.map(e => (e ? applyFieldsFilter(e, request.fields) : undefined))\n : items,\n };\n }\n\n async queryEntities(\n request?: QueryEntitiesRequest,\n ): Promise<QueryEntitiesResponse> {\n // Decode query parameters from cursor or from the request directly\n let filter: EntityFilterQuery | undefined;\n let query: FilterPredicate | undefined;\n let orderFields: EntityOrderQuery | undefined;\n let fullTextFilter: { term: string; fields?: string[] } | undefined;\n let offset: number;\n let limit: number | undefined;\n\n if (request && 'cursor' in request) {\n let c: Record<string, unknown>;\n try {\n c = parseCursor(request.cursor);\n } catch {\n throw new InputError('Invalid cursor');\n }\n filter = deserializeFilter(c.filter as any[]);\n query = c.query as FilterPredicate | undefined;\n orderFields = c.orderFields as EntityOrderQuery | undefined;\n fullTextFilter = c.fullTextFilter as typeof fullTextFilter;\n offset = c.offset as number;\n limit = request.limit;\n } else {\n filter = request?.filter;\n query = request?.query;\n orderFields = request?.orderFields;\n fullTextFilter = request?.fullTextFilter;\n offset = request?.offset ?? 0;\n limit = request?.limit;\n }\n\n // Apply filter\n let items = this.#entities.filter(createFilter(filter));\n\n // Apply predicate-based query filter\n if (query) {\n items = items.filter(filterPredicateToFilterFunction(query));\n }\n\n // Apply full-text filter, defaulting to the sort field or metadata.uid\n if (fullTextFilter) {\n const orderFieldsList = orderFields ? [orderFields].flat() : [];\n items = applyFullTextFilter(items, {\n ...fullTextFilter,\n fields: fullTextFilter.fields ?? [\n orderFieldsList[0]?.field ?? 'metadata.uid',\n ],\n });\n }\n\n items = applyOrdering(items, orderFields);\n\n const totalItems = items.length;\n\n // No pagination requested, return all items\n if (limit === undefined && offset === 0) {\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request?.fields))\n : items,\n pageInfo: {},\n totalItems,\n };\n }\n\n const effectiveLimit = limit ?? totalItems;\n const pageItems = items.slice(offset, offset + effectiveLimit);\n\n const cursorBase = {\n filter: serializeFilter(filter),\n query,\n orderFields,\n fullTextFilter,\n totalItems,\n };\n const pageInfo: QueryEntitiesResponse['pageInfo'] = {};\n if (offset + effectiveLimit < totalItems) {\n pageInfo.nextCursor = makeCursor({\n ...cursorBase,\n offset: offset + effectiveLimit,\n });\n }\n if (offset > 0) {\n pageInfo.prevCursor = makeCursor({\n ...cursorBase,\n offset: Math.max(0, offset - effectiveLimit),\n });\n }\n\n return {\n items: request?.fields\n ? pageItems.map(e => applyFieldsFilter(e, request?.fields))\n : pageItems,\n totalItems,\n pageInfo,\n };\n }\n\n async getEntityAncestors(\n request: GetEntityAncestorsRequest,\n ): Promise<GetEntityAncestorsResponse> {\n const entity = this.#createEntityRefMap().get(request.entityRef);\n if (!entity) {\n throw new NotFoundError(`Entity with ref ${request.entityRef} not found`);\n }\n return {\n items: [{ entity, parentEntityRefs: [] }],\n rootEntityRef: request.entityRef,\n };\n }\n\n async getEntityByRef(\n entityRef: string | CompoundEntityRef,\n ): Promise<Entity | undefined> {\n return this.#createEntityRefMap().get(\n stringifyEntityRef(parseEntityRef(entityRef)),\n );\n }\n\n async removeEntityByUid(uid: string): Promise<void> {\n const index = this.#entities.findIndex(e => e.metadata.uid === uid);\n if (index !== -1) {\n this.#entities.splice(index, 1);\n }\n }\n\n async refreshEntity(_entityRef: string): Promise<void> {}\n\n async getEntityFacets(\n request: GetEntityFacetsRequest,\n ): Promise<GetEntityFacetsResponse> {\n const filter = createFilter(request.filter);\n let filteredEntities = this.#entities.filter(filter);\n if (request.query) {\n filteredEntities = filteredEntities.filter(\n filterPredicateToFilterFunction(request.query),\n );\n }\n const facets = Object.fromEntries(\n request.facets.map(facet => {\n const facetValues = new Map<string, number>();\n for (const entity of filteredEntities) {\n const rows = buildEntitySearch(entity);\n // Use a Set to count each distinct value once per entity,\n // matching the backend's count(DISTINCT entity_id) behavior\n const uniqueValues = new Set(\n rows\n .filter(row => row.key.toLowerCase() === facet.toLowerCase())\n .map(row => row.value)\n .filter(v => v !== null && v !== undefined)\n .map(v => String(v)),\n );\n for (const value of uniqueValues) {\n facetValues.set(value, (facetValues.get(value) ?? 0) + 1);\n }\n }\n const counts = Array.from(facetValues.entries()).map(\n ([value, count]) => ({ value, count }),\n );\n return [facet, counts];\n }),\n );\n return {\n facets,\n };\n }\n\n async getLocations(_request?: {}): Promise<GetLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async queryLocations(\n _request?: QueryLocationsRequest,\n ): Promise<QueryLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamLocations(\n _request?: QueryLocationsInitialRequest,\n ): AsyncIterable<Location[]> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationById(_id: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByRef(_locationRef: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async addLocation(\n _location: AddLocationRequest,\n ): Promise<AddLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async removeLocationById(_id: string): Promise<void> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async updateLocation(\n _id: string,\n _location: { type?: string; target: string },\n ): Promise<Location> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByEntity(\n _entityRef: string | CompoundEntityRef,\n ): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async validateEntity(\n _entity: Entity,\n _locationRef: string,\n ): Promise<ValidateEntityResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async analyzeLocation(\n _location: AnalyzeLocationRequest,\n ): Promise<AnalyzeLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamEntities(\n request?: StreamEntitiesRequest,\n ): AsyncIterable<Entity[]> {\n let cursor: string | undefined = undefined;\n const limit = request?.pageSize ?? DEFAULT_STREAM_ENTITIES_LIMIT;\n do {\n const res = await this.queryEntities(\n cursor ? { ...request, limit, cursor } : { ...request, limit },\n );\n\n yield res.items;\n\n cursor = res.pageInfo.nextCursor;\n } while (cursor);\n }\n\n #createEntityRefMap() {\n return new Map(this.#entities.map(e => [stringifyEntityRef(e), e]));\n }\n}\n"],"names":["CATALOG_FILTER_EXISTS","traverse","DEFAULT_NAMESPACE","lodash","stringifyEntityRef","InputError","filterPredicateToFilterFunction","NotFoundError","parseEntityRef","NotImplementedError","DEFAULT_STREAM_ENTITIES_LIMIT"],"mappings":"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"}
@@ -55,19 +55,19 @@ function buildEntitySearch(entity) {
55
55
  if (entity.metadata?.name) {
56
56
  rows.push({
57
57
  key: "metadata.name",
58
- value: entity.metadata.name.toLocaleLowerCase("en-US")
58
+ value: entity.metadata.name.toLowerCase()
59
59
  });
60
60
  }
61
61
  if (entity.metadata?.namespace) {
62
62
  rows.push({
63
63
  key: "metadata.namespace",
64
- value: entity.metadata.namespace.toLocaleLowerCase("en-US")
64
+ value: entity.metadata.namespace.toLowerCase()
65
65
  });
66
66
  }
67
67
  if (entity.metadata?.uid) {
68
68
  rows.push({
69
69
  key: "metadata.uid",
70
- value: entity.metadata.uid.toLocaleLowerCase("en-US")
70
+ value: entity.metadata.uid.toLowerCase()
71
71
  });
72
72
  }
73
73
  if (!entity.metadata.namespace) {
@@ -75,8 +75,8 @@ function buildEntitySearch(entity) {
75
75
  }
76
76
  for (const relation of entity.relations ?? []) {
77
77
  rows.push({
78
- key: `relations.${relation.type.toLocaleLowerCase("en-US")}`,
79
- value: relation.targetRef.toLocaleLowerCase("en-US")
78
+ key: `relations.${relation.type.toLowerCase()}`,
79
+ value: relation.targetRef.toLowerCase()
80
80
  });
81
81
  }
82
82
  return rows;
@@ -90,7 +90,7 @@ function createFilter(filterOrFilters) {
90
90
  const rows = buildEntitySearch(entity);
91
91
  return filters.some((filter) => {
92
92
  for (const [key, expectedValue] of Object.entries(filter)) {
93
- const searchValues = rows.filter((row) => row.key === key.toLocaleLowerCase("en-US")).map((row) => row.value?.toString().toLocaleLowerCase("en-US"));
93
+ const searchValues = rows.filter((row) => row.key === key.toLowerCase()).map((row) => row.value?.toString().toLowerCase());
94
94
  if (searchValues.length === 0) {
95
95
  return false;
96
96
  }
@@ -99,12 +99,10 @@ function createFilter(filterOrFilters) {
99
99
  }
100
100
  if (Array.isArray(expectedValue)) {
101
101
  return expectedValue.some(
102
- (value) => searchValues?.includes(String(value).toLocaleLowerCase("en-US"))
102
+ (value) => searchValues?.includes(String(value).toLowerCase())
103
103
  );
104
104
  }
105
- if (!searchValues?.includes(
106
- String(expectedValue).toLocaleLowerCase("en-US")
107
- )) {
105
+ if (!searchValues?.includes(String(expectedValue).toLowerCase())) {
108
106
  return false;
109
107
  }
110
108
  }
@@ -155,11 +153,11 @@ function applyOrdering(entities, order) {
155
153
  const aRows = searchMap.get(a);
156
154
  const bRows = searchMap.get(b);
157
155
  for (const { field, order: dir } of orders) {
158
- const key = field.toLocaleLowerCase("en-US");
159
- const aRow = aRows.find((r) => r.key.toLocaleLowerCase("en-US") === key);
160
- const bRow = bRows.find((r) => r.key.toLocaleLowerCase("en-US") === key);
161
- const aValue = aRow?.value !== null && aRow?.value !== void 0 ? String(aRow.value).toLocaleLowerCase("en-US") : null;
162
- const bValue = bRow?.value !== null && bRow?.value !== void 0 ? String(bRow.value).toLocaleLowerCase("en-US") : null;
156
+ const key = field.toLowerCase();
157
+ const aRow = aRows.find((r) => r.key.toLowerCase() === key);
158
+ const bRow = bRows.find((r) => r.key.toLowerCase() === key);
159
+ const aValue = aRow?.value !== null && aRow?.value !== void 0 ? String(aRow.value).toLowerCase() : null;
160
+ const bValue = bRow?.value !== null && bRow?.value !== void 0 ? String(bRow.value).toLowerCase() : null;
163
161
  if (aValue === null && bValue === null) continue;
164
162
  if (aValue === null) return 1;
165
163
  if (bValue === null) return -1;
@@ -177,16 +175,16 @@ function applyFullTextFilter(entities, fullTextFilter) {
177
175
  if (!fullTextFilter?.term?.trim()) {
178
176
  return entities;
179
177
  }
180
- const term = fullTextFilter.term.trim().toLocaleLowerCase("en-US");
181
- const fields = fullTextFilter.fields?.map((f) => f.toLocaleLowerCase("en-US"));
178
+ const term = fullTextFilter.term.trim().toLowerCase();
179
+ const fields = fullTextFilter.fields?.map((f) => f.toLowerCase());
182
180
  return entities.filter((entity) => {
183
181
  const rows = buildEntitySearch(entity);
184
182
  return rows.some((row) => {
185
- if (fields?.length && !fields.includes(row.key.toLocaleLowerCase("en-US"))) {
183
+ if (fields?.length && !fields.includes(row.key.toLowerCase())) {
186
184
  return false;
187
185
  }
188
186
  if (row.value === null || row.value === void 0) return false;
189
- const value = String(row.value).toLocaleLowerCase("en-US");
187
+ const value = String(row.value).toLowerCase();
190
188
  return value.includes(term);
191
189
  });
192
190
  });
@@ -347,9 +345,7 @@ class InMemoryCatalogClient {
347
345
  for (const entity of filteredEntities) {
348
346
  const rows = buildEntitySearch(entity);
349
347
  const uniqueValues = new Set(
350
- rows.filter(
351
- (row) => row.key.toLocaleLowerCase("en-US") === facet.toLocaleLowerCase("en-US")
352
- ).map((row) => row.value).filter((v) => v !== null && v !== void 0).map((v) => String(v))
348
+ rows.filter((row) => row.key.toLowerCase() === facet.toLowerCase()).map((row) => row.value).filter((v) => v !== null && v !== void 0).map((v) => String(v))
353
349
  );
354
350
  for (const value of uniqueValues) {
355
351
  facetValues.set(value, (facetValues.get(value) ?? 0) + 1);
@@ -1 +1 @@
1
- {"version":3,"file":"InMemoryCatalogClient.esm.js","sources":["../../src/testUtils/InMemoryCatalogClient.ts"],"sourcesContent":["/*\n * Copyright 2024 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 {\n AddLocationRequest,\n AddLocationResponse,\n CATALOG_FILTER_EXISTS,\n CatalogApi,\n EntityFilterQuery,\n EntityOrderQuery,\n GetEntitiesByRefsRequest,\n GetEntitiesByRefsResponse,\n GetEntitiesRequest,\n GetEntitiesResponse,\n GetEntityAncestorsRequest,\n GetEntityAncestorsResponse,\n GetEntityFacetsRequest,\n GetEntityFacetsResponse,\n GetLocationsResponse,\n Location,\n QueryEntitiesRequest,\n QueryEntitiesResponse,\n QueryLocationsInitialRequest,\n QueryLocationsRequest,\n QueryLocationsResponse,\n StreamEntitiesRequest,\n ValidateEntityResponse,\n} from '@backstage/catalog-client';\nimport {\n CompoundEntityRef,\n DEFAULT_NAMESPACE,\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n InputError,\n NotFoundError,\n NotImplementedError,\n} from '@backstage/errors';\nimport {\n FilterPredicate,\n filterPredicateToFilterFunction,\n} from '@backstage/filter-predicates';\nimport lodash from 'lodash';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { traverse } from '../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch';\nimport type {\n AnalyzeLocationRequest,\n AnalyzeLocationResponse,\n} from '@backstage/plugin-catalog-common';\nimport { DEFAULT_STREAM_ENTITIES_LIMIT } from '../constants.ts';\n\nfunction makeCursor(data: Record<string, unknown>): string {\n return btoa(JSON.stringify(data));\n}\n\nfunction parseCursor(cursor: string): Record<string, unknown> {\n return JSON.parse(atob(cursor));\n}\n\n// CATALOG_FILTER_EXISTS is a Symbol that doesn't survive JSON serialization,\n// so we swap it with a sentinel string when encoding/decoding cursors.\nconst FILTER_EXISTS_SENTINEL = '\\0CATALOG_FILTER_EXISTS';\n\nfunction serializeFilterValue(v: unknown): unknown {\n if (v === CATALOG_FILTER_EXISTS) return FILTER_EXISTS_SENTINEL;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === CATALOG_FILTER_EXISTS ? FILTER_EXISTS_SENTINEL : x,\n );\n }\n return v;\n}\n\nfunction deserializeFilterValue(v: unknown): unknown {\n if (v === FILTER_EXISTS_SENTINEL) return CATALOG_FILTER_EXISTS;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === FILTER_EXISTS_SENTINEL ? CATALOG_FILTER_EXISTS : x,\n );\n }\n return v;\n}\n\nfunction serializeFilter(filter?: EntityFilterQuery): any[] | undefined {\n if (!filter) return undefined;\n return [filter]\n .flat()\n .map(f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]) => [k, serializeFilterValue(v)]),\n ),\n );\n}\n\nfunction deserializeFilter(filter?: any[]): EntityFilterQuery | undefined {\n if (!filter) return undefined;\n return filter.map(\n f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]: [string, any]) => [\n k,\n deserializeFilterValue(v),\n ]),\n ) as Record<string, string | symbol | (string | symbol)[]>,\n );\n}\n\nfunction buildEntitySearch(entity: Entity) {\n const rows = traverse(entity);\n\n if (entity.metadata?.name) {\n rows.push({\n key: 'metadata.name',\n value: entity.metadata.name.toLocaleLowerCase('en-US'),\n });\n }\n if (entity.metadata?.namespace) {\n rows.push({\n key: 'metadata.namespace',\n value: entity.metadata.namespace.toLocaleLowerCase('en-US'),\n });\n }\n if (entity.metadata?.uid) {\n rows.push({\n key: 'metadata.uid',\n value: entity.metadata.uid.toLocaleLowerCase('en-US'),\n });\n }\n\n if (!entity.metadata.namespace) {\n rows.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n rows.push({\n key: `relations.${relation.type.toLocaleLowerCase('en-US')}`,\n value: relation.targetRef.toLocaleLowerCase('en-US'),\n });\n }\n\n return rows;\n}\n\nfunction createFilter(\n filterOrFilters?: EntityFilterQuery,\n): (entity: Entity) => boolean {\n if (!filterOrFilters) {\n return () => true;\n }\n\n const filters = [filterOrFilters].flat();\n\n return entity => {\n const rows = buildEntitySearch(entity);\n\n return filters.some(filter => {\n for (const [key, expectedValue] of Object.entries(filter)) {\n const searchValues = rows\n .filter(row => row.key === key.toLocaleLowerCase('en-US'))\n .map(row => row.value?.toString().toLocaleLowerCase('en-US'));\n\n if (searchValues.length === 0) {\n return false;\n }\n if (expectedValue === CATALOG_FILTER_EXISTS) {\n continue;\n }\n if (Array.isArray(expectedValue)) {\n return expectedValue.some(value =>\n searchValues?.includes(String(value).toLocaleLowerCase('en-US')),\n );\n }\n if (\n !searchValues?.includes(\n String(expectedValue).toLocaleLowerCase('en-US'),\n )\n ) {\n return false;\n }\n }\n return true;\n });\n };\n}\n\n// Resolves a dot-separated field path against an entity, handling keys that\n// themselves contain dots (e.g. annotation keys like \"backstage.io/orphan\").\n// This matches the backend's parseEntityTransformParams implementation.\nfunction getPathArrayAndValue(\n input: Entity,\n field: string,\n): [string[], unknown] {\n return field.split('.').reduce(\n ([pathArray, inputSubset], pathPart, index, fieldParts) => {\n if (lodash.hasIn(inputSubset, pathPart)) {\n return [pathArray.concat(pathPart), (inputSubset as any)[pathPart]];\n } else if (fieldParts[index + 1] !== undefined) {\n fieldParts[index + 1] = `${pathPart}.${fieldParts[index + 1]}`;\n return [pathArray, inputSubset];\n }\n return [pathArray, undefined];\n },\n [[] as string[], input as unknown],\n );\n}\n\nfunction applyFieldsFilter(entity: Entity, fields?: string[]): Entity {\n if (!fields?.length) {\n return entity;\n }\n const output: Record<string, any> = {};\n for (const field of fields) {\n const [pathArray, value] = getPathArrayAndValue(entity, field);\n if (value !== undefined) {\n lodash.set(output, pathArray, value);\n }\n }\n return output as Entity;\n}\n\nfunction applyOrdering(entities: Entity[], order?: EntityOrderQuery): Entity[] {\n if (!order) {\n return entities;\n }\n const orders = [order].flat();\n if (orders.length === 0) {\n return entities;\n }\n\n const searchMap = new Map<Entity, Array<{ key: string; value: unknown }>>();\n for (const entity of entities) {\n searchMap.set(entity, buildEntitySearch(entity));\n }\n\n return [...entities].sort((a, b) => {\n const aRows = searchMap.get(a)!;\n const bRows = searchMap.get(b)!;\n\n for (const { field, order: dir } of orders) {\n const key = field.toLocaleLowerCase('en-US');\n const aRow = aRows.find(r => r.key.toLocaleLowerCase('en-US') === key);\n const bRow = bRows.find(r => r.key.toLocaleLowerCase('en-US') === key);\n const aValue =\n aRow?.value !== null && aRow?.value !== undefined\n ? String(aRow.value).toLocaleLowerCase('en-US')\n : null;\n const bValue =\n bRow?.value !== null && bRow?.value !== undefined\n ? String(bRow.value).toLocaleLowerCase('en-US')\n : null;\n\n if (aValue === null && bValue === null) continue;\n if (aValue === null) return 1;\n if (bValue === null) return -1;\n\n if (aValue < bValue) return dir === 'asc' ? -1 : 1;\n if (aValue > bValue) return dir === 'asc' ? 1 : -1;\n }\n\n // Stable tie-breaker by entity ref, matching backend behavior\n const aRef = stringifyEntityRef(a);\n const bRef = stringifyEntityRef(b);\n if (aRef < bRef) return -1;\n if (aRef > bRef) return 1;\n return 0;\n });\n}\n\nfunction applyFullTextFilter(\n entities: Entity[],\n fullTextFilter?: { term: string; fields?: string[] },\n): Entity[] {\n if (!fullTextFilter?.term?.trim()) {\n return entities;\n }\n const term = fullTextFilter.term.trim().toLocaleLowerCase('en-US');\n const fields = fullTextFilter.fields?.map(f => f.toLocaleLowerCase('en-US'));\n\n return entities.filter(entity => {\n const rows = buildEntitySearch(entity);\n return rows.some(row => {\n if (\n fields?.length &&\n !fields.includes(row.key.toLocaleLowerCase('en-US'))\n ) {\n return false;\n }\n if (row.value === null || row.value === undefined) return false;\n const value = String(row.value).toLocaleLowerCase('en-US');\n return value.includes(term);\n });\n });\n}\n\n/**\n * Implements a fake catalog client that stores entities in memory.\n * Supports filtering, ordering, pagination, full-text search, and field\n * projection for entity query methods. Location and validation methods\n * throw {@link @backstage/errors#NotImplementedError}.\n *\n * @public\n */\nexport class InMemoryCatalogClient implements CatalogApi {\n #entities: Entity[];\n\n constructor(options?: { entities?: Entity[] }) {\n this.#entities = options?.entities?.slice() ?? [];\n }\n\n async getEntities(\n request?: GetEntitiesRequest,\n ): Promise<GetEntitiesResponse> {\n const filter = createFilter(request?.filter);\n let items = this.#entities.filter(filter);\n items = applyOrdering(items, request?.order);\n\n let offset = request?.offset ?? 0;\n let limit = request?.limit;\n\n if (request?.after) {\n try {\n const cursor = parseCursor(request.after);\n if (cursor.offset !== undefined) offset = cursor.offset as number;\n if (cursor.limit !== undefined) limit = cursor.limit as number;\n } catch {\n throw new InputError('Invalid cursor');\n }\n }\n\n if (offset > 0) {\n items = items.slice(offset);\n }\n if (limit !== undefined) {\n items = items.slice(0, limit);\n }\n\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request.fields))\n : items,\n };\n }\n\n async getEntitiesByRefs(\n request: GetEntitiesByRefsRequest,\n ): Promise<GetEntitiesByRefsResponse> {\n const filter = createFilter(request.filter);\n const queryFilter = request.query\n ? filterPredicateToFilterFunction(request.query)\n : undefined;\n const refMap = this.#createEntityRefMap();\n const items = request.entityRefs\n .map(ref => refMap.get(ref))\n .map(e =>\n e && filter(e) && (!queryFilter || queryFilter(e)) ? e : undefined,\n );\n return {\n items: request.fields\n ? items.map(e => (e ? applyFieldsFilter(e, request.fields) : undefined))\n : items,\n };\n }\n\n async queryEntities(\n request?: QueryEntitiesRequest,\n ): Promise<QueryEntitiesResponse> {\n // Decode query parameters from cursor or from the request directly\n let filter: EntityFilterQuery | undefined;\n let query: FilterPredicate | undefined;\n let orderFields: EntityOrderQuery | undefined;\n let fullTextFilter: { term: string; fields?: string[] } | undefined;\n let offset: number;\n let limit: number | undefined;\n\n if (request && 'cursor' in request) {\n let c: Record<string, unknown>;\n try {\n c = parseCursor(request.cursor);\n } catch {\n throw new InputError('Invalid cursor');\n }\n filter = deserializeFilter(c.filter as any[]);\n query = c.query as FilterPredicate | undefined;\n orderFields = c.orderFields as EntityOrderQuery | undefined;\n fullTextFilter = c.fullTextFilter as typeof fullTextFilter;\n offset = c.offset as number;\n limit = request.limit;\n } else {\n filter = request?.filter;\n query = request?.query;\n orderFields = request?.orderFields;\n fullTextFilter = request?.fullTextFilter;\n offset = request?.offset ?? 0;\n limit = request?.limit;\n }\n\n // Apply filter\n let items = this.#entities.filter(createFilter(filter));\n\n // Apply predicate-based query filter\n if (query) {\n items = items.filter(filterPredicateToFilterFunction(query));\n }\n\n // Apply full-text filter, defaulting to the sort field or metadata.uid\n if (fullTextFilter) {\n const orderFieldsList = orderFields ? [orderFields].flat() : [];\n items = applyFullTextFilter(items, {\n ...fullTextFilter,\n fields: fullTextFilter.fields ?? [\n orderFieldsList[0]?.field ?? 'metadata.uid',\n ],\n });\n }\n\n items = applyOrdering(items, orderFields);\n\n const totalItems = items.length;\n\n // No pagination requested, return all items\n if (limit === undefined && offset === 0) {\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request?.fields))\n : items,\n pageInfo: {},\n totalItems,\n };\n }\n\n const effectiveLimit = limit ?? totalItems;\n const pageItems = items.slice(offset, offset + effectiveLimit);\n\n const cursorBase = {\n filter: serializeFilter(filter),\n query,\n orderFields,\n fullTextFilter,\n totalItems,\n };\n const pageInfo: QueryEntitiesResponse['pageInfo'] = {};\n if (offset + effectiveLimit < totalItems) {\n pageInfo.nextCursor = makeCursor({\n ...cursorBase,\n offset: offset + effectiveLimit,\n });\n }\n if (offset > 0) {\n pageInfo.prevCursor = makeCursor({\n ...cursorBase,\n offset: Math.max(0, offset - effectiveLimit),\n });\n }\n\n return {\n items: request?.fields\n ? pageItems.map(e => applyFieldsFilter(e, request?.fields))\n : pageItems,\n totalItems,\n pageInfo,\n };\n }\n\n async getEntityAncestors(\n request: GetEntityAncestorsRequest,\n ): Promise<GetEntityAncestorsResponse> {\n const entity = this.#createEntityRefMap().get(request.entityRef);\n if (!entity) {\n throw new NotFoundError(`Entity with ref ${request.entityRef} not found`);\n }\n return {\n items: [{ entity, parentEntityRefs: [] }],\n rootEntityRef: request.entityRef,\n };\n }\n\n async getEntityByRef(\n entityRef: string | CompoundEntityRef,\n ): Promise<Entity | undefined> {\n return this.#createEntityRefMap().get(\n stringifyEntityRef(parseEntityRef(entityRef)),\n );\n }\n\n async removeEntityByUid(uid: string): Promise<void> {\n const index = this.#entities.findIndex(e => e.metadata.uid === uid);\n if (index !== -1) {\n this.#entities.splice(index, 1);\n }\n }\n\n async refreshEntity(_entityRef: string): Promise<void> {}\n\n async getEntityFacets(\n request: GetEntityFacetsRequest,\n ): Promise<GetEntityFacetsResponse> {\n const filter = createFilter(request.filter);\n let filteredEntities = this.#entities.filter(filter);\n if (request.query) {\n filteredEntities = filteredEntities.filter(\n filterPredicateToFilterFunction(request.query),\n );\n }\n const facets = Object.fromEntries(\n request.facets.map(facet => {\n const facetValues = new Map<string, number>();\n for (const entity of filteredEntities) {\n const rows = buildEntitySearch(entity);\n // Use a Set to count each distinct value once per entity,\n // matching the backend's count(DISTINCT entity_id) behavior\n const uniqueValues = new Set(\n rows\n .filter(\n row =>\n row.key.toLocaleLowerCase('en-US') ===\n facet.toLocaleLowerCase('en-US'),\n )\n .map(row => row.value)\n .filter(v => v !== null && v !== undefined)\n .map(v => String(v)),\n );\n for (const value of uniqueValues) {\n facetValues.set(value, (facetValues.get(value) ?? 0) + 1);\n }\n }\n const counts = Array.from(facetValues.entries()).map(\n ([value, count]) => ({ value, count }),\n );\n return [facet, counts];\n }),\n );\n return {\n facets,\n };\n }\n\n async getLocations(_request?: {}): Promise<GetLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async queryLocations(\n _request?: QueryLocationsRequest,\n ): Promise<QueryLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamLocations(\n _request?: QueryLocationsInitialRequest,\n ): AsyncIterable<Location[]> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationById(_id: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByRef(_locationRef: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async addLocation(\n _location: AddLocationRequest,\n ): Promise<AddLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async removeLocationById(_id: string): Promise<void> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async updateLocation(\n _id: string,\n _location: { type?: string; target: string },\n ): Promise<Location> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByEntity(\n _entityRef: string | CompoundEntityRef,\n ): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async validateEntity(\n _entity: Entity,\n _locationRef: string,\n ): Promise<ValidateEntityResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async analyzeLocation(\n _location: AnalyzeLocationRequest,\n ): Promise<AnalyzeLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamEntities(\n request?: StreamEntitiesRequest,\n ): AsyncIterable<Entity[]> {\n let cursor: string | undefined = undefined;\n const limit = request?.pageSize ?? DEFAULT_STREAM_ENTITIES_LIMIT;\n do {\n const res = await this.queryEntities(\n cursor ? { ...request, limit, cursor } : { ...request, limit },\n );\n\n yield res.items;\n\n cursor = res.pageInfo.nextCursor;\n } while (cursor);\n }\n\n #createEntityRefMap() {\n return new Map(this.#entities.map(e => [stringifyEntityRef(e), e]));\n }\n}\n"],"names":[],"mappings":";;;;;;;;AAkEA,SAAS,WAAW,IAAA,EAAuC;AACzD,EAAA,OAAO,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA;AAClC;AAEA,SAAS,YAAY,MAAA,EAAyC;AAC5D,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,CAAA;AAChC;AAIA,MAAM,sBAAA,GAAyB,yBAAA;AAE/B,SAAS,qBAAqB,CAAA,EAAqB;AACjD,EAAA,IAAI,CAAA,KAAM,uBAAuB,OAAO,sBAAA;AACxC,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AACpB,IAAA,OAAO,CAAA,CAAE,GAAA;AAAA,MAAI,CAAA,CAAA,KACX,CAAA,KAAM,qBAAA,GAAwB,sBAAA,GAAyB;AAAA,KACzD;AAAA,EACF;AACA,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,uBAAuB,CAAA,EAAqB;AACnD,EAAA,IAAI,CAAA,KAAM,wBAAwB,OAAO,qBAAA;AACzC,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG;AACpB,IAAA,OAAO,CAAA,CAAE,GAAA;AAAA,MAAI,CAAA,CAAA,KACX,CAAA,KAAM,sBAAA,GAAyB,qBAAA,GAAwB;AAAA,KACzD;AAAA,EACF;AACA,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,gBAAgB,MAAA,EAA+C;AACtE,EAAA,IAAI,CAAC,QAAQ,OAAO,MAAA;AACpB,EAAA,OAAO,CAAC,MAAM,CAAA,CACX,IAAA,EAAK,CACL,GAAA;AAAA,IAAI,OACH,MAAA,CAAO,WAAA;AAAA,MACL,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,CAAE,IAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA,EAAG,oBAAA,CAAqB,CAAC,CAAC,CAAC;AAAA;AAChE,GACF;AACJ;AAEA,SAAS,kBAAkB,MAAA,EAA+C;AACxE,EAAA,IAAI,CAAC,QAAQ,OAAO,MAAA;AACpB,EAAA,OAAO,MAAA,CAAO,GAAA;AAAA,IACZ,OACE,MAAA,CAAO,WAAA;AAAA,MACL,MAAA,CAAO,QAAQ,CAAC,CAAA,CAAE,IAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAqB;AAAA,QAC/C,CAAA;AAAA,QACA,uBAAuB,CAAC;AAAA,OACzB;AAAA;AACH,GACJ;AACF;AAEA,SAAS,kBAAkB,MAAA,EAAgB;AACzC,EAAA,MAAM,IAAA,GAAO,SAAS,MAAM,CAAA;AAE5B,EAAA,IAAI,MAAA,CAAO,UAAU,IAAA,EAAM;AACzB,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,GAAA,EAAK,eAAA;AAAA,MACL,KAAA,EAAO,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,kBAAkB,OAAO;AAAA,KACtD,CAAA;AAAA,EACH;AACA,EAAA,IAAI,MAAA,CAAO,UAAU,SAAA,EAAW;AAC9B,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,GAAA,EAAK,oBAAA;AAAA,MACL,KAAA,EAAO,MAAA,CAAO,QAAA,CAAS,SAAA,CAAU,kBAAkB,OAAO;AAAA,KAC3D,CAAA;AAAA,EACH;AACA,EAAA,IAAI,MAAA,CAAO,UAAU,GAAA,EAAK;AACxB,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,GAAA,EAAK,cAAA;AAAA,MACL,KAAA,EAAO,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,kBAAkB,OAAO;AAAA,KACrD,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,SAAA,EAAW;AAC9B,IAAA,IAAA,CAAK,KAAK,EAAE,GAAA,EAAK,oBAAA,EAAsB,KAAA,EAAO,mBAAmB,CAAA;AAAA,EACnE;AAGA,EAAA,KAAA,MAAW,QAAA,IAAY,MAAA,CAAO,SAAA,IAAa,EAAC,EAAG;AAC7C,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,KAAK,CAAA,UAAA,EAAa,QAAA,CAAS,IAAA,CAAK,iBAAA,CAAkB,OAAO,CAAC,CAAA,CAAA;AAAA,MAC1D,KAAA,EAAO,QAAA,CAAS,SAAA,CAAU,iBAAA,CAAkB,OAAO;AAAA,KACpD,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,aACP,eAAA,EAC6B;AAC7B,EAAA,IAAI,CAAC,eAAA,EAAiB;AACpB,IAAA,OAAO,MAAM,IAAA;AAAA,EACf;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,eAAe,CAAA,CAAE,IAAA,EAAK;AAEvC,EAAA,OAAO,CAAA,MAAA,KAAU;AACf,IAAA,MAAM,IAAA,GAAO,kBAAkB,MAAM,CAAA;AAErC,IAAA,OAAO,OAAA,CAAQ,KAAK,CAAA,MAAA,KAAU;AAC5B,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,aAAa,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACzD,QAAA,MAAM,eAAe,IAAA,CAClB,MAAA,CAAO,SAAO,GAAA,CAAI,GAAA,KAAQ,IAAI,iBAAA,CAAkB,OAAO,CAAC,CAAA,CACxD,GAAA,CAAI,SAAO,GAAA,CAAI,KAAA,EAAO,UAAS,CAAE,iBAAA,CAAkB,OAAO,CAAC,CAAA;AAE9D,QAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,UAAA,OAAO,KAAA;AAAA,QACT;AACA,QAAA,IAAI,kBAAkB,qBAAA,EAAuB;AAC3C,UAAA;AAAA,QACF;AACA,QAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,aAAa,CAAA,EAAG;AAChC,UAAA,OAAO,aAAA,CAAc,IAAA;AAAA,YAAK,CAAA,KAAA,KACxB,cAAc,QAAA,CAAS,MAAA,CAAO,KAAK,CAAA,CAAE,iBAAA,CAAkB,OAAO,CAAC;AAAA,WACjE;AAAA,QACF;AACA,QAAA,IACE,CAAC,YAAA,EAAc,QAAA;AAAA,UACb,MAAA,CAAO,aAAa,CAAA,CAAE,iBAAA,CAAkB,OAAO;AAAA,SACjD,EACA;AACA,UAAA,OAAO,KAAA;AAAA,QACT;AAAA,MACF;AACA,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAAA,EACH,CAAA;AACF;AAKA,SAAS,oBAAA,CACP,OACA,KAAA,EACqB;AACrB,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA,CAAE,MAAA;AAAA,IACtB,CAAC,CAAC,SAAA,EAAW,WAAW,CAAA,EAAG,QAAA,EAAU,OAAO,UAAA,KAAe;AACzD,MAAA,IAAI,MAAA,CAAO,KAAA,CAAM,WAAA,EAAa,QAAQ,CAAA,EAAG;AACvC,QAAA,OAAO,CAAC,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA,EAAI,WAAA,CAAoB,QAAQ,CAAC,CAAA;AAAA,MACpE,CAAA,MAAA,IAAW,UAAA,CAAW,KAAA,GAAQ,CAAC,MAAM,MAAA,EAAW;AAC9C,QAAA,UAAA,CAAW,KAAA,GAAQ,CAAC,CAAA,GAAI,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,UAAA,CAAW,KAAA,GAAQ,CAAC,CAAC,CAAA,CAAA;AAC5D,QAAA,OAAO,CAAC,WAAW,WAAW,CAAA;AAAA,MAChC;AACA,MAAA,OAAO,CAAC,WAAW,MAAS,CAAA;AAAA,IAC9B,CAAA;AAAA,IACA,CAAC,EAAC,EAAe,KAAgB;AAAA,GACnC;AACF;AAEA,SAAS,iBAAA,CAAkB,QAAgB,MAAA,EAA2B;AACpE,EAAA,IAAI,CAAC,QAAQ,MAAA,EAAQ;AACnB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,SAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,CAAC,SAAA,EAAW,KAAK,CAAA,GAAI,oBAAA,CAAqB,QAAQ,KAAK,CAAA;AAC7D,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,MAAA,CAAO,GAAA,CAAI,MAAA,EAAQ,SAAA,EAAW,KAAK,CAAA;AAAA,IACrC;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,aAAA,CAAc,UAAoB,KAAA,EAAoC;AAC7E,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAA,GAAS,CAAC,KAAK,CAAA,CAAE,IAAA,EAAK;AAC5B,EAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAoD;AAC1E,EAAA,KAAA,MAAW,UAAU,QAAA,EAAU;AAC7B,IAAA,SAAA,CAAU,GAAA,CAAI,MAAA,EAAQ,iBAAA,CAAkB,MAAM,CAAC,CAAA;AAAA,EACjD;AAEA,EAAA,OAAO,CAAC,GAAG,QAAQ,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAClC,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA;AAC7B,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA;AAE7B,IAAA,KAAA,MAAW,EAAE,KAAA,EAAO,KAAA,EAAO,GAAA,MAAS,MAAA,EAAQ;AAC1C,MAAA,MAAM,GAAA,GAAM,KAAA,CAAM,iBAAA,CAAkB,OAAO,CAAA;AAC3C,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,GAAA,CAAI,iBAAA,CAAkB,OAAO,CAAA,KAAM,GAAG,CAAA;AACrE,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,GAAA,CAAI,iBAAA,CAAkB,OAAO,CAAA,KAAM,GAAG,CAAA;AACrE,MAAA,MAAM,MAAA,GACJ,IAAA,EAAM,KAAA,KAAU,IAAA,IAAQ,IAAA,EAAM,KAAA,KAAU,MAAA,GACpC,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,iBAAA,CAAkB,OAAO,CAAA,GAC5C,IAAA;AACN,MAAA,MAAM,MAAA,GACJ,IAAA,EAAM,KAAA,KAAU,IAAA,IAAQ,IAAA,EAAM,KAAA,KAAU,MAAA,GACpC,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,iBAAA,CAAkB,OAAO,CAAA,GAC5C,IAAA;AAEN,MAAA,IAAI,MAAA,KAAW,IAAA,IAAQ,MAAA,KAAW,IAAA,EAAM;AACxC,MAAA,IAAI,MAAA,KAAW,MAAM,OAAO,CAAA;AAC5B,MAAA,IAAI,MAAA,KAAW,MAAM,OAAO,EAAA;AAE5B,MAAA,IAAI,MAAA,GAAS,MAAA,EAAQ,OAAO,GAAA,KAAQ,QAAQ,EAAA,GAAK,CAAA;AACjD,MAAA,IAAI,MAAA,GAAS,MAAA,EAAQ,OAAO,GAAA,KAAQ,QAAQ,CAAA,GAAI,EAAA;AAAA,IAClD;AAGA,IAAA,MAAM,IAAA,GAAO,mBAAmB,CAAC,CAAA;AACjC,IAAA,MAAM,IAAA,GAAO,mBAAmB,CAAC,CAAA;AACjC,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,EAAA;AACxB,IAAA,IAAI,IAAA,GAAO,MAAM,OAAO,CAAA;AACxB,IAAA,OAAO,CAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,SAAS,mBAAA,CACP,UACA,cAAA,EACU;AACV,EAAA,IAAI,CAAC,cAAA,EAAgB,IAAA,EAAM,IAAA,EAAK,EAAG;AACjC,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,MAAM,OAAO,cAAA,CAAe,IAAA,CAAK,IAAA,EAAK,CAAE,kBAAkB,OAAO,CAAA;AACjE,EAAA,MAAM,MAAA,GAAS,eAAe,MAAA,EAAQ,GAAA,CAAI,OAAK,CAAA,CAAE,iBAAA,CAAkB,OAAO,CAAC,CAAA;AAE3E,EAAA,OAAO,QAAA,CAAS,OAAO,CAAA,MAAA,KAAU;AAC/B,IAAA,MAAM,IAAA,GAAO,kBAAkB,MAAM,CAAA;AACrC,IAAA,OAAO,IAAA,CAAK,KAAK,CAAA,GAAA,KAAO;AACtB,MAAA,IACE,MAAA,EAAQ,MAAA,IACR,CAAC,MAAA,CAAO,QAAA,CAAS,IAAI,GAAA,CAAI,iBAAA,CAAkB,OAAO,CAAC,CAAA,EACnD;AACA,QAAA,OAAO,KAAA;AAAA,MACT;AACA,MAAA,IAAI,IAAI,KAAA,KAAU,IAAA,IAAQ,GAAA,CAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAC1D,MAAA,MAAM,QAAQ,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA,CAAE,kBAAkB,OAAO,CAAA;AACzD,MAAA,OAAO,KAAA,CAAM,SAAS,IAAI,CAAA;AAAA,IAC5B,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAUO,MAAM,qBAAA,CAA4C;AAAA,EACvD,SAAA;AAAA,EAEA,YAAY,OAAA,EAAmC;AAC7C,IAAA,IAAA,CAAK,SAAA,GAAY,OAAA,EAAS,QAAA,EAAU,KAAA,MAAW,EAAC;AAAA,EAClD;AAAA,EAEA,MAAM,YACJ,OAAA,EAC8B;AAC9B,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,EAAS,MAAM,CAAA;AAC3C,IAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,MAAM,CAAA;AACxC,IAAA,KAAA,GAAQ,aAAA,CAAc,KAAA,EAAO,OAAA,EAAS,KAAK,CAAA;AAE3C,IAAA,IAAI,MAAA,GAAS,SAAS,MAAA,IAAU,CAAA;AAChC,IAAA,IAAI,QAAQ,OAAA,EAAS,KAAA;AAErB,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,WAAA,CAAY,OAAA,CAAQ,KAAK,CAAA;AACxC,QAAA,IAAI,MAAA,CAAO,MAAA,KAAW,KAAA,CAAA,EAAW,MAAA,GAAS,MAAA,CAAO,MAAA;AACjD,QAAA,IAAI,MAAA,CAAO,KAAA,KAAU,KAAA,CAAA,EAAW,KAAA,GAAQ,MAAA,CAAO,KAAA;AAAA,MACjD,CAAA,CAAA,MAAQ;AACN,QAAA,MAAM,IAAI,WAAW,gBAAgB,CAAA;AAAA,MACvC;AAAA,IACF;AAEA,IAAA,IAAI,SAAS,CAAA,EAAG;AACd,MAAA,KAAA,GAAQ,KAAA,CAAM,MAAM,MAAM,CAAA;AAAA,IAC5B;AACA,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA;AAAA,IAC9B;AAEA,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,OAAA,EAAS,MAAA,GACZ,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,KAAK,iBAAA,CAAkB,CAAA,EAAG,OAAA,CAAQ,MAAM,CAAC,CAAA,GACnD;AAAA,KACN;AAAA,EACF;AAAA,EAEA,MAAM,kBACJ,OAAA,EACoC;AACpC,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,MAAM,CAAA;AAC1C,IAAA,MAAM,cAAc,OAAA,CAAQ,KAAA,GACxB,+BAAA,CAAgC,OAAA,CAAQ,KAAK,CAAA,GAC7C,MAAA;AACJ,IAAA,MAAM,MAAA,GAAS,KAAK,mBAAA,EAAoB;AACxC,IAAA,MAAM,KAAA,GAAQ,QAAQ,UAAA,CACnB,GAAA,CAAI,SAAO,MAAA,CAAO,GAAA,CAAI,GAAG,CAAC,CAAA,CAC1B,GAAA;AAAA,MAAI,CAAA,CAAA,KACH,CAAA,IAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,WAAA,IAAe,WAAA,CAAY,CAAC,CAAA,CAAA,GAAK,CAAA,GAAI;AAAA,KAC3D;AACF,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,OAAA,CAAQ,MAAA,GACX,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,KAAM,CAAA,GAAI,iBAAA,CAAkB,CAAA,EAAG,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAU,CAAA,GACrE;AAAA,KACN;AAAA,EACF;AAAA,EAEA,MAAM,cACJ,OAAA,EACgC;AAEhC,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,KAAA;AACJ,IAAA,IAAI,WAAA;AACJ,IAAA,IAAI,cAAA;AACJ,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI,KAAA;AAEJ,IAAA,IAAI,OAAA,IAAW,YAAY,OAAA,EAAS;AAClC,MAAA,IAAI,CAAA;AACJ,MAAA,IAAI;AACF,QAAA,CAAA,GAAI,WAAA,CAAY,QAAQ,MAAM,CAAA;AAAA,MAChC,CAAA,CAAA,MAAQ;AACN,QAAA,MAAM,IAAI,WAAW,gBAAgB,CAAA;AAAA,MACvC;AACA,MAAA,MAAA,GAAS,iBAAA,CAAkB,EAAE,MAAe,CAAA;AAC5C,MAAA,KAAA,GAAQ,CAAA,CAAE,KAAA;AACV,MAAA,WAAA,GAAc,CAAA,CAAE,WAAA;AAChB,MAAA,cAAA,GAAiB,CAAA,CAAE,cAAA;AACnB,MAAA,MAAA,GAAS,CAAA,CAAE,MAAA;AACX,MAAA,KAAA,GAAQ,OAAA,CAAQ,KAAA;AAAA,IAClB,CAAA,MAAO;AACL,MAAA,MAAA,GAAS,OAAA,EAAS,MAAA;AAClB,MAAA,KAAA,GAAQ,OAAA,EAAS,KAAA;AACjB,MAAA,WAAA,GAAc,OAAA,EAAS,WAAA;AACvB,MAAA,cAAA,GAAiB,OAAA,EAAS,cAAA;AAC1B,MAAA,MAAA,GAAS,SAAS,MAAA,IAAU,CAAA;AAC5B,MAAA,KAAA,GAAQ,OAAA,EAAS,KAAA;AAAA,IACnB;AAGA,IAAA,IAAI,QAAQ,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,YAAA,CAAa,MAAM,CAAC,CAAA;AAGtD,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,KAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,+BAAA,CAAgC,KAAK,CAAC,CAAA;AAAA,IAC7D;AAGA,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,MAAM,kBAAkB,WAAA,GAAc,CAAC,WAAW,CAAA,CAAE,IAAA,KAAS,EAAC;AAC9D,MAAA,KAAA,GAAQ,oBAAoB,KAAA,EAAO;AAAA,QACjC,GAAG,cAAA;AAAA,QACH,MAAA,EAAQ,eAAe,MAAA,IAAU;AAAA,UAC/B,eAAA,CAAgB,CAAC,CAAA,EAAG,KAAA,IAAS;AAAA;AAC/B,OACD,CAAA;AAAA,IACH;AAEA,IAAA,KAAA,GAAQ,aAAA,CAAc,OAAO,WAAW,CAAA;AAExC,IAAA,MAAM,aAAa,KAAA,CAAM,MAAA;AAGzB,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,MAAA,KAAW,CAAA,EAAG;AACvC,MAAA,OAAO;AAAA,QACL,KAAA,EAAO,OAAA,EAAS,MAAA,GACZ,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,KAAK,iBAAA,CAAkB,CAAA,EAAG,OAAA,EAAS,MAAM,CAAC,CAAA,GACpD,KAAA;AAAA,QACJ,UAAU,EAAC;AAAA,QACX;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,iBAAiB,KAAA,IAAS,UAAA;AAChC,IAAA,MAAM,SAAA,GAAY,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,SAAS,cAAc,CAAA;AAE7D,IAAA,MAAM,UAAA,GAAa;AAAA,MACjB,MAAA,EAAQ,gBAAgB,MAAM,CAAA;AAAA,MAC9B,KAAA;AAAA,MACA,WAAA;AAAA,MACA,cAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,MAAM,WAA8C,EAAC;AACrD,IAAA,IAAI,MAAA,GAAS,iBAAiB,UAAA,EAAY;AACxC,MAAA,QAAA,CAAS,aAAa,UAAA,CAAW;AAAA,QAC/B,GAAG,UAAA;AAAA,QACH,QAAQ,MAAA,GAAS;AAAA,OAClB,CAAA;AAAA,IACH;AACA,IAAA,IAAI,SAAS,CAAA,EAAG;AACd,MAAA,QAAA,CAAS,aAAa,UAAA,CAAW;AAAA,QAC/B,GAAG,UAAA;AAAA,QACH,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAS,cAAc;AAAA,OAC5C,CAAA;AAAA,IACH;AAEA,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,OAAA,EAAS,MAAA,GACZ,SAAA,CAAU,GAAA,CAAI,CAAA,CAAA,KAAK,iBAAA,CAAkB,CAAA,EAAG,OAAA,EAAS,MAAM,CAAC,CAAA,GACxD,SAAA;AAAA,MACJ,UAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAM,mBACJ,OAAA,EACqC;AACrC,IAAA,MAAM,SAAS,IAAA,CAAK,mBAAA,EAAoB,CAAE,GAAA,CAAI,QAAQ,SAAS,CAAA;AAC/D,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAM,IAAI,aAAA,CAAc,CAAA,gBAAA,EAAmB,OAAA,CAAQ,SAAS,CAAA,UAAA,CAAY,CAAA;AAAA,IAC1E;AACA,IAAA,OAAO;AAAA,MACL,OAAO,CAAC,EAAE,QAAQ,gBAAA,EAAkB,IAAI,CAAA;AAAA,MACxC,eAAe,OAAA,CAAQ;AAAA,KACzB;AAAA,EACF;AAAA,EAEA,MAAM,eACJ,SAAA,EAC6B;AAC7B,IAAA,OAAO,IAAA,CAAK,qBAAoB,CAAE,GAAA;AAAA,MAChC,kBAAA,CAAmB,cAAA,CAAe,SAAS,CAAC;AAAA,KAC9C;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,GAAA,EAA4B;AAClD,IAAA,MAAM,KAAA,GAAQ,KAAK,SAAA,CAAU,SAAA,CAAU,OAAK,CAAA,CAAE,QAAA,CAAS,QAAQ,GAAG,CAAA;AAClE,IAAA,IAAI,UAAU,EAAA,EAAI;AAChB,MAAA,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,UAAA,EAAmC;AAAA,EAAC;AAAA,EAExD,MAAM,gBACJ,OAAA,EACkC;AAClC,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,MAAM,CAAA;AAC1C,IAAA,IAAI,gBAAA,GAAmB,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,MAAM,CAAA;AACnD,IAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,MAAA,gBAAA,GAAmB,gBAAA,CAAiB,MAAA;AAAA,QAClC,+BAAA,CAAgC,QAAQ,KAAK;AAAA,OAC/C;AAAA,IACF;AACA,IAAA,MAAM,SAAS,MAAA,CAAO,WAAA;AAAA,MACpB,OAAA,CAAQ,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAC1B,QAAA,MAAM,WAAA,uBAAkB,GAAA,EAAoB;AAC5C,QAAA,KAAA,MAAW,UAAU,gBAAA,EAAkB;AACrC,UAAA,MAAM,IAAA,GAAO,kBAAkB,MAAM,CAAA;AAGrC,UAAA,MAAM,eAAe,IAAI,GAAA;AAAA,YACvB,IAAA,CACG,MAAA;AAAA,cACC,CAAA,GAAA,KACE,IAAI,GAAA,CAAI,iBAAA,CAAkB,OAAO,CAAA,KACjC,KAAA,CAAM,kBAAkB,OAAO;AAAA,cAElC,GAAA,CAAI,CAAA,GAAA,KAAO,GAAA,CAAI,KAAK,EACpB,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,KAAM,IAAA,IAAQ,MAAM,MAAS,CAAA,CACzC,IAAI,CAAA,CAAA,KAAK,MAAA,CAAO,CAAC,CAAC;AAAA,WACvB;AACA,UAAA,KAAA,MAAW,SAAS,YAAA,EAAc;AAChC,YAAA,WAAA,CAAY,IAAI,KAAA,EAAA,CAAQ,WAAA,CAAY,IAAI,KAAK,CAAA,IAAK,KAAK,CAAC,CAAA;AAAA,UAC1D;AAAA,QACF;AACA,QAAA,MAAM,SAAS,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,OAAA,EAAS,CAAA,CAAE,GAAA;AAAA,UAC/C,CAAC,CAAC,KAAA,EAAO,KAAK,CAAA,MAAO,EAAE,OAAO,KAAA,EAAM;AAAA,SACtC;AACA,QAAA,OAAO,CAAC,OAAO,MAAM,CAAA;AAAA,MACvB,CAAC;AAAA,KACH;AACA,IAAA,OAAO;AAAA,MACL;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,QAAA,EAA8C;AAC/D,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,eACJ,QAAA,EACiC;AACjC,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,OAAO,gBACL,QAAA,EAC2B;AAC3B,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,gBAAgB,GAAA,EAA4C;AAChE,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,iBAAiB,YAAA,EAAqD;AAC1E,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,YACJ,SAAA,EAC8B;AAC9B,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,mBAAmB,GAAA,EAA4B;AACnD,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,cAAA,CACJ,GAAA,EACA,SAAA,EACmB;AACnB,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,oBACJ,UAAA,EAC+B;AAC/B,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,cAAA,CACJ,OAAA,EACA,YAAA,EACiC;AACjC,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,MAAM,gBACJ,SAAA,EACkC;AAClC,IAAA,MAAM,IAAI,oBAAoB,yBAAyB,CAAA;AAAA,EACzD;AAAA,EAEA,OAAO,eACL,OAAA,EACyB;AACzB,IAAA,IAAI,MAAA,GAA6B,MAAA;AACjC,IAAA,MAAM,KAAA,GAAQ,SAAS,QAAA,IAAY,6BAAA;AACnC,IAAA,GAAG;AACD,MAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,aAAA;AAAA,QACrB,MAAA,GAAS,EAAE,GAAG,OAAA,EAAS,KAAA,EAAO,QAAO,GAAI,EAAE,GAAG,OAAA,EAAS,KAAA;AAAM,OAC/D;AAEA,MAAA,MAAM,GAAA,CAAI,KAAA;AAEV,MAAA,MAAA,GAAS,IAAI,QAAA,CAAS,UAAA;AAAA,IACxB,CAAA,QAAS,MAAA;AAAA,EACX;AAAA,EAEA,mBAAA,GAAsB;AACpB,IAAA,OAAO,IAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,kBAAA,CAAmB,CAAC,CAAA,EAAG,CAAC,CAAC,CAAC,CAAA;AAAA,EACpE;AACF;;;;"}
1
+ {"version":3,"file":"InMemoryCatalogClient.esm.js","sources":["../../src/testUtils/InMemoryCatalogClient.ts"],"sourcesContent":["/*\n * Copyright 2024 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 {\n AddLocationRequest,\n AddLocationResponse,\n CATALOG_FILTER_EXISTS,\n CatalogApi,\n EntityFilterQuery,\n EntityOrderQuery,\n GetEntitiesByRefsRequest,\n GetEntitiesByRefsResponse,\n GetEntitiesRequest,\n GetEntitiesResponse,\n GetEntityAncestorsRequest,\n GetEntityAncestorsResponse,\n GetEntityFacetsRequest,\n GetEntityFacetsResponse,\n GetLocationsResponse,\n Location,\n QueryEntitiesRequest,\n QueryEntitiesResponse,\n QueryLocationsInitialRequest,\n QueryLocationsRequest,\n QueryLocationsResponse,\n StreamEntitiesRequest,\n ValidateEntityResponse,\n} from '@backstage/catalog-client';\nimport {\n CompoundEntityRef,\n DEFAULT_NAMESPACE,\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n InputError,\n NotFoundError,\n NotImplementedError,\n} from '@backstage/errors';\nimport {\n FilterPredicate,\n filterPredicateToFilterFunction,\n} from '@backstage/filter-predicates';\nimport lodash from 'lodash';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { traverse } from '../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch';\nimport type {\n AnalyzeLocationRequest,\n AnalyzeLocationResponse,\n} from '@backstage/plugin-catalog-common';\nimport { DEFAULT_STREAM_ENTITIES_LIMIT } from '../constants.ts';\n\nfunction makeCursor(data: Record<string, unknown>): string {\n return btoa(JSON.stringify(data));\n}\n\nfunction parseCursor(cursor: string): Record<string, unknown> {\n return JSON.parse(atob(cursor));\n}\n\n// CATALOG_FILTER_EXISTS is a Symbol that doesn't survive JSON serialization,\n// so we swap it with a sentinel string when encoding/decoding cursors.\nconst FILTER_EXISTS_SENTINEL = '\\0CATALOG_FILTER_EXISTS';\n\nfunction serializeFilterValue(v: unknown): unknown {\n if (v === CATALOG_FILTER_EXISTS) return FILTER_EXISTS_SENTINEL;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === CATALOG_FILTER_EXISTS ? FILTER_EXISTS_SENTINEL : x,\n );\n }\n return v;\n}\n\nfunction deserializeFilterValue(v: unknown): unknown {\n if (v === FILTER_EXISTS_SENTINEL) return CATALOG_FILTER_EXISTS;\n if (Array.isArray(v)) {\n return v.map(x =>\n x === FILTER_EXISTS_SENTINEL ? CATALOG_FILTER_EXISTS : x,\n );\n }\n return v;\n}\n\nfunction serializeFilter(filter?: EntityFilterQuery): any[] | undefined {\n if (!filter) return undefined;\n return [filter]\n .flat()\n .map(f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]) => [k, serializeFilterValue(v)]),\n ),\n );\n}\n\nfunction deserializeFilter(filter?: any[]): EntityFilterQuery | undefined {\n if (!filter) return undefined;\n return filter.map(\n f =>\n Object.fromEntries(\n Object.entries(f).map(([k, v]: [string, any]) => [\n k,\n deserializeFilterValue(v),\n ]),\n ) as Record<string, string | symbol | (string | symbol)[]>,\n );\n}\n\nfunction buildEntitySearch(entity: Entity) {\n const rows = traverse(entity);\n\n if (entity.metadata?.name) {\n rows.push({\n key: 'metadata.name',\n value: entity.metadata.name.toLowerCase(),\n });\n }\n if (entity.metadata?.namespace) {\n rows.push({\n key: 'metadata.namespace',\n value: entity.metadata.namespace.toLowerCase(),\n });\n }\n if (entity.metadata?.uid) {\n rows.push({\n key: 'metadata.uid',\n value: entity.metadata.uid.toLowerCase(),\n });\n }\n\n if (!entity.metadata.namespace) {\n rows.push({ key: 'metadata.namespace', value: DEFAULT_NAMESPACE });\n }\n\n // Visit relations\n for (const relation of entity.relations ?? []) {\n rows.push({\n key: `relations.${relation.type.toLowerCase()}`,\n value: relation.targetRef.toLowerCase(),\n });\n }\n\n return rows;\n}\n\nfunction createFilter(\n filterOrFilters?: EntityFilterQuery,\n): (entity: Entity) => boolean {\n if (!filterOrFilters) {\n return () => true;\n }\n\n const filters = [filterOrFilters].flat();\n\n return entity => {\n const rows = buildEntitySearch(entity);\n\n return filters.some(filter => {\n for (const [key, expectedValue] of Object.entries(filter)) {\n const searchValues = rows\n .filter(row => row.key === key.toLowerCase())\n .map(row => row.value?.toString().toLowerCase());\n\n if (searchValues.length === 0) {\n return false;\n }\n if (expectedValue === CATALOG_FILTER_EXISTS) {\n continue;\n }\n if (Array.isArray(expectedValue)) {\n return expectedValue.some(value =>\n searchValues?.includes(String(value).toLowerCase()),\n );\n }\n if (!searchValues?.includes(String(expectedValue).toLowerCase())) {\n return false;\n }\n }\n return true;\n });\n };\n}\n\n// Resolves a dot-separated field path against an entity, handling keys that\n// themselves contain dots (e.g. annotation keys like \"backstage.io/orphan\").\n// This matches the backend's parseEntityTransformParams implementation.\nfunction getPathArrayAndValue(\n input: Entity,\n field: string,\n): [string[], unknown] {\n return field.split('.').reduce(\n ([pathArray, inputSubset], pathPart, index, fieldParts) => {\n if (lodash.hasIn(inputSubset, pathPart)) {\n return [pathArray.concat(pathPart), (inputSubset as any)[pathPart]];\n } else if (fieldParts[index + 1] !== undefined) {\n fieldParts[index + 1] = `${pathPart}.${fieldParts[index + 1]}`;\n return [pathArray, inputSubset];\n }\n return [pathArray, undefined];\n },\n [[] as string[], input as unknown],\n );\n}\n\nfunction applyFieldsFilter(entity: Entity, fields?: string[]): Entity {\n if (!fields?.length) {\n return entity;\n }\n const output: Record<string, any> = {};\n for (const field of fields) {\n const [pathArray, value] = getPathArrayAndValue(entity, field);\n if (value !== undefined) {\n lodash.set(output, pathArray, value);\n }\n }\n return output as Entity;\n}\n\nfunction applyOrdering(entities: Entity[], order?: EntityOrderQuery): Entity[] {\n if (!order) {\n return entities;\n }\n const orders = [order].flat();\n if (orders.length === 0) {\n return entities;\n }\n\n const searchMap = new Map<Entity, Array<{ key: string; value: unknown }>>();\n for (const entity of entities) {\n searchMap.set(entity, buildEntitySearch(entity));\n }\n\n return [...entities].sort((a, b) => {\n const aRows = searchMap.get(a)!;\n const bRows = searchMap.get(b)!;\n\n for (const { field, order: dir } of orders) {\n const key = field.toLowerCase();\n const aRow = aRows.find(r => r.key.toLowerCase() === key);\n const bRow = bRows.find(r => r.key.toLowerCase() === key);\n const aValue =\n aRow?.value !== null && aRow?.value !== undefined\n ? String(aRow.value).toLowerCase()\n : null;\n const bValue =\n bRow?.value !== null && bRow?.value !== undefined\n ? String(bRow.value).toLowerCase()\n : null;\n\n if (aValue === null && bValue === null) continue;\n if (aValue === null) return 1;\n if (bValue === null) return -1;\n\n if (aValue < bValue) return dir === 'asc' ? -1 : 1;\n if (aValue > bValue) return dir === 'asc' ? 1 : -1;\n }\n\n // Stable tie-breaker by entity ref, matching backend behavior\n const aRef = stringifyEntityRef(a);\n const bRef = stringifyEntityRef(b);\n if (aRef < bRef) return -1;\n if (aRef > bRef) return 1;\n return 0;\n });\n}\n\nfunction applyFullTextFilter(\n entities: Entity[],\n fullTextFilter?: { term: string; fields?: string[] },\n): Entity[] {\n if (!fullTextFilter?.term?.trim()) {\n return entities;\n }\n const term = fullTextFilter.term.trim().toLowerCase();\n const fields = fullTextFilter.fields?.map(f => f.toLowerCase());\n\n return entities.filter(entity => {\n const rows = buildEntitySearch(entity);\n return rows.some(row => {\n if (fields?.length && !fields.includes(row.key.toLowerCase())) {\n return false;\n }\n if (row.value === null || row.value === undefined) return false;\n const value = String(row.value).toLowerCase();\n return value.includes(term);\n });\n });\n}\n\n/**\n * Implements a fake catalog client that stores entities in memory.\n * Supports filtering, ordering, pagination, full-text search, and field\n * projection for entity query methods. Location and validation methods\n * throw {@link @backstage/errors#NotImplementedError}.\n *\n * @public\n */\nexport class InMemoryCatalogClient implements CatalogApi {\n #entities: Entity[];\n\n constructor(options?: { entities?: Entity[] }) {\n this.#entities = options?.entities?.slice() ?? [];\n }\n\n async getEntities(\n request?: GetEntitiesRequest,\n ): Promise<GetEntitiesResponse> {\n const filter = createFilter(request?.filter);\n let items = this.#entities.filter(filter);\n items = applyOrdering(items, request?.order);\n\n let offset = request?.offset ?? 0;\n let limit = request?.limit;\n\n if (request?.after) {\n try {\n const cursor = parseCursor(request.after);\n if (cursor.offset !== undefined) offset = cursor.offset as number;\n if (cursor.limit !== undefined) limit = cursor.limit as number;\n } catch {\n throw new InputError('Invalid cursor');\n }\n }\n\n if (offset > 0) {\n items = items.slice(offset);\n }\n if (limit !== undefined) {\n items = items.slice(0, limit);\n }\n\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request.fields))\n : items,\n };\n }\n\n async getEntitiesByRefs(\n request: GetEntitiesByRefsRequest,\n ): Promise<GetEntitiesByRefsResponse> {\n const filter = createFilter(request.filter);\n const queryFilter = request.query\n ? filterPredicateToFilterFunction(request.query)\n : undefined;\n const refMap = this.#createEntityRefMap();\n const items = request.entityRefs\n .map(ref => refMap.get(ref))\n .map(e =>\n e && filter(e) && (!queryFilter || queryFilter(e)) ? e : undefined,\n );\n return {\n items: request.fields\n ? items.map(e => (e ? applyFieldsFilter(e, request.fields) : undefined))\n : items,\n };\n }\n\n async queryEntities(\n request?: QueryEntitiesRequest,\n ): Promise<QueryEntitiesResponse> {\n // Decode query parameters from cursor or from the request directly\n let filter: EntityFilterQuery | undefined;\n let query: FilterPredicate | undefined;\n let orderFields: EntityOrderQuery | undefined;\n let fullTextFilter: { term: string; fields?: string[] } | undefined;\n let offset: number;\n let limit: number | undefined;\n\n if (request && 'cursor' in request) {\n let c: Record<string, unknown>;\n try {\n c = parseCursor(request.cursor);\n } catch {\n throw new InputError('Invalid cursor');\n }\n filter = deserializeFilter(c.filter as any[]);\n query = c.query as FilterPredicate | undefined;\n orderFields = c.orderFields as EntityOrderQuery | undefined;\n fullTextFilter = c.fullTextFilter as typeof fullTextFilter;\n offset = c.offset as number;\n limit = request.limit;\n } else {\n filter = request?.filter;\n query = request?.query;\n orderFields = request?.orderFields;\n fullTextFilter = request?.fullTextFilter;\n offset = request?.offset ?? 0;\n limit = request?.limit;\n }\n\n // Apply filter\n let items = this.#entities.filter(createFilter(filter));\n\n // Apply predicate-based query filter\n if (query) {\n items = items.filter(filterPredicateToFilterFunction(query));\n }\n\n // Apply full-text filter, defaulting to the sort field or metadata.uid\n if (fullTextFilter) {\n const orderFieldsList = orderFields ? [orderFields].flat() : [];\n items = applyFullTextFilter(items, {\n ...fullTextFilter,\n fields: fullTextFilter.fields ?? [\n orderFieldsList[0]?.field ?? 'metadata.uid',\n ],\n });\n }\n\n items = applyOrdering(items, orderFields);\n\n const totalItems = items.length;\n\n // No pagination requested, return all items\n if (limit === undefined && offset === 0) {\n return {\n items: request?.fields\n ? items.map(e => applyFieldsFilter(e, request?.fields))\n : items,\n pageInfo: {},\n totalItems,\n };\n }\n\n const effectiveLimit = limit ?? totalItems;\n const pageItems = items.slice(offset, offset + effectiveLimit);\n\n const cursorBase = {\n filter: serializeFilter(filter),\n query,\n orderFields,\n fullTextFilter,\n totalItems,\n };\n const pageInfo: QueryEntitiesResponse['pageInfo'] = {};\n if (offset + effectiveLimit < totalItems) {\n pageInfo.nextCursor = makeCursor({\n ...cursorBase,\n offset: offset + effectiveLimit,\n });\n }\n if (offset > 0) {\n pageInfo.prevCursor = makeCursor({\n ...cursorBase,\n offset: Math.max(0, offset - effectiveLimit),\n });\n }\n\n return {\n items: request?.fields\n ? pageItems.map(e => applyFieldsFilter(e, request?.fields))\n : pageItems,\n totalItems,\n pageInfo,\n };\n }\n\n async getEntityAncestors(\n request: GetEntityAncestorsRequest,\n ): Promise<GetEntityAncestorsResponse> {\n const entity = this.#createEntityRefMap().get(request.entityRef);\n if (!entity) {\n throw new NotFoundError(`Entity with ref ${request.entityRef} not found`);\n }\n return {\n items: [{ entity, parentEntityRefs: [] }],\n rootEntityRef: request.entityRef,\n };\n }\n\n async getEntityByRef(\n entityRef: string | CompoundEntityRef,\n ): Promise<Entity | undefined> {\n return this.#createEntityRefMap().get(\n stringifyEntityRef(parseEntityRef(entityRef)),\n );\n }\n\n async removeEntityByUid(uid: string): Promise<void> {\n const index = this.#entities.findIndex(e => e.metadata.uid === uid);\n if (index !== -1) {\n this.#entities.splice(index, 1);\n }\n }\n\n async refreshEntity(_entityRef: string): Promise<void> {}\n\n async getEntityFacets(\n request: GetEntityFacetsRequest,\n ): Promise<GetEntityFacetsResponse> {\n const filter = createFilter(request.filter);\n let filteredEntities = this.#entities.filter(filter);\n if (request.query) {\n filteredEntities = filteredEntities.filter(\n filterPredicateToFilterFunction(request.query),\n );\n }\n const facets = Object.fromEntries(\n request.facets.map(facet => {\n const facetValues = new Map<string, number>();\n for (const entity of filteredEntities) {\n const rows = buildEntitySearch(entity);\n // Use a Set to count each distinct value once per entity,\n // matching the backend's count(DISTINCT entity_id) behavior\n const uniqueValues = new Set(\n rows\n .filter(row => row.key.toLowerCase() === facet.toLowerCase())\n .map(row => row.value)\n .filter(v => v !== null && v !== undefined)\n .map(v => String(v)),\n );\n for (const value of uniqueValues) {\n facetValues.set(value, (facetValues.get(value) ?? 0) + 1);\n }\n }\n const counts = Array.from(facetValues.entries()).map(\n ([value, count]) => ({ value, count }),\n );\n return [facet, counts];\n }),\n );\n return {\n facets,\n };\n }\n\n async getLocations(_request?: {}): Promise<GetLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async queryLocations(\n _request?: QueryLocationsRequest,\n ): Promise<QueryLocationsResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamLocations(\n _request?: QueryLocationsInitialRequest,\n ): AsyncIterable<Location[]> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationById(_id: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByRef(_locationRef: string): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async addLocation(\n _location: AddLocationRequest,\n ): Promise<AddLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async removeLocationById(_id: string): Promise<void> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async updateLocation(\n _id: string,\n _location: { type?: string; target: string },\n ): Promise<Location> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async getLocationByEntity(\n _entityRef: string | CompoundEntityRef,\n ): Promise<Location | undefined> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async validateEntity(\n _entity: Entity,\n _locationRef: string,\n ): Promise<ValidateEntityResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async analyzeLocation(\n _location: AnalyzeLocationRequest,\n ): Promise<AnalyzeLocationResponse> {\n throw new NotImplementedError('Method not implemented.');\n }\n\n async *streamEntities(\n request?: StreamEntitiesRequest,\n ): AsyncIterable<Entity[]> {\n let cursor: string | undefined = undefined;\n const limit = request?.pageSize ?? DEFAULT_STREAM_ENTITIES_LIMIT;\n do {\n const res = await this.queryEntities(\n cursor ? { ...request, limit, cursor } : { ...request, limit },\n );\n\n yield res.items;\n\n cursor = res.pageInfo.nextCursor;\n } while (cursor);\n }\n\n #createEntityRefMap() {\n return new Map(this.#entities.map(e => [stringifyEntityRef(e), e]));\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/catalog-client",
3
- "version": "1.16.2-next.1",
3
+ "version": "1.16.2",
4
4
  "description": "An isomorphic client for the catalog backend",
5
5
  "backstage": {
6
6
  "role": "common-library"
@@ -58,16 +58,16 @@
58
58
  "test": "backstage-cli package test"
59
59
  },
60
60
  "dependencies": {
61
- "@backstage/catalog-model": "1.10.0",
62
- "@backstage/errors": "1.3.1",
63
- "@backstage/filter-predicates": "0.1.5-next.1",
64
- "@backstage/plugin-catalog-common": "1.2.0-next.0",
61
+ "@backstage/catalog-model": "^1.10.1",
62
+ "@backstage/errors": "^1.3.1",
63
+ "@backstage/filter-predicates": "^0.1.5",
64
+ "@backstage/plugin-catalog-common": "^1.2.0",
65
65
  "cross-fetch": "^4.0.0",
66
66
  "lodash": "^4.17.21",
67
67
  "uri-template": "^2.0.0"
68
68
  },
69
69
  "devDependencies": {
70
- "@backstage/cli": "0.36.6-next.1",
70
+ "@backstage/cli": "^0.36.6",
71
71
  "@types/lodash": "^4.14.151",
72
72
  "msw": "^2.0.0"
73
73
  },