refactor(api): regenerate OpenAPI models for updated predicate-based endpoint

- Update `/by-predicate` endpoint name to POST `/by-query`
- regenerate openapi models to fix generic type names

Signed-off-by: Nilay1999 <nilayparmar19@gmail.com>
This commit is contained in:
Nilay1999
2026-01-22 22:39:42 +05:30
committed by benjdlambert
parent ebeebd9012
commit 8ddce23115
33 changed files with 777 additions and 223 deletions
@@ -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<string>;
};
};
/**
* @public
*/
export type QueryEntitiesByPredicate = {
body: QueryEntitiesByPredicateRequest;
query: {
limit?: number;
offset?: number;
orderField?: Array<string>;
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 &#x60;orderField&#x60; query parameters to affect that ordering. For example, to return entities by their name: &#x60;/entities/by-query?orderField&#x3D;metadata.name,asc&#x60; Each parameter can be followed by &#x60;asc&#x60; for ascending lexicographical order or &#x60;desc&#x60; for descending (reverse) lexicographical order.
* @param after - Pointer to the previous page of results.
*/
public async queryEntitiesByPredicate(
// @ts-ignore
request: QueryEntitiesByPredicate,
options?: RequestOptions,
): Promise<TypedResponse<QueryEntitiesByPredicate200Response>> {
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 -
@@ -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 };
@@ -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<EntityPredicate>;
}
@@ -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<EntityPredicate>;
}
@@ -19,8 +19,9 @@
// ******************************************************************
/**
* Check if field exists
* @public
*/
export interface EntityPredicateValueOneOf {
export interface EntityPredicateExists {
$exists: boolean;
}
@@ -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<EntityPredicateInInInner>;
}
@@ -21,4 +21,4 @@
/**
* @public
*/
export type EntityPredicateValueOneOf1InInner = boolean | number | string;
export type EntityPredicateInInInner = boolean | number | string;
@@ -20,8 +20,9 @@
import { EntityPredicate } from '../models/EntityPredicate.model';
/**
* Negates the condition
* @public
*/
export interface EntityPredicateOneOf2 {
export interface EntityPredicateNot {
$not: EntityPredicate;
}
@@ -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;
@@ -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<Entity>;
pageInfo: GetEntitiesByPredicates200ResponsePageInfo;
pageInfo: QueryEntitiesByPredicate200ResponsePageInfo;
}
@@ -21,7 +21,7 @@
/**
* @public
*/
export interface GetEntitiesByPredicates200ResponsePageInfo {
export interface QueryEntitiesByPredicate200ResponsePageInfo {
/**
* The cursor for the next batch of entities.
*/
@@ -22,6 +22,6 @@ import { EntityPredicate } from '../models/EntityPredicate.model';
/**
* @public
*/
export interface EntityPredicateOneOf1 {
$any: Array<EntityPredicate>;
export interface QueryEntitiesByPredicateRequest {
query?: EntityPredicate;
}
@@ -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';
+27 -1
View File
@@ -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;
@@ -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';
@@ -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<EntityPredicate>;
}
/**
* At least one condition must match (OR logic).
*
* @public
*/
export interface EntityPredicateAny {
$any: Array<EntityPredicate>;
}
/**
* 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<string | number | boolean>;
}
+70 -63
View File
@@ -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
@@ -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<Entity> | Error | Error;
};
/**
* @public
*/
export type GetEntitiesByPredicates = {
body: GetEntitiesByPredicatesRequest;
query: {
limit?: number;
offset?: number;
after?: string;
order?: Array<string>;
};
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<string>;
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;
@@ -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
@@ -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<EntityPredicate>;
}
@@ -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<EntityPredicate>;
}
@@ -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;
}
@@ -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<EntityPredicateInInInner>;
}
@@ -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;
@@ -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;
}
@@ -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;
@@ -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<EntityPredicateValueOneOf1InInner>;
}
@@ -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<Entity>;
pageInfo: QueryEntitiesByPredicate200ResponsePageInfo;
}
@@ -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;
}
@@ -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;
}
@@ -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';
@@ -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',
@@ -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,