diff --git a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts index 4530cd795c..bc34d1c769 100644 --- a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts +++ b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts @@ -29,6 +29,8 @@ import { Entity } from '../models/Entity.model'; import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model'; import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model'; import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model'; +import { QueryEntitiesByPredicate200Response } from '../models/QueryEntitiesByPredicate200Response.model'; +import { QueryEntitiesByPredicateRequest } from '../models/QueryEntitiesByPredicateRequest.model'; import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model'; import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model'; import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model'; @@ -139,6 +141,18 @@ export type GetEntityFacets = { filter?: Array; }; }; +/** + * @public + */ +export type QueryEntitiesByPredicate = { + body: QueryEntitiesByPredicateRequest; + query: { + limit?: number; + offset?: number; + orderField?: Array; + after?: string; + }; +}; /** * @public */ @@ -449,6 +463,37 @@ export class DefaultApiClient { }); } + /** + * Query entities using predicate-based filters. This endpoint provides an alternative filtering method with a more expressive filter syntax supporting logical operators ($all, $any, $not) and value operators ($exists, $in). Example query: ```json { \"query\": { \"$all\": [ {\"kind\": \"component\"}, {\"$any\": [ {\"spec.type\": \"service\"}, {\"spec.type\": \"website\"} ]}, {\"$not\": {\"spec.lifecycle\": \"experimental\"}} ] } } ``` + * @param queryEntitiesByPredicateRequest - + * @param limit - Number of records to return in the response. + * @param offset - Number of records to skip in the query page. + * @param orderField - By default the entities are returned ordered by their internal uid. You can customize the `orderField` query parameters to affect that ordering. For example, to return entities by their name: `/entities/by-query?orderField=metadata.name,asc` Each parameter can be followed by `asc` for ascending lexicographical order or `desc` for descending (reverse) lexicographical order. + * @param after - Pointer to the previous page of results. + */ + public async queryEntitiesByPredicate( + // @ts-ignore + request: QueryEntitiesByPredicate, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-query{?limit,offset,orderField*,after}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + /** * Refresh the entity related to entityRef. * @param refreshEntityRequest - diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicate.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicate.model.ts new file mode 100644 index 0000000000..bdc862ca50 --- /dev/null +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicate.model.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicateAll } from '../models/EntityPredicateAll.model'; +import { EntityPredicateAny } from '../models/EntityPredicateAny.model'; +import { EntityPredicateNot } from '../models/EntityPredicateNot.model'; +import { EntityPredicateValue } from '../models/EntityPredicateValue.model'; + +/** + * A predicate-based filter supporting logical operators. - $all: All conditions must match (AND) - $any: At least one condition must match (OR) - $not: Negates the condition - $exists: Check if field exists - $in: Match any value in array + * @public + */ +export type EntityPredicate = + | EntityPredicateAll + | EntityPredicateAny + | EntityPredicateNot + | boolean + | number + | string + | { [key: string]: EntityPredicateValue }; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateAll.model.ts similarity index 92% rename from plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateAll.model.ts index da6f4678b9..080b8307b4 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateAll.model.ts @@ -20,8 +20,9 @@ import { EntityPredicate } from '../models/EntityPredicate.model'; /** + * All conditions must match (AND logic) * @public */ -export interface EntityPredicateOneOf { +export interface EntityPredicateAll { $all: Array; } diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateAny.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateAny.model.ts new file mode 100644 index 0000000000..170ea3ab0c --- /dev/null +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateAny.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicate } from '../models/EntityPredicate.model'; + +/** + * At least one condition must match (OR logic) + * @public + */ +export interface EntityPredicateAny { + $any: Array; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateExists.model.ts similarity index 92% rename from plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateExists.model.ts index e260d2456a..7800fcd20c 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateExists.model.ts @@ -19,8 +19,9 @@ // ****************************************************************** /** + * Check if field exists * @public */ -export interface EntityPredicateValueOneOf { +export interface EntityPredicateExists { $exists: boolean; } diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateIn.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateIn.model.ts new file mode 100644 index 0000000000..574ab4f45b --- /dev/null +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateIn.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicateInInInner } from '../models/EntityPredicateInInInner.model'; + +/** + * Match any value in array + * @public + */ +export interface EntityPredicateIn { + $in: Array; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf1InInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateInInInner.model.ts similarity index 91% rename from plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf1InInner.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateInInInner.model.ts index 45c7bfac4c..2b4e3d7c2f 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf1InInner.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateInInInner.model.ts @@ -21,4 +21,4 @@ /** * @public */ -export type EntityPredicateValueOneOf1InInner = boolean | number | string; +export type EntityPredicateInInInner = boolean | number | string; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf2.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateNot.model.ts similarity index 93% rename from plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf2.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateNot.model.ts index af1ca66a3a..e56279b0fc 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf2.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateNot.model.ts @@ -20,8 +20,9 @@ import { EntityPredicate } from '../models/EntityPredicate.model'; /** + * Negates the condition * @public */ -export interface EntityPredicateOneOf2 { +export interface EntityPredicateNot { $not: EntityPredicate; } diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateValue.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateValue.model.ts new file mode 100644 index 0000000000..c230c2a984 --- /dev/null +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityPredicateValue.model.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicateExists } from '../models/EntityPredicateExists.model'; +import { EntityPredicateIn } from '../models/EntityPredicateIn.model'; + +/** + * Value for a field predicate + * @public + */ +export type EntityPredicateValue = + | EntityPredicateExists + | EntityPredicateIn + | boolean + | number + | string; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicates200Response.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicate200Response.model.ts similarity index 80% rename from plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicates200Response.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicate200Response.model.ts index 0acbfe559e..3c4b74cd10 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicates200Response.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicate200Response.model.ts @@ -18,15 +18,15 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { Entity } from '../models/Entity.model'; -import { GetEntitiesByPredicates200ResponsePageInfo } from '../models/GetEntitiesByPredicates200ResponsePageInfo.model'; +import { QueryEntitiesByPredicate200ResponsePageInfo } from '../models/QueryEntitiesByPredicate200ResponsePageInfo.model'; /** * @public */ -export interface GetEntitiesByPredicates200Response { +export interface QueryEntitiesByPredicate200Response { /** * The list of entities matching the predicate filter. */ items: Array; - pageInfo: GetEntitiesByPredicates200ResponsePageInfo; + pageInfo: QueryEntitiesByPredicate200ResponsePageInfo; } diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicates200ResponsePageInfo.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicate200ResponsePageInfo.model.ts similarity index 93% rename from plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicates200ResponsePageInfo.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicate200ResponsePageInfo.model.ts index 64ea958943..3ef9f73758 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicates200ResponsePageInfo.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicate200ResponsePageInfo.model.ts @@ -21,7 +21,7 @@ /** * @public */ -export interface GetEntitiesByPredicates200ResponsePageInfo { +export interface QueryEntitiesByPredicate200ResponsePageInfo { /** * The cursor for the next batch of entities. */ diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf1.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts similarity index 92% rename from plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf1.model.ts rename to packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts index 8e2b1217f5..8b8276c9da 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateOneOf1.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts @@ -22,6 +22,6 @@ import { EntityPredicate } from '../models/EntityPredicate.model'; /** * @public */ -export interface EntityPredicateOneOf1 { - $any: Array; +export interface QueryEntitiesByPredicateRequest { + query?: EntityPredicate; } diff --git a/packages/catalog-client/src/schema/openapi/generated/models/index.ts b/packages/catalog-client/src/schema/openapi/generated/models/index.ts index abdb58378c..9d7ee33602 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/index.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/index.ts @@ -31,6 +31,14 @@ export * from '../models/EntityFacet.model'; export * from '../models/EntityFacetsResponse.model'; export * from '../models/EntityLink.model'; export * from '../models/EntityMeta.model'; +export * from '../models/EntityPredicate.model'; +export * from '../models/EntityPredicateAll.model'; +export * from '../models/EntityPredicateAny.model'; +export * from '../models/EntityPredicateExists.model'; +export * from '../models/EntityPredicateIn.model'; +export * from '../models/EntityPredicateInInInner.model'; +export * from '../models/EntityPredicateNot.model'; +export * from '../models/EntityPredicateValue.model'; export * from '../models/EntityRelation.model'; export * from '../models/ErrorError.model'; export * from '../models/ErrorRequest.model'; @@ -45,6 +53,9 @@ export * from '../models/LocationsQueryResponse.model'; export * from '../models/LocationsQueryResponsePageInfo.model'; export * from '../models/ModelError.model'; export * from '../models/NullableEntity.model'; +export * from '../models/QueryEntitiesByPredicate200Response.model'; +export * from '../models/QueryEntitiesByPredicate200ResponsePageInfo.model'; +export * from '../models/QueryEntitiesByPredicateRequest.model'; export * from '../models/RecursivePartialEntity.model'; export * from '../models/RecursivePartialEntityMeta.model'; export * from '../models/RecursivePartialEntityMetaAllOf.model'; diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index 12c5e00c03..dda003ec90 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -418,16 +418,42 @@ export type QueryEntitiesRequest = * The method takes this type in an initial pagination request, * when requesting the first batch of entities. * - * The properties filter, sortField, query and sortFieldOrder, are going + * The properties filter, query, sortField and sortFieldOrder, are going * to be immutable for the entire lifecycle of the following requests. * + * @remarks + * + * Either `filter` or `query` can be provided, but not both: + * - `filter`: Uses the traditional key-value filter syntax (GET endpoint) + * - `query`: Uses the predicate-based filter syntax with logical operators (POST endpoint) + * * @public */ export type QueryEntitiesInitialRequest = { fields?: string[]; limit?: number; offset?: number; + /** + * Traditional key-value based filter. Mutually exclusive with `query`. + */ filter?: EntityFilterQuery; + /** + * Predicate-based filter with logical operators ($all, $any, $not, $exists, $in). + * Mutually exclusive with `filter`. + * + * @example + * ```typescript + * { + * query: { + * $all: [ + * { kind: 'component' }, + * { 'spec.type': 'service' } + * ] + * } + * } + * ``` + */ + query?: FilterPredicate; orderFields?: EntityOrderQuery; fullTextFilter?: { term: string; diff --git a/packages/catalog-client/src/types/index.ts b/packages/catalog-client/src/types/index.ts index b280f1874f..20a5f3303d 100644 --- a/packages/catalog-client/src/types/index.ts +++ b/packages/catalog-client/src/types/index.ts @@ -44,4 +44,13 @@ export type { QueryLocationsInitialRequest, QueryLocationsResponse, } from './api'; +export type { + EntityPredicate, + EntityPredicateAll, + EntityPredicateAny, + EntityPredicateNot, + EntityPredicateValue, + EntityPredicateExists, + EntityPredicateIn, +} from './predicate'; export { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from './status'; diff --git a/packages/catalog-client/src/types/predicate.ts b/packages/catalog-client/src/types/predicate.ts new file mode 100644 index 0000000000..11b081d650 --- /dev/null +++ b/packages/catalog-client/src/types/predicate.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A predicate-based filter supporting logical operators. + * + * @remarks + * + * This provides a more expressive filter syntax compared to the traditional + * EntityFilterQuery. It supports: + * - Logical operators: $all (AND), $any (OR), $not (negation) + * - Value operators: $exists, $in + * - Direct field matching with primitive values + * + * @example + * ```typescript + * // Match all service components + * { + * $all: [ + * { kind: 'component' }, + * { 'spec.type': 'service' } + * ] + * } + * + * // Match components owned by specific teams + * { + * $all: [ + * { kind: 'component' }, + * { 'spec.owner': { $in: ['backend-team', 'platform-team'] } } + * ] + * } + * + * // Match non-production services + * { + * $all: [ + * { kind: 'component' }, + * { 'spec.type': 'service' }, + * { $not: { 'spec.lifecycle': 'production' } } + * ] + * } + * ``` + * + * @public + */ +export type EntityPredicate = + | EntityPredicateAll + | EntityPredicateAny + | EntityPredicateNot + | boolean + | number + | string + | { [key: string]: EntityPredicateValue }; + +/** + * All conditions must match (AND logic). + * + * @public + */ +export interface EntityPredicateAll { + $all: Array; +} + +/** + * At least one condition must match (OR logic). + * + * @public + */ +export interface EntityPredicateAny { + $any: Array; +} + +/** + * Negates the condition. + * + * @public + */ +export interface EntityPredicateNot { + $not: EntityPredicate; +} + +/** + * Value for a field predicate. + * + * @public + */ +export type EntityPredicateValue = + | EntityPredicateExists + | EntityPredicateIn + | boolean + | number + | string; + +/** + * Check if field exists. + * + * @public + */ +export interface EntityPredicateExists { + $exists: boolean; +} + +/** + * Match any value in array. + * + * @public + */ +export interface EntityPredicateIn { + $in: Array; +} diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index d4c7560c30..fba22d153a 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -350,59 +350,74 @@ components: - type: string - type: number - type: boolean - - type: object - additionalProperties: false - properties: - $all: - type: array - items: - $ref: '#/components/schemas/EntityPredicate' - required: - - $all - - type: object - additionalProperties: false - properties: - $any: - type: array - items: - $ref: '#/components/schemas/EntityPredicate' - required: - - $any - - type: object - additionalProperties: false - properties: - $not: - $ref: '#/components/schemas/EntityPredicate' - required: - - $not + - $ref: '#/components/schemas/EntityPredicateAll' + - $ref: '#/components/schemas/EntityPredicateAny' + - $ref: '#/components/schemas/EntityPredicateNot' - type: object additionalProperties: $ref: '#/components/schemas/EntityPredicateValue' + EntityPredicateAll: + type: object + description: All conditions must match (AND logic) + additionalProperties: false + properties: + $all: + type: array + items: + $ref: '#/components/schemas/EntityPredicate' + required: + - $all + EntityPredicateAny: + type: object + description: At least one condition must match (OR logic) + additionalProperties: false + properties: + $any: + type: array + items: + $ref: '#/components/schemas/EntityPredicate' + required: + - $any + EntityPredicateNot: + type: object + description: Negates the condition + additionalProperties: false + properties: + $not: + $ref: '#/components/schemas/EntityPredicate' + required: + - $not EntityPredicateValue: description: Value for a field predicate oneOf: - type: string - type: number - type: boolean - - type: object - additionalProperties: false - properties: - $exists: - type: boolean - required: - - $exists - - type: object - additionalProperties: false - properties: - $in: - type: array - items: - oneOf: - - type: string - - type: number - - type: boolean - required: - - $in + - $ref: '#/components/schemas/EntityPredicateExists' + - $ref: '#/components/schemas/EntityPredicateIn' + EntityPredicateExists: + type: object + description: Check if field exists + additionalProperties: false + properties: + $exists: + type: boolean + required: + - $exists + EntityPredicateIn: + type: object + description: Match any value in array + additionalProperties: false + properties: + $in: + type: array + items: + oneOf: + - type: string + - type: number + - type: boolean + required: + - $in MapStringString: type: object properties: {} @@ -1160,20 +1175,19 @@ paths: type: string explode: false style: form - /entities/by-predicates: post: - operationId: GetEntitiesByPredicates + operationId: QueryEntitiesByPredicate tags: - Entity description: | - Query entities using predicate-based filters. This endpoint supports a more - expressive filter syntax with logical operators ($all, $any, $not) and - value operators ($exists, $in). + Query entities using predicate-based filters. This endpoint provides an + alternative filtering method with a more expressive filter syntax supporting + logical operators ($all, $any, $not) and value operators ($exists, $in). - Example filter: + Example query: ```json { - "filter": { + "query": { "$all": [ {"kind": "component"}, {"$any": [ @@ -1217,15 +1231,8 @@ paths: parameters: - $ref: '#/components/parameters/limit' - $ref: '#/components/parameters/offset' + - $ref: '#/components/parameters/orderField' - $ref: '#/components/parameters/after' - - name: order - in: query - allowReserved: true - required: false - schema: - type: array - items: - type: string requestBody: required: true content: @@ -1234,18 +1241,18 @@ paths: type: object additionalProperties: false properties: - filter: + query: $ref: '#/components/schemas/EntityPredicate' examples: Get all service components: value: - filter: + query: $all: - kind: component - spec.type: service Get components owned by specific teams: value: - filter: + query: $all: - kind: component - spec.owner: @@ -1254,7 +1261,7 @@ paths: - platform-team Get non-production services: value: - filter: + query: $all: - kind: component - spec.type: service diff --git a/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts index a185f9a359..258c777067 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -25,9 +25,9 @@ import { EntitiesQueryResponse } from '../models/EntitiesQueryResponse.model'; import { Entity } from '../models/Entity.model'; import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model'; import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model'; -import { GetEntitiesByPredicates200Response } from '../models/GetEntitiesByPredicates200Response.model'; -import { GetEntitiesByPredicatesRequest } from '../models/GetEntitiesByPredicatesRequest.model'; import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model'; +import { QueryEntitiesByPredicate200Response } from '../models/QueryEntitiesByPredicate200Response.model'; +import { QueryEntitiesByPredicateRequest } from '../models/QueryEntitiesByPredicateRequest.model'; import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model'; import { ValidateEntity400Response } from '../models/ValidateEntity400Response.model'; import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model'; @@ -55,19 +55,6 @@ export type GetEntities = { }; response: Array | Error | Error; }; -/** - * @public - */ -export type GetEntitiesByPredicates = { - body: GetEntitiesByPredicatesRequest; - query: { - limit?: number; - offset?: number; - after?: string; - order?: Array; - }; - response: GetEntitiesByPredicates200Response | Error | Error; -}; /** * @public */ @@ -135,6 +122,19 @@ export type GetEntityFacets = { }; response: EntityFacetsResponse | Error | Error; }; +/** + * @public + */ +export type QueryEntitiesByPredicate = { + body: QueryEntitiesByPredicateRequest; + query: { + limit?: number; + offset?: number; + orderField?: Array; + after?: string; + }; + response: QueryEntitiesByPredicate200Response | Error | Error; +}; /** * @public */ @@ -223,8 +223,6 @@ export type EndpointMap = { '#get|/entities': GetEntities; - '#post|/entities/by-predicates': GetEntitiesByPredicates; - '#get|/entities/by-query': GetEntitiesByQuery; '#post|/entities/by-refs': GetEntitiesByRefs; @@ -237,6 +235,8 @@ export type EndpointMap = { '#get|/entity-facets': GetEntityFacets; + '#post|/entities/by-query': QueryEntitiesByPredicate; + '#post|/refresh': RefreshEntity; '#post|/validate-entity': ValidateEntity; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicate.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicate.model.ts index 18b9038228..bdc862ca50 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicate.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicate.model.ts @@ -17,9 +17,9 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** -import { EntityPredicateOneOf } from '../models/EntityPredicateOneOf.model'; -import { EntityPredicateOneOf1 } from '../models/EntityPredicateOneOf1.model'; -import { EntityPredicateOneOf2 } from '../models/EntityPredicateOneOf2.model'; +import { EntityPredicateAll } from '../models/EntityPredicateAll.model'; +import { EntityPredicateAny } from '../models/EntityPredicateAny.model'; +import { EntityPredicateNot } from '../models/EntityPredicateNot.model'; import { EntityPredicateValue } from '../models/EntityPredicateValue.model'; /** @@ -27,9 +27,9 @@ import { EntityPredicateValue } from '../models/EntityPredicateValue.model'; * @public */ export type EntityPredicate = - | EntityPredicateOneOf - | EntityPredicateOneOf1 - | EntityPredicateOneOf2 + | EntityPredicateAll + | EntityPredicateAny + | EntityPredicateNot | boolean | number | string diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateAll.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateAll.model.ts new file mode 100644 index 0000000000..080b8307b4 --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateAll.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicate } from '../models/EntityPredicate.model'; + +/** + * All conditions must match (AND logic) + * @public + */ +export interface EntityPredicateAll { + $all: Array; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateAny.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateAny.model.ts new file mode 100644 index 0000000000..170ea3ab0c --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateAny.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicate } from '../models/EntityPredicate.model'; + +/** + * At least one condition must match (OR logic) + * @public + */ +export interface EntityPredicateAny { + $any: Array; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateExists.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateExists.model.ts new file mode 100644 index 0000000000..7800fcd20c --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateExists.model.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * Check if field exists + * @public + */ +export interface EntityPredicateExists { + $exists: boolean; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateIn.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateIn.model.ts new file mode 100644 index 0000000000..574ab4f45b --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateIn.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicateInInInner } from '../models/EntityPredicateInInInner.model'; + +/** + * Match any value in array + * @public + */ +export interface EntityPredicateIn { + $in: Array; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateInInInner.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateInInInner.model.ts new file mode 100644 index 0000000000..2b4e3d7c2f --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateInInInner.model.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type EntityPredicateInInInner = boolean | number | string; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateNot.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateNot.model.ts new file mode 100644 index 0000000000..e56279b0fc --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateNot.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { EntityPredicate } from '../models/EntityPredicate.model'; + +/** + * Negates the condition + * @public + */ +export interface EntityPredicateNot { + $not: EntityPredicate; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValue.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValue.model.ts index 0ea00bef2f..c230c2a984 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValue.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValue.model.ts @@ -17,16 +17,16 @@ // ****************************************************************** // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** -import { EntityPredicateValueOneOf } from '../models/EntityPredicateValueOneOf.model'; -import { EntityPredicateValueOneOf1 } from '../models/EntityPredicateValueOneOf1.model'; +import { EntityPredicateExists } from '../models/EntityPredicateExists.model'; +import { EntityPredicateIn } from '../models/EntityPredicateIn.model'; /** * Value for a field predicate * @public */ export type EntityPredicateValue = - | EntityPredicateValueOneOf - | EntityPredicateValueOneOf1 + | EntityPredicateExists + | EntityPredicateIn | boolean | number | string; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf1.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf1.model.ts deleted file mode 100644 index 1bea769175..0000000000 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityPredicateValueOneOf1.model.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2026 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// ****************************************************************** -// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * -// ****************************************************************** -import { EntityPredicateValueOneOf1InInner } from '../models/EntityPredicateValueOneOf1InInner.model'; - -/** - * @public - */ -export interface EntityPredicateValueOneOf1 { - $in: Array; -} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicate200Response.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicate200Response.model.ts new file mode 100644 index 0000000000..3c4b74cd10 --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicate200Response.model.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { Entity } from '../models/Entity.model'; +import { QueryEntitiesByPredicate200ResponsePageInfo } from '../models/QueryEntitiesByPredicate200ResponsePageInfo.model'; + +/** + * @public + */ +export interface QueryEntitiesByPredicate200Response { + /** + * The list of entities matching the predicate filter. + */ + items: Array; + pageInfo: QueryEntitiesByPredicate200ResponsePageInfo; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicate200ResponsePageInfo.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicate200ResponsePageInfo.model.ts new file mode 100644 index 0000000000..3ef9f73758 --- /dev/null +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicate200ResponsePageInfo.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface QueryEntitiesByPredicate200ResponsePageInfo { + /** + * The cursor for the next batch of entities. + */ + nextCursor?: string; +} diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicatesRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts similarity index 87% rename from plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicatesRequest.model.ts rename to plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts index 2937e27df5..8b8276c9da 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByPredicatesRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/QueryEntitiesByPredicateRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,6 @@ import { EntityPredicate } from '../models/EntityPredicate.model'; /** * @public */ -export interface GetEntitiesByPredicatesRequest { - filter?: EntityPredicate; +export interface QueryEntitiesByPredicateRequest { + query?: EntityPredicate; } diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts index abd35ed409..9d7ee33602 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts @@ -32,20 +32,17 @@ export * from '../models/EntityFacetsResponse.model'; export * from '../models/EntityLink.model'; export * from '../models/EntityMeta.model'; export * from '../models/EntityPredicate.model'; -export * from '../models/EntityPredicateOneOf.model'; -export * from '../models/EntityPredicateOneOf1.model'; -export * from '../models/EntityPredicateOneOf2.model'; +export * from '../models/EntityPredicateAll.model'; +export * from '../models/EntityPredicateAny.model'; +export * from '../models/EntityPredicateExists.model'; +export * from '../models/EntityPredicateIn.model'; +export * from '../models/EntityPredicateInInInner.model'; +export * from '../models/EntityPredicateNot.model'; export * from '../models/EntityPredicateValue.model'; -export * from '../models/EntityPredicateValueOneOf.model'; -export * from '../models/EntityPredicateValueOneOf1.model'; -export * from '../models/EntityPredicateValueOneOf1InInner.model'; export * from '../models/EntityRelation.model'; export * from '../models/ErrorError.model'; export * from '../models/ErrorRequest.model'; export * from '../models/ErrorResponse.model'; -export * from '../models/GetEntitiesByPredicates200Response.model'; -export * from '../models/GetEntitiesByPredicates200ResponsePageInfo.model'; -export * from '../models/GetEntitiesByPredicatesRequest.model'; export * from '../models/GetEntitiesByRefsRequest.model'; export * from '../models/GetLocations200ResponseInner.model'; export * from '../models/GetLocationsByQueryRequest.model'; @@ -56,6 +53,9 @@ export * from '../models/LocationsQueryResponse.model'; export * from '../models/LocationsQueryResponsePageInfo.model'; export * from '../models/ModelError.model'; export * from '../models/NullableEntity.model'; +export * from '../models/QueryEntitiesByPredicate200Response.model'; +export * from '../models/QueryEntitiesByPredicate200ResponsePageInfo.model'; +export * from '../models/QueryEntitiesByPredicateRequest.model'; export * from '../models/RecursivePartialEntity.model'; export * from '../models/RecursivePartialEntityMeta.model'; export * from '../models/RecursivePartialEntityMetaAllOf.model'; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/router.ts b/plugins/catalog-backend/src/schema/openapi/generated/router.ts index 5fe2c642b7..2625bd0125 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/router.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/router.ts @@ -276,40 +276,13 @@ export const spec = { type: 'boolean', }, { - type: 'object', - additionalProperties: false, - properties: { - $all: { - type: 'array', - items: { - $ref: '#/components/schemas/EntityPredicate', - }, - }, - }, - required: ['$all'], + $ref: '#/components/schemas/EntityPredicateAll', }, { - type: 'object', - additionalProperties: false, - properties: { - $any: { - type: 'array', - items: { - $ref: '#/components/schemas/EntityPredicate', - }, - }, - }, - required: ['$any'], + $ref: '#/components/schemas/EntityPredicateAny', }, { - type: 'object', - additionalProperties: false, - properties: { - $not: { - $ref: '#/components/schemas/EntityPredicate', - }, - }, - required: ['$not'], + $ref: '#/components/schemas/EntityPredicateNot', }, { type: 'object', @@ -319,6 +292,45 @@ export const spec = { }, ], }, + EntityPredicateAll: { + type: 'object', + description: 'All conditions must match (AND logic)', + additionalProperties: false, + properties: { + $all: { + type: 'array', + items: { + $ref: '#/components/schemas/EntityPredicate', + }, + }, + }, + required: ['$all'], + }, + EntityPredicateAny: { + type: 'object', + description: 'At least one condition must match (OR logic)', + additionalProperties: false, + properties: { + $any: { + type: 'array', + items: { + $ref: '#/components/schemas/EntityPredicate', + }, + }, + }, + required: ['$any'], + }, + EntityPredicateNot: { + type: 'object', + description: 'Negates the condition', + additionalProperties: false, + properties: { + $not: { + $ref: '#/components/schemas/EntityPredicate', + }, + }, + required: ['$not'], + }, EntityPredicateValue: { description: 'Value for a field predicate', oneOf: [ @@ -332,40 +344,48 @@ export const spec = { type: 'boolean', }, { - type: 'object', - additionalProperties: false, - properties: { - $exists: { - type: 'boolean', - }, - }, - required: ['$exists'], + $ref: '#/components/schemas/EntityPredicateExists', }, { - type: 'object', - additionalProperties: false, - properties: { - $in: { - type: 'array', - items: { - oneOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - { - type: 'boolean', - }, - ], - }, - }, - }, - required: ['$in'], + $ref: '#/components/schemas/EntityPredicateIn', }, ], }, + EntityPredicateExists: { + type: 'object', + description: 'Check if field exists', + additionalProperties: false, + properties: { + $exists: { + type: 'boolean', + }, + }, + required: ['$exists'], + }, + EntityPredicateIn: { + type: 'object', + description: 'Match any value in array', + additionalProperties: false, + properties: { + $in: { + type: 'array', + items: { + oneOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + { + type: 'boolean', + }, + ], + }, + }, + }, + required: ['$in'], + }, MapStringString: { type: 'object', properties: {}, @@ -1332,13 +1352,11 @@ export const spec = { }, ], }, - }, - '/entities/by-predicates': { post: { - operationId: 'GetEntitiesByPredicates', + operationId: 'QueryEntitiesByPredicate', tags: ['Entity'], description: - 'Query entities using predicate-based filters. This endpoint supports a more\nexpressive filter syntax with logical operators ($all, $any, $not) and\nvalue operators ($exists, $in).\n\nExample filter:\n```json\n{\n "filter": {\n "$all": [\n {"kind": "component"},\n {"$any": [\n {"spec.type": "service"},\n {"spec.type": "website"}\n ]},\n {"$not": {"spec.lifecycle": "experimental"}}\n ]\n }\n}\n```\n', + 'Query entities using predicate-based filters. This endpoint provides an\nalternative filtering method with a more expressive filter syntax supporting\nlogical operators ($all, $any, $not) and value operators ($exists, $in).\n\nExample query:\n```json\n{\n "query": {\n "$all": [\n {"kind": "component"},\n {"$any": [\n {"spec.type": "service"},\n {"spec.type": "website"}\n ]},\n {"$not": {"spec.lifecycle": "experimental"}}\n ]\n }\n}\n```\n', responses: { '200': { description: 'Ok', @@ -1392,19 +1410,10 @@ export const spec = { $ref: '#/components/parameters/offset', }, { - $ref: '#/components/parameters/after', + $ref: '#/components/parameters/orderField', }, { - name: 'order', - in: 'query', - allowReserved: true, - required: false, - schema: { - type: 'array', - items: { - type: 'string', - }, - }, + $ref: '#/components/parameters/after', }, ], requestBody: { @@ -1415,7 +1424,7 @@ export const spec = { type: 'object', additionalProperties: false, properties: { - filter: { + query: { $ref: '#/components/schemas/EntityPredicate', }, }, @@ -1423,7 +1432,7 @@ export const spec = { examples: { 'Get all service components': { value: { - filter: { + query: { $all: [ { kind: 'component', @@ -1437,7 +1446,7 @@ export const spec = { }, 'Get components owned by specific teams': { value: { - filter: { + query: { $all: [ { kind: 'component', @@ -1453,7 +1462,7 @@ export const spec = { }, 'Get non-production services': { value: { - filter: { + query: { $all: [ { kind: 'component', diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index e26a0c1233..6af2fc96b0 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -68,6 +68,7 @@ import { encodeLocationQueryCursor, parseLocationQuery, } from './request/parseLocationQuery'; +import { parseEntityOrderFieldParams } from './request/parseEntityOrderFieldParams'; /** * Options used by {@link createRouter}. @@ -261,24 +262,24 @@ export async function createRouter( throw err; } }) - .post('/entities/by-predicates', async (req, res) => { + .post('/entities/by-query', async (req, res) => { const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { - queryType: 'by-predicates', + queryType: 'by-query-predicate', }, }); try { - const filter = req.body.filter as EntityPredicate | undefined; - const order = parseEntityOrderParams(req.query); + const query = req.body.query as EntityPredicate | undefined; + const order = parseEntityOrderFieldParams(req.query); const pagination = parseEntityPaginationParams(req.query); const credentials = await httpAuth.credentials(req); const { entities, pageInfo } = await entitiesCatalog.queryEntitiesByPredicate({ - filter, + query, order, pagination, credentials,