@backstage/plugin-catalog-backend 3.6.1 → 3.6.2-next.1

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,10 +1,44 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
- ## 3.6.1
3
+ ## 3.6.2-next.1
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - 250778e: Fixed a performance regression in the `/entity-facets` endpoint when filters or permission conditions are applied, by routing the EXISTS-based filter through `final_entities` instead of correlating against the much larger `search` table.
7
+ - e9b78e9: Removed the `uuid` dependency and replaced usage with the built-in `crypto.randomUUID()`.
8
+ - Updated dependencies
9
+ - @backstage/catalog-model@1.8.1-next.1
10
+ - @backstage/plugin-catalog-node@2.2.1-next.1
11
+ - @backstage/plugin-permission-common@0.9.9-next.1
12
+
13
+ ## 3.6.1-next.0
14
+
15
+ ### Patch Changes
16
+
17
+ - b33f845: Fixed several database migration `down` functions that were not properly reversible, causing the SQL report to show warnings:
18
+
19
+ - `20241003170511_alter_target_in_locations.js`: both `up` and `down` now include `.notNullable()` when altering the `locations.target` column, preventing the `NOT NULL` constraint from being accidentally dropped when widening the column type from `varchar(255)` to `text`.
20
+ - `20220116144621_remove_legacy.js`: the `down` function now properly recreates the three dropped legacy tables (`entities`, `entities_search`, `entities_relations`) with correct columns and indices.
21
+ - `20210302150147_refresh_state.js`: the `down` function now drops dependent tables in the correct order (avoiding a FK constraint violation) and fixes a typo where the table was referred to as `references` instead of `refresh_state_references`.
22
+ - `20201005122705_add_entity_full_name.js`: the `down` function now drops the `full_name` column from `entities` (not `entities_search`), and restores the `entities_unique_name` index with the correct column order `(kind, name, namespace)`.
23
+ - `20200702153613_entities.js`: the `down` function now uses `table.integer('generation')` instead of `table.string('generation')`, restoring the correct column type.
24
+
25
+ - cf195de: Fixed a performance regression in the `/entity-facets` endpoint when filters or permission conditions are applied, by routing the EXISTS-based filter through `final_entities` instead of correlating against the much larger `search` table.
26
+ - 744fa1f: Removed duplicated entries that appeared in both `dependencies` and `devDependencies`.
27
+ - Updated dependencies
28
+ - @backstage/errors@1.3.1-next.0
29
+ - @backstage/integration@2.0.2-next.0
30
+ - @backstage/backend-openapi-utils@0.6.9-next.0
31
+ - @backstage/backend-plugin-api@1.9.1-next.0
32
+ - @backstage/catalog-client@1.15.1-next.0
33
+ - @backstage/catalog-model@1.8.1-next.0
34
+ - @backstage/config@1.3.8-next.0
35
+ - @backstage/filter-predicates@0.1.3-next.0
36
+ - @backstage/plugin-catalog-node@2.2.1-next.0
37
+ - @backstage/plugin-events-node@0.4.22-next.0
38
+ - @backstage/plugin-permission-common@0.9.9-next.0
39
+ - @backstage/plugin-permission-node@0.10.13-next.0
40
+ - @backstage/types@1.2.2
41
+ - @backstage/plugin-catalog-common@1.1.10-next.0
8
42
 
9
43
  ## 3.6.0
10
44
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  var catalogModel = require('@backstage/catalog-model');
4
4
  var lodash = require('lodash');
5
- var uuid = require('uuid');
5
+ var node_crypto = require('node:crypto');
6
6
  var conversion = require('./conversion.cjs.js');
7
7
  var deleteWithEagerPruningOfChildren = require('./operations/provider/deleteWithEagerPruningOfChildren.cjs.js');
8
8
  var refreshByRefreshKeys = require('./operations/provider/refreshByRefreshKeys.cjs.js');
@@ -59,7 +59,7 @@ class DefaultProviderDatabase {
59
59
  await tx.batchInsert(
60
60
  "refresh_state",
61
61
  chunk.map((item) => ({
62
- entity_id: uuid.v4(),
62
+ entity_id: node_crypto.randomUUID(),
63
63
  entity_ref: catalogModel.stringifyEntityRef(item.deferred.entity),
64
64
  unprocessed_entity: JSON.stringify(item.deferred.entity),
65
65
  unprocessed_hash: item.hash,
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultProviderDatabase.cjs.js","sources":["../../src/database/DefaultProviderDatabase.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DeferredEntity } from '@backstage/plugin-catalog-node';\nimport { Knex } from 'knex';\nimport lodash from 'lodash';\nimport { v4 as uuid } from 'uuid';\nimport { rethrowError } from './conversion';\nimport { deleteWithEagerPruningOfChildren } from './operations/provider/deleteWithEagerPruningOfChildren';\nimport { refreshByRefreshKeys } from './operations/provider/refreshByRefreshKeys';\nimport { checkLocationKeyConflict } from './operations/refreshState/checkLocationKeyConflict';\nimport { insertUnprocessedEntity } from './operations/refreshState/insertUnprocessedEntity';\nimport { updateUnprocessedEntity } from './operations/refreshState/updateUnprocessedEntity';\nimport { DbRefreshStateReferencesRow, DbRefreshStateRow } from './tables';\nimport {\n ProviderDatabase,\n RefreshByKeyOptions,\n ReplaceUnprocessedEntitiesOptions,\n Transaction,\n} from './types';\nimport { generateStableHash } from './util';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n// The number of items that are sent per batch to the database layer, when\n// doing .batchInsert calls to knex. This needs to be low enough to not cause\n// errors in the underlying engine due to exceeding query limits, but large\n// enough to get the speed benefits.\nconst BATCH_SIZE = 50;\n\nexport class DefaultProviderDatabase implements ProviderDatabase {\n private readonly options: {\n database: Knex;\n logger: LoggerService;\n };\n\n constructor(options: { database: Knex; logger: LoggerService }) {\n this.options = options;\n }\n\n async transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T> {\n try {\n let result: T | undefined = undefined;\n await this.options.database.transaction(\n async tx => {\n // We can't return here, as knex swallows the return type in case the\n // transaction is rolled back:\n // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136\n result = await fn(tx);\n },\n {\n // If we explicitly trigger a rollback, don't fail.\n doNotRejectOnRollback: true,\n },\n );\n return result!;\n } catch (e) {\n this.options.logger.debug(`Error during transaction, ${e}`);\n throw rethrowError(e);\n }\n }\n\n async replaceUnprocessedEntities(\n txOpaque: Knex | Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<void> {\n const tx = txOpaque as Knex | Knex.Transaction;\n const { toAdd, toUpsert, toRemove } = await this.createDelta(tx, options);\n\n if (toRemove.length) {\n const removedCount = await deleteWithEagerPruningOfChildren({\n knex: tx,\n entityRefs: toRemove,\n sourceKey: options.sourceKey,\n });\n this.options.logger.debug(\n `removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`,\n );\n }\n\n if (toAdd.length) {\n // The reason for this chunking, rather than just massively batch\n // inserting the entire payload, is that we fall back to the individual\n // upsert mechanism below on conflicts. That path is massively slower than\n // the fast batch path, so we don't want to end up accidentally having to\n // for example item-by-item upsert tens of thousands of entities in a\n // large initial delivery dump. The implication is that the size of these\n // chunks needs to weigh the benefit of fast successful inserts, against\n // the drawback of super slow but more rare fallbacks. There's quickly\n // diminishing returns though with turning up this value way high.\n for (const chunk of lodash.chunk(toAdd, 50)) {\n try {\n await tx.batchInsert(\n 'refresh_state',\n chunk.map(item => ({\n entity_id: uuid(),\n entity_ref: stringifyEntityRef(item.deferred.entity),\n unprocessed_entity: JSON.stringify(item.deferred.entity),\n unprocessed_hash: item.hash,\n errors: '',\n location_key: item.deferred.locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n })),\n BATCH_SIZE,\n );\n await tx.batchInsert(\n 'refresh_state_references',\n chunk.map(item => ({\n source_key: options.sourceKey,\n target_entity_ref: stringifyEntityRef(item.deferred.entity),\n })),\n BATCH_SIZE,\n );\n } catch (error) {\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n this.options.logger.debug(\n `Fast insert path failed, falling back to slow path, ${error}`,\n );\n toUpsert.push(...chunk);\n }\n }\n }\n }\n\n if (toUpsert.length) {\n for (const {\n deferred: { entity, locationKey },\n hash,\n } of toUpsert) {\n const entityRef = stringifyEntityRef(entity);\n\n try {\n let ok = await updateUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n });\n if (!ok) {\n ok = await insertUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n logger: this.options.logger,\n });\n }\n if (ok) {\n await tx<DbRefreshStateReferencesRow>('refresh_state_references')\n .where('target_entity_ref', entityRef)\n .delete();\n\n await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n ).insert({\n source_key: options.sourceKey,\n target_entity_ref: entityRef,\n });\n } else {\n await tx<DbRefreshStateReferencesRow>('refresh_state_references')\n .where('target_entity_ref', entityRef)\n .andWhere({ source_key: options.sourceKey })\n .delete();\n\n const conflictingKey = await checkLocationKeyConflict({\n tx,\n entityRef,\n locationKey,\n });\n if (conflictingKey) {\n this.options.logger.warn(\n `Source ${options.sourceKey} detected conflicting entityRef ${entityRef} already referenced by ${conflictingKey} and now also ${locationKey}`,\n );\n }\n }\n } catch (error) {\n this.options.logger.error(\n `Failed to add '${entityRef}' from source '${options.sourceKey}', ${error}`,\n );\n }\n }\n }\n }\n\n async listReferenceSourceKeys(txOpaque: Transaction): Promise<string[]> {\n const tx = txOpaque as Knex | Knex.Transaction;\n\n const rows = await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .distinct('source_key')\n .whereNotNull('source_key');\n\n return rows\n .map(row => row.source_key)\n .filter((key): key is string => !!key);\n }\n\n async refreshByRefreshKeys(\n txOpaque: Transaction,\n options: RefreshByKeyOptions,\n ) {\n const tx = txOpaque as Knex.Transaction;\n await refreshByRefreshKeys({ tx, keys: options.keys });\n }\n\n private async createDelta(\n tx: Knex | Knex.Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<{\n toAdd: { deferred: DeferredEntity; hash: string }[];\n toUpsert: { deferred: DeferredEntity; hash: string }[];\n toRemove: string[];\n }> {\n if (options.type === 'delta') {\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = options.removed.map(e => e.entityRef);\n\n for (const chunk of lodash.chunk(options.added, 1000)) {\n const entityRefs = chunk.map(e => stringifyEntityRef(e.entity));\n const rows = await tx<DbRefreshStateRow>('refresh_state')\n .select(['entity_ref', 'unprocessed_hash', 'location_key'])\n .whereIn('entity_ref', entityRefs);\n const oldStates = new Map(\n rows.map(row => [\n row.entity_ref,\n {\n unprocessed_hash: row.unprocessed_hash,\n location_key: row.location_key,\n },\n ]),\n );\n\n chunk.forEach((deferred, i) => {\n const entityRef = entityRefs[i];\n const newHash = generateStableHash(deferred.entity);\n const oldState = oldStates.get(entityRef);\n if (oldState === undefined) {\n // Add any entity that does not exist in the database\n toAdd.push({ deferred, hash: newHash });\n } else if (\n (deferred.locationKey ?? null) !== (oldState.location_key ?? null)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(entityRef);\n toAdd.push({ deferred, hash: newHash });\n } else if (newHash !== oldState.unprocessed_hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push({ deferred, hash: newHash });\n }\n });\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n\n // Grab all of the existing references from the same source, and their locationKeys as well\n const oldRefs = await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .leftJoin<DbRefreshStateRow>('refresh_state', {\n target_entity_ref: 'entity_ref',\n })\n .where({ source_key: options.sourceKey })\n .select({\n target_entity_ref: 'refresh_state_references.target_entity_ref',\n location_key: 'refresh_state.location_key',\n unprocessed_hash: 'refresh_state.unprocessed_hash',\n });\n\n const items = options.items.map(deferred => ({\n deferred,\n ref: stringifyEntityRef(deferred.entity),\n hash: generateStableHash(deferred.entity),\n }));\n\n const oldRefsSet = new Map(\n oldRefs.map(r => [\n r.target_entity_ref,\n {\n locationKey: r.location_key,\n oldEntityHash: r.unprocessed_hash,\n },\n ]),\n );\n const newRefsSet = new Set(items.map(item => item.ref));\n\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = oldRefs\n .map(row => row.target_entity_ref)\n .filter(ref => !newRefsSet.has(ref));\n\n for (const item of items) {\n const oldRef = oldRefsSet.get(item.ref);\n const upsertItem = { deferred: item.deferred, hash: item.hash };\n if (!oldRef) {\n // Add any entity that does not exist in the database\n toAdd.push(upsertItem);\n } else if (\n (oldRef.locationKey ?? undefined) !==\n (item.deferred.locationKey ?? undefined)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(item.ref);\n toAdd.push(upsertItem);\n } else if (oldRef.oldEntityHash !== item.hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push(upsertItem);\n }\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n}\n"],"names":["rethrowError","deleteWithEagerPruningOfChildren","lodash","uuid","stringifyEntityRef","isDatabaseConflictError","updateUnprocessedEntity","insertUnprocessedEntity","checkLocationKeyConflict","refreshByRefreshKeys","toAdd","toUpsert","toRemove","generateStableHash"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultProviderDatabase.cjs.js","sources":["../../src/database/DefaultProviderDatabase.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DeferredEntity } from '@backstage/plugin-catalog-node';\nimport { Knex } from 'knex';\nimport lodash from 'lodash';\nimport { randomUUID as uuid } from 'node:crypto';\nimport { rethrowError } from './conversion';\nimport { deleteWithEagerPruningOfChildren } from './operations/provider/deleteWithEagerPruningOfChildren';\nimport { refreshByRefreshKeys } from './operations/provider/refreshByRefreshKeys';\nimport { checkLocationKeyConflict } from './operations/refreshState/checkLocationKeyConflict';\nimport { insertUnprocessedEntity } from './operations/refreshState/insertUnprocessedEntity';\nimport { updateUnprocessedEntity } from './operations/refreshState/updateUnprocessedEntity';\nimport { DbRefreshStateReferencesRow, DbRefreshStateRow } from './tables';\nimport {\n ProviderDatabase,\n RefreshByKeyOptions,\n ReplaceUnprocessedEntitiesOptions,\n Transaction,\n} from './types';\nimport { generateStableHash } from './util';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n// The number of items that are sent per batch to the database layer, when\n// doing .batchInsert calls to knex. This needs to be low enough to not cause\n// errors in the underlying engine due to exceeding query limits, but large\n// enough to get the speed benefits.\nconst BATCH_SIZE = 50;\n\nexport class DefaultProviderDatabase implements ProviderDatabase {\n private readonly options: {\n database: Knex;\n logger: LoggerService;\n };\n\n constructor(options: { database: Knex; logger: LoggerService }) {\n this.options = options;\n }\n\n async transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T> {\n try {\n let result: T | undefined = undefined;\n await this.options.database.transaction(\n async tx => {\n // We can't return here, as knex swallows the return type in case the\n // transaction is rolled back:\n // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136\n result = await fn(tx);\n },\n {\n // If we explicitly trigger a rollback, don't fail.\n doNotRejectOnRollback: true,\n },\n );\n return result!;\n } catch (e) {\n this.options.logger.debug(`Error during transaction, ${e}`);\n throw rethrowError(e);\n }\n }\n\n async replaceUnprocessedEntities(\n txOpaque: Knex | Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<void> {\n const tx = txOpaque as Knex | Knex.Transaction;\n const { toAdd, toUpsert, toRemove } = await this.createDelta(tx, options);\n\n if (toRemove.length) {\n const removedCount = await deleteWithEagerPruningOfChildren({\n knex: tx,\n entityRefs: toRemove,\n sourceKey: options.sourceKey,\n });\n this.options.logger.debug(\n `removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`,\n );\n }\n\n if (toAdd.length) {\n // The reason for this chunking, rather than just massively batch\n // inserting the entire payload, is that we fall back to the individual\n // upsert mechanism below on conflicts. That path is massively slower than\n // the fast batch path, so we don't want to end up accidentally having to\n // for example item-by-item upsert tens of thousands of entities in a\n // large initial delivery dump. The implication is that the size of these\n // chunks needs to weigh the benefit of fast successful inserts, against\n // the drawback of super slow but more rare fallbacks. There's quickly\n // diminishing returns though with turning up this value way high.\n for (const chunk of lodash.chunk(toAdd, 50)) {\n try {\n await tx.batchInsert(\n 'refresh_state',\n chunk.map(item => ({\n entity_id: uuid(),\n entity_ref: stringifyEntityRef(item.deferred.entity),\n unprocessed_entity: JSON.stringify(item.deferred.entity),\n unprocessed_hash: item.hash,\n errors: '',\n location_key: item.deferred.locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n })),\n BATCH_SIZE,\n );\n await tx.batchInsert(\n 'refresh_state_references',\n chunk.map(item => ({\n source_key: options.sourceKey,\n target_entity_ref: stringifyEntityRef(item.deferred.entity),\n })),\n BATCH_SIZE,\n );\n } catch (error) {\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n this.options.logger.debug(\n `Fast insert path failed, falling back to slow path, ${error}`,\n );\n toUpsert.push(...chunk);\n }\n }\n }\n }\n\n if (toUpsert.length) {\n for (const {\n deferred: { entity, locationKey },\n hash,\n } of toUpsert) {\n const entityRef = stringifyEntityRef(entity);\n\n try {\n let ok = await updateUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n });\n if (!ok) {\n ok = await insertUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n logger: this.options.logger,\n });\n }\n if (ok) {\n await tx<DbRefreshStateReferencesRow>('refresh_state_references')\n .where('target_entity_ref', entityRef)\n .delete();\n\n await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n ).insert({\n source_key: options.sourceKey,\n target_entity_ref: entityRef,\n });\n } else {\n await tx<DbRefreshStateReferencesRow>('refresh_state_references')\n .where('target_entity_ref', entityRef)\n .andWhere({ source_key: options.sourceKey })\n .delete();\n\n const conflictingKey = await checkLocationKeyConflict({\n tx,\n entityRef,\n locationKey,\n });\n if (conflictingKey) {\n this.options.logger.warn(\n `Source ${options.sourceKey} detected conflicting entityRef ${entityRef} already referenced by ${conflictingKey} and now also ${locationKey}`,\n );\n }\n }\n } catch (error) {\n this.options.logger.error(\n `Failed to add '${entityRef}' from source '${options.sourceKey}', ${error}`,\n );\n }\n }\n }\n }\n\n async listReferenceSourceKeys(txOpaque: Transaction): Promise<string[]> {\n const tx = txOpaque as Knex | Knex.Transaction;\n\n const rows = await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .distinct('source_key')\n .whereNotNull('source_key');\n\n return rows\n .map(row => row.source_key)\n .filter((key): key is string => !!key);\n }\n\n async refreshByRefreshKeys(\n txOpaque: Transaction,\n options: RefreshByKeyOptions,\n ) {\n const tx = txOpaque as Knex.Transaction;\n await refreshByRefreshKeys({ tx, keys: options.keys });\n }\n\n private async createDelta(\n tx: Knex | Knex.Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<{\n toAdd: { deferred: DeferredEntity; hash: string }[];\n toUpsert: { deferred: DeferredEntity; hash: string }[];\n toRemove: string[];\n }> {\n if (options.type === 'delta') {\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = options.removed.map(e => e.entityRef);\n\n for (const chunk of lodash.chunk(options.added, 1000)) {\n const entityRefs = chunk.map(e => stringifyEntityRef(e.entity));\n const rows = await tx<DbRefreshStateRow>('refresh_state')\n .select(['entity_ref', 'unprocessed_hash', 'location_key'])\n .whereIn('entity_ref', entityRefs);\n const oldStates = new Map(\n rows.map(row => [\n row.entity_ref,\n {\n unprocessed_hash: row.unprocessed_hash,\n location_key: row.location_key,\n },\n ]),\n );\n\n chunk.forEach((deferred, i) => {\n const entityRef = entityRefs[i];\n const newHash = generateStableHash(deferred.entity);\n const oldState = oldStates.get(entityRef);\n if (oldState === undefined) {\n // Add any entity that does not exist in the database\n toAdd.push({ deferred, hash: newHash });\n } else if (\n (deferred.locationKey ?? null) !== (oldState.location_key ?? null)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(entityRef);\n toAdd.push({ deferred, hash: newHash });\n } else if (newHash !== oldState.unprocessed_hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push({ deferred, hash: newHash });\n }\n });\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n\n // Grab all of the existing references from the same source, and their locationKeys as well\n const oldRefs = await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .leftJoin<DbRefreshStateRow>('refresh_state', {\n target_entity_ref: 'entity_ref',\n })\n .where({ source_key: options.sourceKey })\n .select({\n target_entity_ref: 'refresh_state_references.target_entity_ref',\n location_key: 'refresh_state.location_key',\n unprocessed_hash: 'refresh_state.unprocessed_hash',\n });\n\n const items = options.items.map(deferred => ({\n deferred,\n ref: stringifyEntityRef(deferred.entity),\n hash: generateStableHash(deferred.entity),\n }));\n\n const oldRefsSet = new Map(\n oldRefs.map(r => [\n r.target_entity_ref,\n {\n locationKey: r.location_key,\n oldEntityHash: r.unprocessed_hash,\n },\n ]),\n );\n const newRefsSet = new Set(items.map(item => item.ref));\n\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = oldRefs\n .map(row => row.target_entity_ref)\n .filter(ref => !newRefsSet.has(ref));\n\n for (const item of items) {\n const oldRef = oldRefsSet.get(item.ref);\n const upsertItem = { deferred: item.deferred, hash: item.hash };\n if (!oldRef) {\n // Add any entity that does not exist in the database\n toAdd.push(upsertItem);\n } else if (\n (oldRef.locationKey ?? undefined) !==\n (item.deferred.locationKey ?? undefined)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(item.ref);\n toAdd.push(upsertItem);\n } else if (oldRef.oldEntityHash !== item.hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push(upsertItem);\n }\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n}\n"],"names":["rethrowError","deleteWithEagerPruningOfChildren","lodash","uuid","stringifyEntityRef","isDatabaseConflictError","updateUnprocessedEntity","insertUnprocessedEntity","checkLocationKeyConflict","refreshByRefreshKeys","toAdd","toUpsert","toRemove","generateStableHash"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var catalogModel = require('@backstage/catalog-model');
4
- var uuid = require('uuid');
4
+ var node_crypto = require('node:crypto');
5
5
  var backendPluginApi = require('@backstage/backend-plugin-api');
6
6
 
7
7
  async function insertUnprocessedEntity(options) {
@@ -10,7 +10,7 @@ async function insertUnprocessedEntity(options) {
10
10
  const serializedEntity = JSON.stringify(entity);
11
11
  try {
12
12
  let query = tx("refresh_state").insert({
13
- entity_id: uuid.v4(),
13
+ entity_id: node_crypto.randomUUID(),
14
14
  entity_ref: entityRef,
15
15
  unprocessed_entity: serializedEntity,
16
16
  unprocessed_hash: hash,
@@ -1 +1 @@
1
- {"version":3,"file":"insertUnprocessedEntity.cjs.js","sources":["../../../../src/database/operations/refreshState/insertUnprocessedEntity.ts"],"sourcesContent":["/*\n * Copyright 2022 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\nimport { v4 as uuid } from 'uuid';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n/**\n * Attempts to insert a new refresh state row for the given entity, returning\n * true if successful and false if there was a conflict.\n */\nexport async function insertUnprocessedEntity(options: {\n tx: Knex | Knex.Transaction;\n entity: Entity;\n hash: string;\n locationKey?: string;\n logger: LoggerService;\n}): Promise<boolean> {\n const { tx, entity, hash, logger, locationKey } = options;\n\n const entityRef = stringifyEntityRef(entity);\n const serializedEntity = JSON.stringify(entity);\n\n try {\n let query = tx<DbRefreshStateRow>('refresh_state').insert({\n entity_id: uuid(),\n entity_ref: entityRef,\n unprocessed_entity: serializedEntity,\n unprocessed_hash: hash,\n errors: '',\n location_key: locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n });\n\n // TODO(Rugvip): only tested towards MySQL, Postgres and SQLite.\n // We have to do this because the only way to detect if there was a conflict with\n // SQLite is to catch the error, while Postgres needs to ignore the conflict to not\n // break the ongoing transaction.\n if (tx.client.config.client.includes('pg')) {\n query = query.onConflict('entity_ref').ignore() as any; // type here does not match runtime\n }\n\n // Postgres gives as an object with rowCount, SQLite gives us an array\n const result: { rowCount?: number; length?: number } = await query;\n return result.rowCount === 1 || result.length === 1;\n } catch (error) {\n // SQLite, or MySQL reached this rather than the rowCount check above\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n logger.debug(`Unable to insert a new refresh state row, ${error}`);\n return false;\n }\n }\n}\n"],"names":["stringifyEntityRef","uuid","isDatabaseConflictError"],"mappings":";;;;;;AA6BA,eAAsB,wBAAwB,OAAA,EAMzB;AACnB,EAAA,MAAM,EAAE,EAAA,EAAI,MAAA,EAAQ,IAAA,EAAM,MAAA,EAAQ,aAAY,GAAI,OAAA;AAElD,EAAA,MAAM,SAAA,GAAYA,gCAAmB,MAAM,CAAA;AAC3C,EAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AAE9C,EAAA,IAAI;AACF,IAAA,IAAI,KAAA,GAAQ,EAAA,CAAsB,eAAe,CAAA,CAAE,MAAA,CAAO;AAAA,MACxD,WAAWC,OAAA,EAAK;AAAA,MAChB,UAAA,EAAY,SAAA;AAAA,MACZ,kBAAA,EAAoB,gBAAA;AAAA,MACpB,gBAAA,EAAkB,IAAA;AAAA,MAClB,MAAA,EAAQ,EAAA;AAAA,MACR,YAAA,EAAc,WAAA;AAAA,MACd,cAAA,EAAgB,EAAA,CAAG,EAAA,CAAG,GAAA,EAAI;AAAA,MAC1B,iBAAA,EAAmB,EAAA,CAAG,EAAA,CAAG,GAAA;AAAI,KAC9B,CAAA;AAMD,IAAA,IAAI,GAAG,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AAC1C,MAAA,KAAA,GAAQ,KAAA,CAAM,UAAA,CAAW,YAAY,CAAA,CAAE,MAAA,EAAO;AAAA,IAChD;AAGA,IAAA,MAAM,SAAiD,MAAM,KAAA;AAC7D,IAAA,OAAO,MAAA,CAAO,QAAA,KAAa,CAAA,IAAK,MAAA,CAAO,MAAA,KAAW,CAAA;AAAA,EACpD,SAAS,KAAA,EAAO;AAEd,IAAA,IAAI,CAACC,wCAAA,CAAwB,KAAK,CAAA,EAAG;AACnC,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,0CAAA,EAA6C,KAAK,CAAA,CAAE,CAAA;AACjE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF;;;;"}
1
+ {"version":3,"file":"insertUnprocessedEntity.cjs.js","sources":["../../../../src/database/operations/refreshState/insertUnprocessedEntity.ts"],"sourcesContent":["/*\n * Copyright 2022 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\nimport { randomUUID as uuid } from 'node:crypto';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n/**\n * Attempts to insert a new refresh state row for the given entity, returning\n * true if successful and false if there was a conflict.\n */\nexport async function insertUnprocessedEntity(options: {\n tx: Knex | Knex.Transaction;\n entity: Entity;\n hash: string;\n locationKey?: string;\n logger: LoggerService;\n}): Promise<boolean> {\n const { tx, entity, hash, logger, locationKey } = options;\n\n const entityRef = stringifyEntityRef(entity);\n const serializedEntity = JSON.stringify(entity);\n\n try {\n let query = tx<DbRefreshStateRow>('refresh_state').insert({\n entity_id: uuid(),\n entity_ref: entityRef,\n unprocessed_entity: serializedEntity,\n unprocessed_hash: hash,\n errors: '',\n location_key: locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n });\n\n // TODO(Rugvip): only tested towards MySQL, Postgres and SQLite.\n // We have to do this because the only way to detect if there was a conflict with\n // SQLite is to catch the error, while Postgres needs to ignore the conflict to not\n // break the ongoing transaction.\n if (tx.client.config.client.includes('pg')) {\n query = query.onConflict('entity_ref').ignore() as any; // type here does not match runtime\n }\n\n // Postgres gives as an object with rowCount, SQLite gives us an array\n const result: { rowCount?: number; length?: number } = await query;\n return result.rowCount === 1 || result.length === 1;\n } catch (error) {\n // SQLite, or MySQL reached this rather than the rowCount check above\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n logger.debug(`Unable to insert a new refresh state row, ${error}`);\n return false;\n }\n }\n}\n"],"names":["stringifyEntityRef","uuid","isDatabaseConflictError"],"mappings":";;;;;;AA6BA,eAAsB,wBAAwB,OAAA,EAMzB;AACnB,EAAA,MAAM,EAAE,EAAA,EAAI,MAAA,EAAQ,IAAA,EAAM,MAAA,EAAQ,aAAY,GAAI,OAAA;AAElD,EAAA,MAAM,SAAA,GAAYA,gCAAmB,MAAM,CAAA;AAC3C,EAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AAE9C,EAAA,IAAI;AACF,IAAA,IAAI,KAAA,GAAQ,EAAA,CAAsB,eAAe,CAAA,CAAE,MAAA,CAAO;AAAA,MACxD,WAAWC,sBAAA,EAAK;AAAA,MAChB,UAAA,EAAY,SAAA;AAAA,MACZ,kBAAA,EAAoB,gBAAA;AAAA,MACpB,gBAAA,EAAkB,IAAA;AAAA,MAClB,MAAA,EAAQ,EAAA;AAAA,MACR,YAAA,EAAc,WAAA;AAAA,MACd,cAAA,EAAgB,EAAA,CAAG,EAAA,CAAG,GAAA,EAAI;AAAA,MAC1B,iBAAA,EAAmB,EAAA,CAAG,EAAA,CAAG,GAAA;AAAI,KAC9B,CAAA;AAMD,IAAA,IAAI,GAAG,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG;AAC1C,MAAA,KAAA,GAAQ,KAAA,CAAM,UAAA,CAAW,YAAY,CAAA,CAAE,MAAA,EAAO;AAAA,IAChD;AAGA,IAAA,MAAM,SAAiD,MAAM,KAAA;AAC7D,IAAA,OAAO,MAAA,CAAO,QAAA,KAAa,CAAA,IAAK,MAAA,CAAO,MAAA,KAAW,CAAA;AAAA,EACpD,SAAS,KAAA,EAAO;AAEd,IAAA,IAAI,CAACC,wCAAA,CAAwB,KAAK,CAAA,EAAG;AACnC,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,0CAAA,EAA6C,KAAK,CAAA,CAAE,CAAA;AACjE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF;;;;"}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var splitToChunks = require('lodash/chunk');
4
- var uuid = require('uuid');
4
+ var node_crypto = require('node:crypto');
5
5
  var util = require('../../util.cjs.js');
6
6
 
7
7
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
@@ -38,7 +38,7 @@ async function markForStitching(options) {
38
38
  }, knex);
39
39
  }
40
40
  } else if (mode === "deferred") {
41
- const ticket = uuid.v4();
41
+ const ticket = node_crypto.randomUUID();
42
42
  for (const chunk of entityRefs) {
43
43
  await util.retryOnDeadlock(async () => {
44
44
  if (chunk.length > 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"markForStitching.cjs.js","sources":["../../../../src/database/operations/stitcher/markForStitching.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { Knex } from 'knex';\nimport splitToChunks from 'lodash/chunk';\nimport { v4 as uuid } from 'uuid';\nimport { StitchingStrategy } from '../../../stitching/types';\nimport {\n DbFinalEntitiesRow,\n DbRefreshStateRow,\n DbStitchQueueRow,\n} from '../../tables';\nimport { retryOnDeadlock } from '../../util';\n\nconst UPDATE_CHUNK_SIZE = 100; // Smaller chunks reduce contention\n\n/**\n * Marks a number of entities for stitching some time in the near\n * future.\n *\n * @remarks\n */\nexport async function markForStitching(options: {\n knex: Knex | Knex.Transaction;\n strategy: StitchingStrategy;\n entityRefs?: Iterable<string>;\n entityIds?: Iterable<string>;\n}): Promise<void> {\n const entityRefs = sortSplit(options.entityRefs);\n const entityIds = sortSplit(options.entityIds);\n const knex = options.knex;\n const mode = options.strategy.mode;\n\n if (mode === 'immediate') {\n for (const chunk of entityRefs) {\n await knex\n .table<DbFinalEntitiesRow>('final_entities')\n .update({\n hash: 'force-stitching',\n })\n .whereIn('entity_ref', chunk);\n await retryOnDeadlock(async () => {\n await knex\n .table<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'force-stitching',\n next_update_at: knex.fn.now(),\n })\n .whereIn('entity_ref', chunk);\n }, knex);\n }\n\n for (const chunk of entityIds) {\n await knex\n .table<DbFinalEntitiesRow>('final_entities')\n .update({\n hash: 'force-stitching',\n })\n .whereIn('entity_id', chunk);\n await retryOnDeadlock(async () => {\n await knex\n .table<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'force-stitching',\n next_update_at: knex.fn.now(),\n })\n .whereIn('entity_id', chunk);\n }, knex);\n }\n } else if (mode === 'deferred') {\n // It's OK that this is shared across stitch_queue rows; it just needs to\n // be uniquely generated for every new stitch request.\n const ticket = uuid();\n\n for (const chunk of entityRefs) {\n await retryOnDeadlock(async () => {\n if (chunk.length > 0) {\n await knex<DbStitchQueueRow>('stitch_queue')\n .insert(\n chunk.map(ref => ({\n entity_ref: ref,\n stitch_ticket: ticket,\n next_stitch_at: knex.fn.now(),\n })),\n )\n .onConflict('entity_ref')\n .merge(['next_stitch_at', 'stitch_ticket']);\n }\n }, knex);\n }\n\n for (const chunk of entityIds) {\n await retryOnDeadlock(async () => {\n // Look up entity_refs from refresh_state by entity_id\n const refreshStateRows = await knex<DbRefreshStateRow>('refresh_state')\n .select('entity_ref')\n .whereIn('entity_id', chunk);\n\n if (refreshStateRows.length > 0) {\n await knex<DbStitchQueueRow>('stitch_queue')\n .insert(\n refreshStateRows.map(row => ({\n entity_ref: row.entity_ref,\n stitch_ticket: ticket,\n next_stitch_at: knex.fn.now(),\n })),\n )\n .onConflict('entity_ref')\n .merge(['next_stitch_at', 'stitch_ticket']);\n }\n }, knex);\n }\n } else {\n throw new Error(`Unknown stitching strategy mode ${mode}`);\n }\n}\n\nfunction sortSplit(input: Iterable<string> | undefined): string[][] {\n if (!input) {\n return [];\n }\n const array = Array.isArray(input) ? input.slice() : [...input];\n array.sort();\n return splitToChunks(array, UPDATE_CHUNK_SIZE);\n}\n"],"names":["retryOnDeadlock","uuid","splitToChunks"],"mappings":"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"}
1
+ {"version":3,"file":"markForStitching.cjs.js","sources":["../../../../src/database/operations/stitcher/markForStitching.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { Knex } from 'knex';\nimport splitToChunks from 'lodash/chunk';\nimport { randomUUID as uuid } from 'node:crypto';\nimport { StitchingStrategy } from '../../../stitching/types';\nimport {\n DbFinalEntitiesRow,\n DbRefreshStateRow,\n DbStitchQueueRow,\n} from '../../tables';\nimport { retryOnDeadlock } from '../../util';\n\nconst UPDATE_CHUNK_SIZE = 100; // Smaller chunks reduce contention\n\n/**\n * Marks a number of entities for stitching some time in the near\n * future.\n *\n * @remarks\n */\nexport async function markForStitching(options: {\n knex: Knex | Knex.Transaction;\n strategy: StitchingStrategy;\n entityRefs?: Iterable<string>;\n entityIds?: Iterable<string>;\n}): Promise<void> {\n const entityRefs = sortSplit(options.entityRefs);\n const entityIds = sortSplit(options.entityIds);\n const knex = options.knex;\n const mode = options.strategy.mode;\n\n if (mode === 'immediate') {\n for (const chunk of entityRefs) {\n await knex\n .table<DbFinalEntitiesRow>('final_entities')\n .update({\n hash: 'force-stitching',\n })\n .whereIn('entity_ref', chunk);\n await retryOnDeadlock(async () => {\n await knex\n .table<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'force-stitching',\n next_update_at: knex.fn.now(),\n })\n .whereIn('entity_ref', chunk);\n }, knex);\n }\n\n for (const chunk of entityIds) {\n await knex\n .table<DbFinalEntitiesRow>('final_entities')\n .update({\n hash: 'force-stitching',\n })\n .whereIn('entity_id', chunk);\n await retryOnDeadlock(async () => {\n await knex\n .table<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'force-stitching',\n next_update_at: knex.fn.now(),\n })\n .whereIn('entity_id', chunk);\n }, knex);\n }\n } else if (mode === 'deferred') {\n // It's OK that this is shared across stitch_queue rows; it just needs to\n // be uniquely generated for every new stitch request.\n const ticket = uuid();\n\n for (const chunk of entityRefs) {\n await retryOnDeadlock(async () => {\n if (chunk.length > 0) {\n await knex<DbStitchQueueRow>('stitch_queue')\n .insert(\n chunk.map(ref => ({\n entity_ref: ref,\n stitch_ticket: ticket,\n next_stitch_at: knex.fn.now(),\n })),\n )\n .onConflict('entity_ref')\n .merge(['next_stitch_at', 'stitch_ticket']);\n }\n }, knex);\n }\n\n for (const chunk of entityIds) {\n await retryOnDeadlock(async () => {\n // Look up entity_refs from refresh_state by entity_id\n const refreshStateRows = await knex<DbRefreshStateRow>('refresh_state')\n .select('entity_ref')\n .whereIn('entity_id', chunk);\n\n if (refreshStateRows.length > 0) {\n await knex<DbStitchQueueRow>('stitch_queue')\n .insert(\n refreshStateRows.map(row => ({\n entity_ref: row.entity_ref,\n stitch_ticket: ticket,\n next_stitch_at: knex.fn.now(),\n })),\n )\n .onConflict('entity_ref')\n .merge(['next_stitch_at', 'stitch_ticket']);\n }\n }, knex);\n }\n } else {\n throw new Error(`Unknown stitching strategy mode ${mode}`);\n }\n}\n\nfunction sortSplit(input: Iterable<string> | undefined): string[][] {\n if (!input) {\n return [];\n }\n const array = Array.isArray(input) ? input.slice() : [...input];\n array.sort();\n return splitToChunks(array, UPDATE_CHUNK_SIZE);\n}\n"],"names":["retryOnDeadlock","uuid","splitToChunks"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var errors = require('@backstage/errors');
4
- var uuid = require('uuid');
4
+ var node_crypto = require('node:crypto');
5
5
  var util = require('../processing/util.cjs.js');
6
6
  var conversion = require('../util/conversion.cjs.js');
7
7
  var catalogModel = require('@backstage/catalog-model');
@@ -42,7 +42,7 @@ class DefaultLocationStore {
42
42
  );
43
43
  }
44
44
  const inner = {
45
- id: uuid.v4(),
45
+ id: node_crypto.randomUUID(),
46
46
  type: input.type,
47
47
  target: input.target,
48
48
  location_entity_ref: conversion.computeLocationEntityRef(input.type, input.target)
@@ -311,7 +311,7 @@ class DefaultLocationStore {
311
311
  for (const batch of lodash.chunk(Array.from(urls), 100)) {
312
312
  const existingUrls = await this.db("locations").where("type", "=", "url").whereIn("target", batch).select().then((rows) => new Set(rows.map((row) => row.target)));
313
313
  const newLocations = batch.filter((url) => !existingUrls.has(url)).map((url) => ({
314
- id: uuid.v4(),
314
+ id: node_crypto.randomUUID(),
315
315
  type: "url",
316
316
  target: url,
317
317
  location_entity_ref: conversion.computeLocationEntityRef("url", url)
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultLocationStore.cjs.js","sources":["../../src/providers/DefaultLocationStore.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Location } from '@backstage/catalog-client';\nimport { ConflictError, InputError, NotFoundError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport {\n DbLocationsRow,\n DbRefreshStateRow,\n DbSearchRow,\n} from '../database/tables';\nimport { getEntityLocationRef } from '../processing/util';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\nimport {\n computeLocationEntityRef,\n locationSpecToLocationEntity,\n} from '../util/conversion';\nimport { LocationInput, LocationStore } from '../service/types';\nimport {\n ANNOTATION_ORIGIN_LOCATION,\n CompoundEntityRef,\n parseLocationRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n CatalogScmEvent,\n CatalogScmEventsService,\n} from '@backstage/plugin-catalog-node/alpha';\nimport { chunk, uniqBy } from 'lodash';\nimport parseGitUrl, { type GitUrl } from 'git-url-parse';\nimport { ScmEventHandlingConfig } from '../util/readScmEventHandlingConfig';\nimport {\n FilterPredicate,\n FilterPredicateValue,\n} from '@backstage/filter-predicates';\n\nexport class DefaultLocationStore implements LocationStore, EntityProvider {\n private _connection: EntityProviderConnection | undefined;\n private readonly db: Knex;\n private readonly scmEvents: CatalogScmEventsService;\n private readonly scmEventHandlingConfig: ScmEventHandlingConfig;\n\n constructor(\n db: Knex,\n scmEvents: CatalogScmEventsService,\n scmEventHandlingConfig: ScmEventHandlingConfig,\n ) {\n this.db = db;\n this.scmEvents = scmEvents;\n this.scmEventHandlingConfig = scmEventHandlingConfig;\n }\n\n getProviderName(): string {\n return 'DefaultLocationStore';\n }\n\n async createLocation(\n input: LocationInput,\n options?: {\n onConflict?: 'refresh' | 'reject';\n },\n ): Promise<Location> {\n let existed = false;\n\n const location = await this.db.transaction(async tx => {\n // Attempt to find a previous location matching the input\n const previousLocations = await this.locations(tx);\n // TODO: when location id's are a compilation of input target we can remove this full\n // lookup of locations first and just grab the by that instead.\n const previousLocation = previousLocations.find(\n l => input.type === l.type && input.target === l.target,\n );\n if (previousLocation) {\n if (options?.onConflict === 'refresh') {\n existed = true;\n return previousLocation;\n }\n throw new ConflictError(\n `Location ${input.type}:${input.target} already exists`,\n );\n }\n\n const inner: DbLocationsRow = {\n id: uuid(),\n type: input.type,\n target: input.target,\n location_entity_ref: computeLocationEntityRef(input.type, input.target),\n };\n\n await tx<DbLocationsRow>('locations').insert(inner);\n\n return inner;\n });\n\n // Always upsert the entity, even if the location already existed, to\n // recover from cases where the entity was inadvertently deleted.\n const entity = locationSpecToLocationEntity({\n location,\n locationEntityRef: location.location_entity_ref,\n });\n await this.connection.applyMutation({\n type: 'delta',\n added: [{ entity, locationKey: getEntityLocationRef(entity) }],\n removed: [],\n });\n\n if (existed) {\n // This is the \"onConflict refresh\" case, where a re-registration safely\n // tries to recover from a bad state.\n const entityRef = stringifyEntityRef(entity);\n await this.db<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRef })\n .update({\n next_update_at: this.db.fn.now(),\n result_hash: '',\n });\n }\n\n return {\n id: location.id,\n type: location.type,\n target: location.target,\n entityRef: location.location_entity_ref,\n };\n }\n\n async listLocations(): Promise<Location[]> {\n return (await this.locations()).map(\n ({ id, type, target, location_entity_ref }) => ({\n id,\n type,\n target,\n entityRef: location_entity_ref,\n }),\n );\n }\n\n async queryLocations(options: {\n limit: number;\n afterId?: string;\n query?: FilterPredicate;\n }): Promise<{ items: Location[]; totalItems: number }> {\n let itemsQuery = this.db<DbLocationsRow>('locations').whereNot(\n 'type',\n 'bootstrap',\n );\n\n if (options.query) {\n itemsQuery = applyLocationFilterToQuery(\n this.db.client.config.client,\n itemsQuery,\n options.query,\n );\n }\n\n const countQuery = itemsQuery.clone().count('*', { as: 'count' });\n\n itemsQuery = itemsQuery.orderBy('id', 'asc');\n if (options.afterId !== undefined) {\n itemsQuery = itemsQuery.where('id', '>', options.afterId);\n }\n if (options.limit !== undefined) {\n itemsQuery = itemsQuery.limit(options.limit);\n }\n\n const [items, [{ count }]] = await Promise.all([itemsQuery, countQuery]);\n\n return {\n items: items.map(item => ({\n id: item.id,\n target: item.target,\n type: item.type,\n entityRef: item.location_entity_ref,\n })),\n totalItems: Number(count),\n };\n }\n\n async getLocation(id: string): Promise<Location> {\n const items = await this.db<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!items.length) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n const { id: rowId, type, target, location_entity_ref } = items[0];\n return { id: rowId, type, target, entityRef: location_entity_ref };\n }\n\n async updateLocation(id: string, location: LocationInput): Promise<Location> {\n if (!this.connection) {\n throw new Error('location store is not initialized');\n }\n\n // MySQL doesn't support UPDATE ... RETURNING. MySQL also reports 0 affected\n // rows when the new values are identical to the old ones, so we can't rely\n // on the row count to detect existence. Instead we SELECT to check existence\n // first and then UPDATE inside a transaction.\n let row: DbLocationsRow | undefined;\n if (this.db.client.config.client.includes('mysql')) {\n await this.db.transaction(async tx => {\n [row] = await tx<DbLocationsRow>('locations').where({ id }).select();\n if (!row) {\n return;\n }\n\n const [conflict] = await tx<DbLocationsRow>('locations')\n .where({ type: location.type, target: location.target })\n .whereNot({ id })\n .select();\n if (conflict) {\n throw new ConflictError(\n `Location ${location.type}:${location.target} already exists`,\n );\n }\n\n await tx<DbLocationsRow>('locations')\n .where({ id })\n .update({ type: location.type, target: location.target });\n row = { ...row, type: location.type, target: location.target };\n });\n } else {\n await this.db.transaction(async tx => {\n const [conflict] = await tx<DbLocationsRow>('locations')\n .where({ type: location.type, target: location.target })\n .whereNot({ id })\n .select();\n if (conflict) {\n throw new ConflictError(\n `Location ${location.type}:${location.target} already exists`,\n );\n }\n\n [row] = await tx<DbLocationsRow>('locations')\n .where({ id })\n .update({ type: location.type, target: location.target })\n .returning('*');\n });\n }\n\n if (!row) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n\n const entity = locationSpecToLocationEntity({\n location: row,\n locationEntityRef: row.location_entity_ref,\n });\n\n await this.connection.applyMutation({\n type: 'delta',\n added: [{ entity, locationKey: getEntityLocationRef(entity) }],\n removed: [],\n });\n\n return {\n id: row.id,\n type: row.type,\n target: row.target,\n entityRef: row.location_entity_ref,\n };\n }\n\n async deleteLocation(id: string): Promise<void> {\n if (!this.connection) {\n throw new Error('location store is not initialized');\n }\n\n const deleted = await this.db.transaction(async tx => {\n const [location] = await tx<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!location) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n\n await tx<DbLocationsRow>('locations').where({ id }).del();\n return location;\n });\n const entity = locationSpecToLocationEntity({\n location: deleted,\n locationEntityRef: deleted.location_entity_ref,\n });\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: [{ entity, locationKey: getEntityLocationRef(entity) }],\n });\n }\n\n async getLocationByEntity(entityRef: CompoundEntityRef): Promise<Location> {\n const entityRefString = stringifyEntityRef(entityRef);\n\n const [entityRow] = await this.db<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRefString })\n .select('entity_id')\n .limit(1);\n if (!entityRow) {\n throw new NotFoundError(`found no entity for ref ${entityRefString}`);\n }\n\n const [searchRow] = await this.db<DbSearchRow>('search')\n .where({\n entity_id: entityRow.entity_id,\n key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`,\n })\n .select('original_value')\n .limit(1);\n if (!searchRow?.original_value) {\n throw new NotFoundError(\n `found no origin annotation for ref ${entityRefString}`,\n );\n }\n\n const { type, target } = parseLocationRef(searchRow.original_value);\n const [locationRow] = await this.db<DbLocationsRow>('locations')\n .where({ type, target })\n .select()\n .limit(1);\n\n if (!locationRow) {\n throw new NotFoundError(\n `Found no location with type ${type} and target ${target}`,\n );\n }\n\n return {\n id: locationRow.id,\n type: locationRow.type,\n target: locationRow.target,\n entityRef: locationRow.location_entity_ref,\n };\n }\n\n private get connection(): EntityProviderConnection {\n if (!this._connection) {\n throw new Error('location store is not initialized');\n }\n\n return this._connection;\n }\n\n async connect(connection: EntityProviderConnection): Promise<void> {\n this._connection = connection;\n\n const locations = await this.locations();\n\n const entities = locations.map(location => {\n const entity = locationSpecToLocationEntity({\n location,\n locationEntityRef: location.location_entity_ref,\n });\n return { entity, locationKey: getEntityLocationRef(entity) };\n });\n\n await this.connection.applyMutation({\n type: 'full',\n entities,\n });\n\n if (\n this.scmEventHandlingConfig.unregister ||\n this.scmEventHandlingConfig.move\n ) {\n this.scmEvents.subscribe({ onEvents: this.#onScmEvents.bind(this) });\n }\n }\n\n private async locations(\n dbOrTx: Knex.Transaction | Knex = this.db,\n ): Promise<DbLocationsRow[]> {\n const locations = await dbOrTx<DbLocationsRow>('locations').select();\n return (\n locations\n // TODO(blam): We should create a mutation to remove this location for everyone\n // eventually when it's all done and dusted\n .filter(({ type }) => type !== 'bootstrap')\n );\n }\n\n // #region SCM event handling\n\n async #onScmEvents(events: CatalogScmEvent[]): Promise<void> {\n const exactLocationsToDelete = new Set<string>();\n const locationPrefixesToDelete = new Set<string>();\n const exactLocationsToCreate = new Set<string>();\n const locationPrefixesToMove = new Map<string, string>();\n\n for (const event of events) {\n if (\n event.type === 'location.deleted' &&\n this.scmEventHandlingConfig.unregister\n ) {\n exactLocationsToDelete.add(event.url);\n } else if (\n event.type === 'location.moved' &&\n this.scmEventHandlingConfig.move\n ) {\n // Since Location entities are named after their target URL, these\n // unfortunately have to be translated into deletion and creation\n exactLocationsToDelete.add(event.fromUrl);\n exactLocationsToCreate.add(event.toUrl);\n } else if (\n event.type === 'repository.deleted' &&\n this.scmEventHandlingConfig.unregister\n ) {\n locationPrefixesToDelete.add(event.url);\n } else if (\n event.type === 'repository.moved' &&\n this.scmEventHandlingConfig.move\n ) {\n // These also have to be handled with deletions and creations\n locationPrefixesToMove.set(event.fromUrl, event.toUrl);\n }\n }\n\n if (exactLocationsToDelete.size > 0) {\n const count = await this.#deleteLocationsByExactUrl(\n exactLocationsToDelete,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'delete',\n });\n }\n if (locationPrefixesToDelete.size > 0) {\n const count = await this.#deleteLocationsByUrlPrefix(\n locationPrefixesToDelete,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'delete',\n });\n }\n if (exactLocationsToCreate.size > 0) {\n const count = await this.#createLocationsByExactUrl(\n exactLocationsToCreate,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'create',\n });\n }\n if (locationPrefixesToMove.size > 0) {\n const count = await this.#moveLocationsByUrlPrefix(\n locationPrefixesToMove,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'move',\n });\n }\n }\n\n async #createLocationsByExactUrl(urls: Iterable<string>): Promise<number> {\n let count = 0;\n\n for (const batch of chunk(Array.from(urls), 100)) {\n const existingUrls = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n .whereIn('target', batch)\n .select()\n .then(rows => new Set(rows.map(row => row.target)));\n\n const newLocations = batch\n .filter(url => !existingUrls.has(url))\n .map(url => ({\n id: uuid(),\n type: 'url',\n target: url,\n location_entity_ref: computeLocationEntityRef('url', url),\n }));\n\n if (newLocations.length) {\n await this.db<DbLocationsRow>('locations').insert(newLocations);\n\n await this.connection.applyMutation({\n type: 'delta',\n added: newLocations.map(location => {\n const entity = locationSpecToLocationEntity({\n location,\n locationEntityRef: location.location_entity_ref,\n });\n return { entity, locationKey: getEntityLocationRef(entity) };\n }),\n removed: [],\n });\n\n count += newLocations.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocationsByExactUrl(urls: Iterable<string>): Promise<number> {\n let count = 0;\n\n for (const batch of chunk(Array.from(urls), 100)) {\n const rows = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n .whereIn('target', batch)\n .select();\n\n if (rows.length) {\n await this.db<DbLocationsRow>('locations')\n .whereIn(\n 'id',\n rows.map(row => row.id),\n )\n .delete();\n\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: rows.map(row => ({\n entity: locationSpecToLocationEntity({\n location: row,\n locationEntityRef: row.location_entity_ref,\n }),\n })),\n });\n\n count += rows.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocationsByUrlPrefix(urls: Iterable<string>): Promise<number> {\n const matches = await this.#findLocationsByPrefixOrExactMatch(urls);\n if (matches.length) {\n await this.#deleteLocations(matches.map(l => l.row));\n }\n\n return matches.length;\n }\n\n async #moveLocationsByUrlPrefix(\n urlPrefixes: Map<string, string>,\n ): Promise<number> {\n let count = 0;\n\n for (const [fromPrefix, toPrefix] of urlPrefixes) {\n if (fromPrefix === toPrefix) {\n continue;\n }\n\n if (fromPrefix.match(/[?#]/) || toPrefix.match(/[?#]/)) {\n // TODO(freben): We can't yet support complex URL locations where e.g.\n // the path can be anywhere in the URL including in the query or hash\n // part. The code below currently assumes that we can use simple\n // substring operations.\n continue;\n }\n\n const matches = await this.#findLocationsByPrefixOrExactMatch([\n fromPrefix,\n ]);\n if (matches.length) {\n await this.#deleteLocations(matches.map(m => m.row));\n\n await this.#createLocationsByExactUrl(\n matches.map(m => {\n const remainder = m.row.target\n .slice(fromPrefix.length)\n .replace(/^\\/+/, '');\n if (!remainder) {\n return toPrefix;\n }\n return `${toPrefix.replace(/\\/+$/, '')}/${remainder}`;\n }),\n );\n\n count += matches.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocations(rows: DbLocationsRow[]): Promise<void> {\n // Delete the location table entries (in chunks so as not to overload the\n // knex query builder)\n for (const ids of chunk(\n rows.map(l => l.id),\n 100,\n )) {\n await this.db<DbLocationsRow>('locations').whereIn('id', ids).delete();\n }\n\n // Delete the corresponding Location kind entities (this is efficiently\n // chunked internally in the catalog)\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: rows.map(l => ({\n entity: locationSpecToLocationEntity({\n location: l,\n locationEntityRef: l.location_entity_ref,\n }),\n })),\n });\n }\n\n /**\n * Given a \"base\" URL prefix, find all locations that are for paths at or\n * below it.\n *\n * For example, given a base URL prefix of\n * \"https://github.com/backstage/backstage/blob/master/plugins\", it will match\n * locations inside the plugins directory, and nowhere else.\n */\n async #findLocationsByPrefixOrExactMatch(\n urls: Iterable<string>,\n ): Promise<Array<{ row: DbLocationsRow; parsed: GitUrl }>> {\n const result = new Array<{ row: DbLocationsRow; parsed: GitUrl }>();\n\n for (const url of urls) {\n let base: GitUrl;\n try {\n base = parseGitUrl(url);\n } catch (error) {\n throw new Error(`Invalid URL prefix, could not parse: ${url}`);\n }\n\n if (!base.owner || !base.name) {\n throw new Error(\n `Invalid URL prefix, missing owner or repository: ${url}`,\n );\n }\n\n const pathPrefix =\n base.filepath === '' || base.filepath.endsWith('/')\n ? base.filepath\n : `${base.filepath}/`;\n\n const rows = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n // Initial rough pruning to not have to go through them all\n .where('target', 'like', `%${base.owner}%`)\n .where('target', 'like', `%${base.name}%`)\n .select();\n\n result.push(\n ...rows.flatMap(row => {\n try {\n // We do this pretty explicit set of checks because we want to support\n // providers that have a URL format where the path isn't necessarily at\n // the end of the URL string (e.g. in the query part). Some of these may\n // be empty strings etc, but that's fine as long as they parse to the\n // same thing as above.\n const candidate = parseGitUrl(row.target);\n\n if (\n candidate.protocol === base.protocol &&\n candidate.resource === base.resource &&\n candidate.port === base.port &&\n candidate.organization === base.organization &&\n candidate.owner === base.owner &&\n candidate.name === base.name &&\n // If the base has no ref (for example didn't have the \"/blob/master\"\n // part and therefore targeted an entire repository) then we match any\n // ref below that\n (!base.ref || candidate.ref === base.ref) &&\n // Match both on exact equality and any subpath with a slash between\n (candidate.filepath === base.filepath ||\n candidate.filepath.startsWith(pathPrefix))\n ) {\n return [{ row, parsed: candidate }];\n }\n return [];\n } catch {\n return [];\n }\n }),\n );\n }\n\n return uniqBy(result, entry => entry.row.id);\n }\n\n // #endregion\n}\n\n/**\n * Recursively builds up the SQL expression corresponding to the given filter\n * predicate.\n *\n * @remarks\n *\n * Design note: The code prefers to let the SQL engine achieve case\n * insensitivity. We could attempt to use `.toUpperCase` etc on the client\n * side, but that would only work for the values being passed in, not the column\n * side of the expression. If we let the database perform UPPER on both, we know\n * that they will always be locale consistent etc as well.\n *\n * This does come at a runtime cost. However, the data set is typically rather\n * small in the grand scheme of things, and we can add the proper indices in the\n * future if needed. At this point I considered it not worth the effort.\n */\nfunction applyLocationFilterToQuery(\n clientType: string,\n inputQuery: Knex.QueryBuilder,\n query: FilterPredicate,\n): Knex.QueryBuilder {\n let result = inputQuery;\n\n if (!query || typeof query !== 'object' || Array.isArray(query)) {\n throw new InputError('Invalid filter predicate, expected an object');\n }\n\n if ('$all' in query) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (query.$all.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n return result.where(outer => {\n for (const subQuery of query.$all) {\n outer.andWhere(inner => {\n applyLocationFilterToQuery(clientType, inner, subQuery);\n });\n }\n });\n }\n\n if ('$any' in query) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (query.$any.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n return result.where(outer => {\n for (const subQuery of query.$any) {\n outer.orWhere(inner => {\n applyLocationFilterToQuery(clientType, inner, subQuery);\n });\n }\n });\n }\n\n if ('$not' in query) {\n return result.whereNot(inner => {\n applyLocationFilterToQuery(clientType, inner, query.$not);\n });\n }\n\n const entries = Object.entries(query);\n const keys = entries.map(e => e[0]);\n if (keys.some(k => k.startsWith('$'))) {\n throw new InputError(\n `Invalid filter predicate, unknown logic operator '${keys.join(', ')}'`,\n );\n }\n\n for (const [keyAnyCase, value] of entries) {\n const key = keyAnyCase.toLocaleLowerCase('en-US');\n if (!['id', 'type', 'target', 'entityref'].includes(key)) {\n throw new InputError(\n `Invalid filter predicate, expected key to be 'id', 'type', 'target', or 'entityRef', got '${keyAnyCase}'`,\n );\n }\n\n // Map the API field name to the underlying column name\n const column = key === 'entityref' ? 'location_entity_ref' : key;\n result = applyFilterValueToQuery(clientType, result, column, value);\n }\n\n return result;\n}\n\nfunction applyFilterValueToQuery(\n clientType: string,\n result: Knex.QueryBuilder,\n key: string,\n value: FilterPredicateValue,\n): Knex.QueryBuilder {\n // Is it a primitive value?\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n if (clientType === 'pg') {\n return result.whereRaw(`UPPER(??::text) = UPPER(?::text)`, [key, value]);\n }\n\n if (clientType.includes('mysql')) {\n return result.whereRaw(\n `UPPER(CAST(?? AS CHAR)) = UPPER(CAST(? AS CHAR))`,\n [key, value],\n );\n }\n\n return result.whereRaw(`UPPER(??) = UPPER(?)`, [key, value]);\n }\n\n // Is it a matcher object?\n if (typeof value === 'object') {\n if (!value || Array.isArray(value)) {\n throw new InputError(\n `Invalid filter predicate, got unknown matcher object '${JSON.stringify(\n value,\n )}'`,\n );\n }\n\n // Technically existence checks do not make much sense in the context of\n // this table at the time of writing (values are always present), but\n // there's nothing gained by prohibiting it.\n if ('$exists' in value) {\n return value.$exists ? result.whereNotNull(key) : result.whereNull(key);\n }\n\n if ('$in' in value) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (value.$in.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n // The id is matched with plain equality; it's of UUID type and case\n // insensitivity does not apply.\n if (key === 'id') {\n return result.whereIn(key, value.$in);\n }\n\n if (clientType === 'pg') {\n const rhs = value.$in.map(() => 'UPPER(?::text)').join(', ');\n return result.whereRaw(`UPPER(??::text) IN (${rhs})`, [\n key,\n ...value.$in,\n ]);\n }\n\n if (clientType.includes('mysql')) {\n const rhs = value.$in.map(() => 'UPPER(CAST(? AS CHAR))').join(', ');\n return result.whereRaw(`UPPER(CAST(?? AS CHAR)) IN (${rhs})`, [\n key,\n ...value.$in,\n ]);\n }\n\n const rhs = value.$in.map(() => 'UPPER(?)').join(', ');\n return result.whereRaw(`UPPER(??) IN (${rhs})`, [key, ...value.$in]);\n }\n\n if ('$hasPrefix' in value) {\n const escaped = value.$hasPrefix.replace(/([\\\\%_])/g, '\\\\$1');\n\n if (clientType === 'pg') {\n return result.whereRaw(\"?? ilike ? escape '\\\\'\", [key, `${escaped}%`]);\n }\n\n if (clientType.includes('mysql')) {\n return result.whereRaw(\"UPPER(??) like UPPER(?) escape '\\\\\\\\'\", [\n key,\n `${escaped}%`,\n ]);\n }\n\n return result.whereRaw(\"UPPER(??) like UPPER(?) escape '\\\\'\", [\n key,\n `${escaped}%`,\n ]);\n }\n\n // There are no array shaped values for location queries, so we just always\n // fail here\n if ('$contains' in value) {\n return result.whereRaw('1 = 0');\n }\n\n throw new InputError(\n `Invalid filter predicate, got unknown matcher object '${JSON.stringify(\n value,\n )}'`,\n );\n }\n\n throw new InputError(\n `Invalid filter predicate, expected value to be a primitive value or a matcher object, got '${typeof value}'`,\n );\n}\n"],"names":["ConflictError","uuid","computeLocationEntityRef","locationSpecToLocationEntity","getEntityLocationRef","stringifyEntityRef","NotFoundError","ANNOTATION_ORIGIN_LOCATION","parseLocationRef","chunk","parseGitUrl","uniqBy","InputError","rhs"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultLocationStore.cjs.js","sources":["../../src/providers/DefaultLocationStore.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Location } from '@backstage/catalog-client';\nimport { ConflictError, InputError, NotFoundError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { randomUUID as uuid } from 'node:crypto';\nimport {\n DbLocationsRow,\n DbRefreshStateRow,\n DbSearchRow,\n} from '../database/tables';\nimport { getEntityLocationRef } from '../processing/util';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\nimport {\n computeLocationEntityRef,\n locationSpecToLocationEntity,\n} from '../util/conversion';\nimport { LocationInput, LocationStore } from '../service/types';\nimport {\n ANNOTATION_ORIGIN_LOCATION,\n CompoundEntityRef,\n parseLocationRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n CatalogScmEvent,\n CatalogScmEventsService,\n} from '@backstage/plugin-catalog-node/alpha';\nimport { chunk, uniqBy } from 'lodash';\nimport parseGitUrl, { type GitUrl } from 'git-url-parse';\nimport { ScmEventHandlingConfig } from '../util/readScmEventHandlingConfig';\nimport {\n FilterPredicate,\n FilterPredicateValue,\n} from '@backstage/filter-predicates';\n\nexport class DefaultLocationStore implements LocationStore, EntityProvider {\n private _connection: EntityProviderConnection | undefined;\n private readonly db: Knex;\n private readonly scmEvents: CatalogScmEventsService;\n private readonly scmEventHandlingConfig: ScmEventHandlingConfig;\n\n constructor(\n db: Knex,\n scmEvents: CatalogScmEventsService,\n scmEventHandlingConfig: ScmEventHandlingConfig,\n ) {\n this.db = db;\n this.scmEvents = scmEvents;\n this.scmEventHandlingConfig = scmEventHandlingConfig;\n }\n\n getProviderName(): string {\n return 'DefaultLocationStore';\n }\n\n async createLocation(\n input: LocationInput,\n options?: {\n onConflict?: 'refresh' | 'reject';\n },\n ): Promise<Location> {\n let existed = false;\n\n const location = await this.db.transaction(async tx => {\n // Attempt to find a previous location matching the input\n const previousLocations = await this.locations(tx);\n // TODO: when location id's are a compilation of input target we can remove this full\n // lookup of locations first and just grab the by that instead.\n const previousLocation = previousLocations.find(\n l => input.type === l.type && input.target === l.target,\n );\n if (previousLocation) {\n if (options?.onConflict === 'refresh') {\n existed = true;\n return previousLocation;\n }\n throw new ConflictError(\n `Location ${input.type}:${input.target} already exists`,\n );\n }\n\n const inner: DbLocationsRow = {\n id: uuid(),\n type: input.type,\n target: input.target,\n location_entity_ref: computeLocationEntityRef(input.type, input.target),\n };\n\n await tx<DbLocationsRow>('locations').insert(inner);\n\n return inner;\n });\n\n // Always upsert the entity, even if the location already existed, to\n // recover from cases where the entity was inadvertently deleted.\n const entity = locationSpecToLocationEntity({\n location,\n locationEntityRef: location.location_entity_ref,\n });\n await this.connection.applyMutation({\n type: 'delta',\n added: [{ entity, locationKey: getEntityLocationRef(entity) }],\n removed: [],\n });\n\n if (existed) {\n // This is the \"onConflict refresh\" case, where a re-registration safely\n // tries to recover from a bad state.\n const entityRef = stringifyEntityRef(entity);\n await this.db<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRef })\n .update({\n next_update_at: this.db.fn.now(),\n result_hash: '',\n });\n }\n\n return {\n id: location.id,\n type: location.type,\n target: location.target,\n entityRef: location.location_entity_ref,\n };\n }\n\n async listLocations(): Promise<Location[]> {\n return (await this.locations()).map(\n ({ id, type, target, location_entity_ref }) => ({\n id,\n type,\n target,\n entityRef: location_entity_ref,\n }),\n );\n }\n\n async queryLocations(options: {\n limit: number;\n afterId?: string;\n query?: FilterPredicate;\n }): Promise<{ items: Location[]; totalItems: number }> {\n let itemsQuery = this.db<DbLocationsRow>('locations').whereNot(\n 'type',\n 'bootstrap',\n );\n\n if (options.query) {\n itemsQuery = applyLocationFilterToQuery(\n this.db.client.config.client,\n itemsQuery,\n options.query,\n );\n }\n\n const countQuery = itemsQuery.clone().count('*', { as: 'count' });\n\n itemsQuery = itemsQuery.orderBy('id', 'asc');\n if (options.afterId !== undefined) {\n itemsQuery = itemsQuery.where('id', '>', options.afterId);\n }\n if (options.limit !== undefined) {\n itemsQuery = itemsQuery.limit(options.limit);\n }\n\n const [items, [{ count }]] = await Promise.all([itemsQuery, countQuery]);\n\n return {\n items: items.map(item => ({\n id: item.id,\n target: item.target,\n type: item.type,\n entityRef: item.location_entity_ref,\n })),\n totalItems: Number(count),\n };\n }\n\n async getLocation(id: string): Promise<Location> {\n const items = await this.db<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!items.length) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n const { id: rowId, type, target, location_entity_ref } = items[0];\n return { id: rowId, type, target, entityRef: location_entity_ref };\n }\n\n async updateLocation(id: string, location: LocationInput): Promise<Location> {\n if (!this.connection) {\n throw new Error('location store is not initialized');\n }\n\n // MySQL doesn't support UPDATE ... RETURNING. MySQL also reports 0 affected\n // rows when the new values are identical to the old ones, so we can't rely\n // on the row count to detect existence. Instead we SELECT to check existence\n // first and then UPDATE inside a transaction.\n let row: DbLocationsRow | undefined;\n if (this.db.client.config.client.includes('mysql')) {\n await this.db.transaction(async tx => {\n [row] = await tx<DbLocationsRow>('locations').where({ id }).select();\n if (!row) {\n return;\n }\n\n const [conflict] = await tx<DbLocationsRow>('locations')\n .where({ type: location.type, target: location.target })\n .whereNot({ id })\n .select();\n if (conflict) {\n throw new ConflictError(\n `Location ${location.type}:${location.target} already exists`,\n );\n }\n\n await tx<DbLocationsRow>('locations')\n .where({ id })\n .update({ type: location.type, target: location.target });\n row = { ...row, type: location.type, target: location.target };\n });\n } else {\n await this.db.transaction(async tx => {\n const [conflict] = await tx<DbLocationsRow>('locations')\n .where({ type: location.type, target: location.target })\n .whereNot({ id })\n .select();\n if (conflict) {\n throw new ConflictError(\n `Location ${location.type}:${location.target} already exists`,\n );\n }\n\n [row] = await tx<DbLocationsRow>('locations')\n .where({ id })\n .update({ type: location.type, target: location.target })\n .returning('*');\n });\n }\n\n if (!row) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n\n const entity = locationSpecToLocationEntity({\n location: row,\n locationEntityRef: row.location_entity_ref,\n });\n\n await this.connection.applyMutation({\n type: 'delta',\n added: [{ entity, locationKey: getEntityLocationRef(entity) }],\n removed: [],\n });\n\n return {\n id: row.id,\n type: row.type,\n target: row.target,\n entityRef: row.location_entity_ref,\n };\n }\n\n async deleteLocation(id: string): Promise<void> {\n if (!this.connection) {\n throw new Error('location store is not initialized');\n }\n\n const deleted = await this.db.transaction(async tx => {\n const [location] = await tx<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!location) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n\n await tx<DbLocationsRow>('locations').where({ id }).del();\n return location;\n });\n const entity = locationSpecToLocationEntity({\n location: deleted,\n locationEntityRef: deleted.location_entity_ref,\n });\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: [{ entity, locationKey: getEntityLocationRef(entity) }],\n });\n }\n\n async getLocationByEntity(entityRef: CompoundEntityRef): Promise<Location> {\n const entityRefString = stringifyEntityRef(entityRef);\n\n const [entityRow] = await this.db<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRefString })\n .select('entity_id')\n .limit(1);\n if (!entityRow) {\n throw new NotFoundError(`found no entity for ref ${entityRefString}`);\n }\n\n const [searchRow] = await this.db<DbSearchRow>('search')\n .where({\n entity_id: entityRow.entity_id,\n key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`,\n })\n .select('original_value')\n .limit(1);\n if (!searchRow?.original_value) {\n throw new NotFoundError(\n `found no origin annotation for ref ${entityRefString}`,\n );\n }\n\n const { type, target } = parseLocationRef(searchRow.original_value);\n const [locationRow] = await this.db<DbLocationsRow>('locations')\n .where({ type, target })\n .select()\n .limit(1);\n\n if (!locationRow) {\n throw new NotFoundError(\n `Found no location with type ${type} and target ${target}`,\n );\n }\n\n return {\n id: locationRow.id,\n type: locationRow.type,\n target: locationRow.target,\n entityRef: locationRow.location_entity_ref,\n };\n }\n\n private get connection(): EntityProviderConnection {\n if (!this._connection) {\n throw new Error('location store is not initialized');\n }\n\n return this._connection;\n }\n\n async connect(connection: EntityProviderConnection): Promise<void> {\n this._connection = connection;\n\n const locations = await this.locations();\n\n const entities = locations.map(location => {\n const entity = locationSpecToLocationEntity({\n location,\n locationEntityRef: location.location_entity_ref,\n });\n return { entity, locationKey: getEntityLocationRef(entity) };\n });\n\n await this.connection.applyMutation({\n type: 'full',\n entities,\n });\n\n if (\n this.scmEventHandlingConfig.unregister ||\n this.scmEventHandlingConfig.move\n ) {\n this.scmEvents.subscribe({ onEvents: this.#onScmEvents.bind(this) });\n }\n }\n\n private async locations(\n dbOrTx: Knex.Transaction | Knex = this.db,\n ): Promise<DbLocationsRow[]> {\n const locations = await dbOrTx<DbLocationsRow>('locations').select();\n return (\n locations\n // TODO(blam): We should create a mutation to remove this location for everyone\n // eventually when it's all done and dusted\n .filter(({ type }) => type !== 'bootstrap')\n );\n }\n\n // #region SCM event handling\n\n async #onScmEvents(events: CatalogScmEvent[]): Promise<void> {\n const exactLocationsToDelete = new Set<string>();\n const locationPrefixesToDelete = new Set<string>();\n const exactLocationsToCreate = new Set<string>();\n const locationPrefixesToMove = new Map<string, string>();\n\n for (const event of events) {\n if (\n event.type === 'location.deleted' &&\n this.scmEventHandlingConfig.unregister\n ) {\n exactLocationsToDelete.add(event.url);\n } else if (\n event.type === 'location.moved' &&\n this.scmEventHandlingConfig.move\n ) {\n // Since Location entities are named after their target URL, these\n // unfortunately have to be translated into deletion and creation\n exactLocationsToDelete.add(event.fromUrl);\n exactLocationsToCreate.add(event.toUrl);\n } else if (\n event.type === 'repository.deleted' &&\n this.scmEventHandlingConfig.unregister\n ) {\n locationPrefixesToDelete.add(event.url);\n } else if (\n event.type === 'repository.moved' &&\n this.scmEventHandlingConfig.move\n ) {\n // These also have to be handled with deletions and creations\n locationPrefixesToMove.set(event.fromUrl, event.toUrl);\n }\n }\n\n if (exactLocationsToDelete.size > 0) {\n const count = await this.#deleteLocationsByExactUrl(\n exactLocationsToDelete,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'delete',\n });\n }\n if (locationPrefixesToDelete.size > 0) {\n const count = await this.#deleteLocationsByUrlPrefix(\n locationPrefixesToDelete,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'delete',\n });\n }\n if (exactLocationsToCreate.size > 0) {\n const count = await this.#createLocationsByExactUrl(\n exactLocationsToCreate,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'create',\n });\n }\n if (locationPrefixesToMove.size > 0) {\n const count = await this.#moveLocationsByUrlPrefix(\n locationPrefixesToMove,\n );\n this.scmEvents.markEventActionTaken({\n count,\n action: 'move',\n });\n }\n }\n\n async #createLocationsByExactUrl(urls: Iterable<string>): Promise<number> {\n let count = 0;\n\n for (const batch of chunk(Array.from(urls), 100)) {\n const existingUrls = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n .whereIn('target', batch)\n .select()\n .then(rows => new Set(rows.map(row => row.target)));\n\n const newLocations = batch\n .filter(url => !existingUrls.has(url))\n .map(url => ({\n id: uuid(),\n type: 'url',\n target: url,\n location_entity_ref: computeLocationEntityRef('url', url),\n }));\n\n if (newLocations.length) {\n await this.db<DbLocationsRow>('locations').insert(newLocations);\n\n await this.connection.applyMutation({\n type: 'delta',\n added: newLocations.map(location => {\n const entity = locationSpecToLocationEntity({\n location,\n locationEntityRef: location.location_entity_ref,\n });\n return { entity, locationKey: getEntityLocationRef(entity) };\n }),\n removed: [],\n });\n\n count += newLocations.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocationsByExactUrl(urls: Iterable<string>): Promise<number> {\n let count = 0;\n\n for (const batch of chunk(Array.from(urls), 100)) {\n const rows = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n .whereIn('target', batch)\n .select();\n\n if (rows.length) {\n await this.db<DbLocationsRow>('locations')\n .whereIn(\n 'id',\n rows.map(row => row.id),\n )\n .delete();\n\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: rows.map(row => ({\n entity: locationSpecToLocationEntity({\n location: row,\n locationEntityRef: row.location_entity_ref,\n }),\n })),\n });\n\n count += rows.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocationsByUrlPrefix(urls: Iterable<string>): Promise<number> {\n const matches = await this.#findLocationsByPrefixOrExactMatch(urls);\n if (matches.length) {\n await this.#deleteLocations(matches.map(l => l.row));\n }\n\n return matches.length;\n }\n\n async #moveLocationsByUrlPrefix(\n urlPrefixes: Map<string, string>,\n ): Promise<number> {\n let count = 0;\n\n for (const [fromPrefix, toPrefix] of urlPrefixes) {\n if (fromPrefix === toPrefix) {\n continue;\n }\n\n if (fromPrefix.match(/[?#]/) || toPrefix.match(/[?#]/)) {\n // TODO(freben): We can't yet support complex URL locations where e.g.\n // the path can be anywhere in the URL including in the query or hash\n // part. The code below currently assumes that we can use simple\n // substring operations.\n continue;\n }\n\n const matches = await this.#findLocationsByPrefixOrExactMatch([\n fromPrefix,\n ]);\n if (matches.length) {\n await this.#deleteLocations(matches.map(m => m.row));\n\n await this.#createLocationsByExactUrl(\n matches.map(m => {\n const remainder = m.row.target\n .slice(fromPrefix.length)\n .replace(/^\\/+/, '');\n if (!remainder) {\n return toPrefix;\n }\n return `${toPrefix.replace(/\\/+$/, '')}/${remainder}`;\n }),\n );\n\n count += matches.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocations(rows: DbLocationsRow[]): Promise<void> {\n // Delete the location table entries (in chunks so as not to overload the\n // knex query builder)\n for (const ids of chunk(\n rows.map(l => l.id),\n 100,\n )) {\n await this.db<DbLocationsRow>('locations').whereIn('id', ids).delete();\n }\n\n // Delete the corresponding Location kind entities (this is efficiently\n // chunked internally in the catalog)\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: rows.map(l => ({\n entity: locationSpecToLocationEntity({\n location: l,\n locationEntityRef: l.location_entity_ref,\n }),\n })),\n });\n }\n\n /**\n * Given a \"base\" URL prefix, find all locations that are for paths at or\n * below it.\n *\n * For example, given a base URL prefix of\n * \"https://github.com/backstage/backstage/blob/master/plugins\", it will match\n * locations inside the plugins directory, and nowhere else.\n */\n async #findLocationsByPrefixOrExactMatch(\n urls: Iterable<string>,\n ): Promise<Array<{ row: DbLocationsRow; parsed: GitUrl }>> {\n const result = new Array<{ row: DbLocationsRow; parsed: GitUrl }>();\n\n for (const url of urls) {\n let base: GitUrl;\n try {\n base = parseGitUrl(url);\n } catch (error) {\n throw new Error(`Invalid URL prefix, could not parse: ${url}`);\n }\n\n if (!base.owner || !base.name) {\n throw new Error(\n `Invalid URL prefix, missing owner or repository: ${url}`,\n );\n }\n\n const pathPrefix =\n base.filepath === '' || base.filepath.endsWith('/')\n ? base.filepath\n : `${base.filepath}/`;\n\n const rows = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n // Initial rough pruning to not have to go through them all\n .where('target', 'like', `%${base.owner}%`)\n .where('target', 'like', `%${base.name}%`)\n .select();\n\n result.push(\n ...rows.flatMap(row => {\n try {\n // We do this pretty explicit set of checks because we want to support\n // providers that have a URL format where the path isn't necessarily at\n // the end of the URL string (e.g. in the query part). Some of these may\n // be empty strings etc, but that's fine as long as they parse to the\n // same thing as above.\n const candidate = parseGitUrl(row.target);\n\n if (\n candidate.protocol === base.protocol &&\n candidate.resource === base.resource &&\n candidate.port === base.port &&\n candidate.organization === base.organization &&\n candidate.owner === base.owner &&\n candidate.name === base.name &&\n // If the base has no ref (for example didn't have the \"/blob/master\"\n // part and therefore targeted an entire repository) then we match any\n // ref below that\n (!base.ref || candidate.ref === base.ref) &&\n // Match both on exact equality and any subpath with a slash between\n (candidate.filepath === base.filepath ||\n candidate.filepath.startsWith(pathPrefix))\n ) {\n return [{ row, parsed: candidate }];\n }\n return [];\n } catch {\n return [];\n }\n }),\n );\n }\n\n return uniqBy(result, entry => entry.row.id);\n }\n\n // #endregion\n}\n\n/**\n * Recursively builds up the SQL expression corresponding to the given filter\n * predicate.\n *\n * @remarks\n *\n * Design note: The code prefers to let the SQL engine achieve case\n * insensitivity. We could attempt to use `.toUpperCase` etc on the client\n * side, but that would only work for the values being passed in, not the column\n * side of the expression. If we let the database perform UPPER on both, we know\n * that they will always be locale consistent etc as well.\n *\n * This does come at a runtime cost. However, the data set is typically rather\n * small in the grand scheme of things, and we can add the proper indices in the\n * future if needed. At this point I considered it not worth the effort.\n */\nfunction applyLocationFilterToQuery(\n clientType: string,\n inputQuery: Knex.QueryBuilder,\n query: FilterPredicate,\n): Knex.QueryBuilder {\n let result = inputQuery;\n\n if (!query || typeof query !== 'object' || Array.isArray(query)) {\n throw new InputError('Invalid filter predicate, expected an object');\n }\n\n if ('$all' in query) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (query.$all.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n return result.where(outer => {\n for (const subQuery of query.$all) {\n outer.andWhere(inner => {\n applyLocationFilterToQuery(clientType, inner, subQuery);\n });\n }\n });\n }\n\n if ('$any' in query) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (query.$any.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n return result.where(outer => {\n for (const subQuery of query.$any) {\n outer.orWhere(inner => {\n applyLocationFilterToQuery(clientType, inner, subQuery);\n });\n }\n });\n }\n\n if ('$not' in query) {\n return result.whereNot(inner => {\n applyLocationFilterToQuery(clientType, inner, query.$not);\n });\n }\n\n const entries = Object.entries(query);\n const keys = entries.map(e => e[0]);\n if (keys.some(k => k.startsWith('$'))) {\n throw new InputError(\n `Invalid filter predicate, unknown logic operator '${keys.join(', ')}'`,\n );\n }\n\n for (const [keyAnyCase, value] of entries) {\n const key = keyAnyCase.toLocaleLowerCase('en-US');\n if (!['id', 'type', 'target', 'entityref'].includes(key)) {\n throw new InputError(\n `Invalid filter predicate, expected key to be 'id', 'type', 'target', or 'entityRef', got '${keyAnyCase}'`,\n );\n }\n\n // Map the API field name to the underlying column name\n const column = key === 'entityref' ? 'location_entity_ref' : key;\n result = applyFilterValueToQuery(clientType, result, column, value);\n }\n\n return result;\n}\n\nfunction applyFilterValueToQuery(\n clientType: string,\n result: Knex.QueryBuilder,\n key: string,\n value: FilterPredicateValue,\n): Knex.QueryBuilder {\n // Is it a primitive value?\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n if (clientType === 'pg') {\n return result.whereRaw(`UPPER(??::text) = UPPER(?::text)`, [key, value]);\n }\n\n if (clientType.includes('mysql')) {\n return result.whereRaw(\n `UPPER(CAST(?? AS CHAR)) = UPPER(CAST(? AS CHAR))`,\n [key, value],\n );\n }\n\n return result.whereRaw(`UPPER(??) = UPPER(?)`, [key, value]);\n }\n\n // Is it a matcher object?\n if (typeof value === 'object') {\n if (!value || Array.isArray(value)) {\n throw new InputError(\n `Invalid filter predicate, got unknown matcher object '${JSON.stringify(\n value,\n )}'`,\n );\n }\n\n // Technically existence checks do not make much sense in the context of\n // this table at the time of writing (values are always present), but\n // there's nothing gained by prohibiting it.\n if ('$exists' in value) {\n return value.$exists ? result.whereNotNull(key) : result.whereNull(key);\n }\n\n if ('$in' in value) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (value.$in.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n // The id is matched with plain equality; it's of UUID type and case\n // insensitivity does not apply.\n if (key === 'id') {\n return result.whereIn(key, value.$in);\n }\n\n if (clientType === 'pg') {\n const rhs = value.$in.map(() => 'UPPER(?::text)').join(', ');\n return result.whereRaw(`UPPER(??::text) IN (${rhs})`, [\n key,\n ...value.$in,\n ]);\n }\n\n if (clientType.includes('mysql')) {\n const rhs = value.$in.map(() => 'UPPER(CAST(? AS CHAR))').join(', ');\n return result.whereRaw(`UPPER(CAST(?? AS CHAR)) IN (${rhs})`, [\n key,\n ...value.$in,\n ]);\n }\n\n const rhs = value.$in.map(() => 'UPPER(?)').join(', ');\n return result.whereRaw(`UPPER(??) IN (${rhs})`, [key, ...value.$in]);\n }\n\n if ('$hasPrefix' in value) {\n const escaped = value.$hasPrefix.replace(/([\\\\%_])/g, '\\\\$1');\n\n if (clientType === 'pg') {\n return result.whereRaw(\"?? ilike ? escape '\\\\'\", [key, `${escaped}%`]);\n }\n\n if (clientType.includes('mysql')) {\n return result.whereRaw(\"UPPER(??) like UPPER(?) escape '\\\\\\\\'\", [\n key,\n `${escaped}%`,\n ]);\n }\n\n return result.whereRaw(\"UPPER(??) like UPPER(?) escape '\\\\'\", [\n key,\n `${escaped}%`,\n ]);\n }\n\n // There are no array shaped values for location queries, so we just always\n // fail here\n if ('$contains' in value) {\n return result.whereRaw('1 = 0');\n }\n\n throw new InputError(\n `Invalid filter predicate, got unknown matcher object '${JSON.stringify(\n value,\n )}'`,\n );\n }\n\n throw new InputError(\n `Invalid filter predicate, expected value to be a primitive value or a matcher object, got '${typeof value}'`,\n );\n}\n"],"names":["ConflictError","uuid","computeLocationEntityRef","locationSpecToLocationEntity","getEntityLocationRef","stringifyEntityRef","NotFoundError","ANNOTATION_ORIGIN_LOCATION","parseLocationRef","chunk","parseGitUrl","uniqBy","InputError","rhs"],"mappings":"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"}
@@ -169,7 +169,7 @@ exports.down = async function down(knex) {
169
169
  'An opaque string that changes for each update operation to any part of the entity, including metadata.',
170
170
  );
171
171
  table
172
- .string('generation')
172
+ .integer('generation')
173
173
  .notNullable()
174
174
  .unsigned()
175
175
  .comment(
@@ -23,7 +23,7 @@ exports.up = async function up(knex) {
23
23
  // Adds a single 'bootstrap' location that can be used to trigger work in processors.
24
24
  // This is primarily here to fulfill foreign key constraints.
25
25
  await knex('locations').insert({
26
- id: require('uuid').v4(),
26
+ id: require('node:crypto').randomUUID(),
27
27
  type: 'bootstrap',
28
28
  target: 'bootstrap',
29
29
  });
@@ -52,12 +52,12 @@ exports.down = async function down(knex) {
52
52
  await knex.schema.alterTable('entities', table => {
53
53
  // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
54
54
  table.dropUnique([], 'entities_unique_full_name');
55
- table.unique(['kind', 'namespace', 'name'], {
55
+ table.unique(['kind', 'name', 'namespace'], {
56
56
  indexName: 'entities_unique_name',
57
57
  });
58
58
  });
59
59
 
60
- await knex.schema.alterTable('entities_search', table => {
60
+ await knex.schema.alterTable('entities', table => {
61
61
  table.dropColumn('full_name');
62
62
  });
63
63
  };
@@ -175,33 +175,10 @@ exports.up = async function up(knex) {
175
175
  * @param {import('knex').Knex} knex
176
176
  */
177
177
  exports.down = async function down(knex) {
178
- await knex.schema.alterTable('refresh_state_references', table => {
179
- table.dropIndex([], 'refresh_state_references_source_key_idx');
180
- table.dropIndex([], 'refresh_state_references_source_entity_ref_idx');
181
- table.dropIndex([], 'refresh_state_references_target_entity_ref_idx');
182
- });
183
- await knex.schema.alterTable('refresh_state', table => {
184
- table.dropUnique([], 'refresh_state_entity_ref_uniq');
185
- table.dropIndex([], 'refresh_state_entity_id_idx');
186
- table.dropIndex([], 'refresh_state_entity_ref_idx');
187
- table.dropIndex([], 'refresh_state_next_update_at_idx');
188
- });
189
- await knex.schema.alterTable('final_entities', table => {
190
- table.dropIndex([], 'final_entities_entity_id_idx');
191
- });
192
- await knex.schema.alterTable('relations', table => {
193
- table.index('source_entity_ref', 'relations_source_entity_ref_idx');
194
- table.index('originating_entity_id', 'relations_source_entity_id_idx');
195
- });
196
- await knex.schema.alterTable('search', table => {
197
- table.dropIndex([], 'search_entity_id_idx');
198
- table.dropIndex([], 'search_key_idx');
199
- table.dropIndex([], 'search_value_idx');
200
- });
201
-
178
+ // Drop tables that have FK constraints referencing refresh_state first.
179
+ await knex.schema.dropTable('refresh_state_references');
202
180
  await knex.schema.dropTable('search');
203
181
  await knex.schema.dropTable('final_entities');
204
182
  await knex.schema.dropTable('relations');
205
- await knex.schema.dropTable('references');
206
183
  await knex.schema.dropTable('refresh_state');
207
184
  };
@@ -25,4 +25,89 @@ exports.up = async function up(knex) {
25
25
  await knex.schema.dropTable('entities');
26
26
  };
27
27
 
28
- exports.down = async function down() {};
28
+ /**
29
+ * @param {import('knex').Knex} knex
30
+ */
31
+ exports.down = async function down(knex) {
32
+ await knex.schema.createTable('entities', table => {
33
+ table.comment('All entities currently stored in the catalog');
34
+ table.uuid('id').primary().comment('Auto-generated ID of the entity');
35
+ table
36
+ .uuid('location_id')
37
+ .references('id')
38
+ .inTable('locations')
39
+ .nullable()
40
+ .comment('The location that originated the entity');
41
+ table
42
+ .string('etag')
43
+ .notNullable()
44
+ .comment(
45
+ 'An opaque string that changes for each update operation to any part of the entity, including metadata.',
46
+ );
47
+ table
48
+ .integer('generation')
49
+ .notNullable()
50
+ .unsigned()
51
+ .comment(
52
+ 'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.',
53
+ );
54
+ table
55
+ .string('full_name')
56
+ .notNullable()
57
+ .comment('The full name of the entity');
58
+ table
59
+ .text('data')
60
+ .notNullable()
61
+ .comment('The entire JSON data blob of the entity');
62
+ table.unique(['full_name'], { indexName: 'entities_unique_full_name' });
63
+ table.index('location_id', 'entity_location_id_idx');
64
+ });
65
+
66
+ await knex.schema.createTable('entities_search', table => {
67
+ table.comment(
68
+ 'Flattened key-values from the entities, used for quick filtering',
69
+ );
70
+ table
71
+ .uuid('entity_id')
72
+ .references('id')
73
+ .inTable('entities')
74
+ .onDelete('CASCADE')
75
+ .comment('The entity that matches this key/value');
76
+ table
77
+ .string('key')
78
+ .notNullable()
79
+ .comment('A key that occurs in the entity');
80
+ table
81
+ .string('value')
82
+ .nullable()
83
+ .comment('The corresponding value to match on');
84
+ table.index(['key'], 'entities_search_key');
85
+ table.index(['value'], 'entities_search_value');
86
+ table.index(['entity_id'], 'entity_id_idx');
87
+ });
88
+
89
+ await knex.schema.createTable('entities_relations', table => {
90
+ table.comment('All relations between entities in the catalog');
91
+ table
92
+ .uuid('originating_entity_id')
93
+ .references('id')
94
+ .inTable('entities')
95
+ .onDelete('CASCADE')
96
+ .notNullable()
97
+ .comment('The entity that provided the relation');
98
+ table
99
+ .string('source_full_name')
100
+ .notNullable()
101
+ .comment('The full name of the source entity of the relation');
102
+ table
103
+ .string('type')
104
+ .notNullable()
105
+ .comment('The type of the relation between the entities');
106
+ table
107
+ .string('target_full_name')
108
+ .notNullable()
109
+ .comment('The full name of the target entity of the relation');
110
+ table.index('source_full_name', 'source_full_name_idx');
111
+ table.index('originating_entity_id', 'originating_entity_id_idx');
112
+ });
113
+ };
@@ -22,7 +22,7 @@
22
22
  */
23
23
  exports.up = async function up(knex) {
24
24
  await knex.schema.alterTable('locations', table => {
25
- table.text('target').alter();
25
+ table.text('target').notNullable().alter();
26
26
  });
27
27
  };
28
28
 
@@ -42,6 +42,6 @@ exports.down = async function down(knex) {
42
42
  }
43
43
 
44
44
  await knex.schema.alterTable('locations', table => {
45
- table.string('target').alter();
45
+ table.string('target').notNullable().alter();
46
46
  });
47
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend",
3
- "version": "3.6.1",
3
+ "version": "3.6.2-next.1",
4
4
  "description": "The Backstage backend plugin that provides the Backstage catalog",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -76,20 +76,20 @@
76
76
  "test": "backstage-cli package test"
77
77
  },
78
78
  "dependencies": {
79
- "@backstage/backend-openapi-utils": "^0.6.8",
80
- "@backstage/backend-plugin-api": "^1.9.0",
81
- "@backstage/catalog-client": "^1.15.0",
82
- "@backstage/catalog-model": "^1.8.0",
83
- "@backstage/config": "^1.3.7",
84
- "@backstage/errors": "^1.3.0",
85
- "@backstage/filter-predicates": "^0.1.2",
86
- "@backstage/integration": "^2.0.1",
87
- "@backstage/plugin-catalog-common": "^1.1.9",
88
- "@backstage/plugin-catalog-node": "^2.2.0",
89
- "@backstage/plugin-events-node": "^0.4.21",
90
- "@backstage/plugin-permission-common": "^0.9.8",
91
- "@backstage/plugin-permission-node": "^0.10.12",
92
- "@backstage/types": "^1.2.2",
79
+ "@backstage/backend-openapi-utils": "0.6.9-next.0",
80
+ "@backstage/backend-plugin-api": "1.9.1-next.0",
81
+ "@backstage/catalog-client": "1.15.1-next.0",
82
+ "@backstage/catalog-model": "1.8.1-next.1",
83
+ "@backstage/config": "1.3.8-next.0",
84
+ "@backstage/errors": "1.3.1-next.0",
85
+ "@backstage/filter-predicates": "0.1.3-next.0",
86
+ "@backstage/integration": "2.0.2-next.0",
87
+ "@backstage/plugin-catalog-common": "1.1.10-next.0",
88
+ "@backstage/plugin-catalog-node": "2.2.1-next.1",
89
+ "@backstage/plugin-events-node": "0.4.22-next.0",
90
+ "@backstage/plugin-permission-common": "0.9.9-next.1",
91
+ "@backstage/plugin-permission-node": "0.10.13-next.0",
92
+ "@backstage/types": "1.2.2",
93
93
  "@opentelemetry/api": "^1.9.0",
94
94
  "ajv": "^8.10.0",
95
95
  "ajv-errors": "^3.0.0",
@@ -106,27 +106,24 @@
106
106
  "minimatch": "^10.2.1",
107
107
  "p-limit": "^3.0.2",
108
108
  "prom-client": "^15.0.0",
109
- "uuid": "^11.0.0",
110
109
  "yaml": "^2.0.0",
111
110
  "yn": "^4.0.0",
112
111
  "zod": "^3.25.76 || ^4.0.0",
113
112
  "zod-validation-error": "^4.0.2"
114
113
  },
115
114
  "devDependencies": {
116
- "@backstage/backend-defaults": "^0.17.0",
117
- "@backstage/backend-test-utils": "^1.11.2",
118
- "@backstage/cli": "^0.36.1",
119
- "@backstage/plugin-catalog-backend-module-logs": "^0.1.21",
120
- "@backstage/plugin-permission-common": "^0.9.8",
121
- "@backstage/plugin-scaffolder-common": "^2.1.0",
122
- "@backstage/repo-tools": "^0.17.1",
115
+ "@backstage/backend-defaults": "0.17.1-next.1",
116
+ "@backstage/backend-test-utils": "1.11.3-next.1",
117
+ "@backstage/cli": "0.36.2-next.1",
118
+ "@backstage/plugin-catalog-backend-module-logs": "0.1.22-next.0",
119
+ "@backstage/plugin-scaffolder-common": "2.1.1-next.0",
120
+ "@backstage/repo-tools": "0.17.2-next.0",
123
121
  "@types/core-js": "^2.5.4",
124
122
  "@types/express": "^4.17.6",
125
123
  "@types/git-url-parse": "^9.0.0",
126
124
  "@types/lodash": "^4.14.151",
127
125
  "@types/supertest": "^2.0.8",
128
126
  "better-sqlite3": "^12.0.0",
129
- "luxon": "^3.0.0",
130
127
  "msw": "^2.0.0",
131
128
  "supertest": "^7.0.0",
132
129
  "wait-for-expect": "^4.0.0",