diff --git a/packages/entities/entities-routes/package.json b/packages/entities/entities-routes/package.json
index 134dde872e..1d6b4cba2f 100644
--- a/packages/entities/entities-routes/package.json
+++ b/packages/entities/entities-routes/package.json
@@ -37,6 +37,7 @@
"@kong/kongponents": "9.32.6",
"@types/lodash.isequal": "^4.5.8",
"axios": "^1.7.7",
+ "js-yaml": "^4.1.0",
"vite-plugin-monaco-editor": "^1.1.0",
"vue": "^3.5.13",
"vue-router": "^4.4.5"
diff --git a/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue b/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue
index f8d24825ea..bc5dd8be3f 100644
--- a/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue
+++ b/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue
@@ -27,6 +27,13 @@
Konnect Actions Outside
+ Declarative Config
+
+
Konnect API
diff --git a/packages/entities/entities-shared/src/composables/index.ts b/packages/entities/entities-shared/src/composables/index.ts
index 8810f51680..5711edb919 100644
--- a/packages/entities/entities-shared/src/composables/index.ts
+++ b/packages/entities/entities-shared/src/composables/index.ts
@@ -13,11 +13,13 @@ import useTruncationDetector from './useTruncationDetector'
import useValidators from './useValidators'
import { useSchemaProvider, useSubSchema } from './useSchema'
import useTableState from './useTableState'
+import { useDeclarativeRoutesFetcher } from './useDeclarativeFetcher'
// All composables must be exported as part of the default object for Cypress test stubs
export default {
useAxios,
useDebouncedFilter,
+ useDeclarativeRoutesFetcher,
useDeleteUrlBuilder,
useErrors,
useExternalLinkCreator,
diff --git a/packages/entities/entities-shared/src/composables/useDeclarativeFetcher.ts b/packages/entities/entities-shared/src/composables/useDeclarativeFetcher.ts
new file mode 100644
index 0000000000..3e11a183ee
--- /dev/null
+++ b/packages/entities/entities-shared/src/composables/useDeclarativeFetcher.ts
@@ -0,0 +1,117 @@
+import type { TableDataFetcherParams } from '@kong/kongponents'
+import { ref, toValue, type MaybeRefOrGetter } from 'vue'
+import { FetcherStatus, type DeclarativeConfig, type DeclarativeRoute, type DeclarativeRouteSortableKey, type FetcherResponse, type FetcherState } from '../types'
+import { useFetcherCacheKey } from './useFetcher'
+
+export function useDeclarativeRoutesFetcher(
+ config: MaybeRefOrGetter,
+ cacheIdentifier: string,
+ filterFn: (route: DeclarativeRoute) => boolean,
+) {
+ const state = ref({
+ status: FetcherStatus.Idle,
+ })
+ const fetcherCacheKey = useFetcherCacheKey(cacheIdentifier)
+
+ const fetcher = async (fetcherParams: TableDataFetcherParams): Promise => {
+ // Note: Declarative fetchers do not have an InitialLoad status
+ state.value = { status: FetcherStatus.Loading }
+
+ try {
+ const configValue = toValue(config)
+ if (!configValue) {
+ const response: FetcherResponse = {
+ data: [],
+ total: 0,
+ }
+
+ state.value = {
+ status: FetcherStatus.NoRecords,
+ response,
+ }
+
+ return response
+ }
+
+ let results = configValue.routes.filter(filterFn)
+
+ if (fetcherParams.sortColumnKey) {
+ const sortKey = fetcherParams.sortColumnKey as DeclarativeRouteSortableKey
+ switch (sortKey) {
+ case 'name':
+ results = results.sort((a, b) =>
+ fetcherParams.sortColumnOrder === 'desc'
+ ? b.name.localeCompare(a.name)
+ : a.name.localeCompare(b.name),
+ )
+ break
+ case 'match':
+ results = results.sort((a, b) =>
+ fetcherParams.sortColumnOrder === 'desc'
+ ? b.match.path.localeCompare(a.match.path)
+ : a.match.path.localeCompare(b.match.path),
+ )
+ break
+ default:
+ throw new Error(`Unsupported sortColumnKey "${fetcherParams.sortColumnKey}"`)
+ }
+ }
+
+ let pagination: FetcherResponse['pagination'] | undefined = undefined
+ if (fetcherParams.pageSize) {
+ const pageSize = fetcherParams.pageSize
+ if (pageSize < 1 || !Number.isInteger(pageSize)) {
+ throw new Error(`Expected pageSize to be a positive integer, got ${fetcherParams.pageSize}`)
+ }
+
+ const page = fetcherParams.page ?? 1
+ if (page < 1 || !Number.isInteger(page)) {
+ throw new Error(`Expected page to be a positive integer, got ${page}`)
+ }
+
+ pagination = { offset: 'page' }
+
+ results = results.slice((page - 1) * pageSize, page * pageSize)
+ }
+
+ const response = {
+ data: results,
+ total: configValue.routes.length,
+ pagination,
+ }
+
+ if (results.length === 0 && !fetcherParams.query) {
+ state.value = {
+ status: FetcherStatus.NoRecords,
+ response,
+ }
+ } else {
+ state.value = {
+ status: FetcherStatus.Idle,
+ response,
+ }
+ }
+
+ return response
+ } catch (error: any) {
+ const response: FetcherResponse = {
+ data: [],
+ total: 0,
+ }
+
+ state.value = {
+ status: FetcherStatus.Error,
+ response,
+ error: error.response ? error : { response: error },
+ }
+
+ return response
+ }
+ }
+
+ return {
+ fetcher,
+ fetcherState: state,
+ fetcherCacheKey,
+ }
+}
diff --git a/packages/entities/entities-shared/src/index.ts b/packages/entities/entities-shared/src/index.ts
index 0bce1608ae..1a17a7265d 100644
--- a/packages/entities/entities-shared/src/index.ts
+++ b/packages/entities/entities-shared/src/index.ts
@@ -18,13 +18,65 @@ import TableTags from './components/common/TableTags.vue'
import composables from './composables'
// Extract specific composables to export
-const { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useFetcherCacheKey, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators, useSchemaProvider, useTableState } = composables
+const {
+ useAxios,
+ useDebouncedFilter,
+ useDeclarativeRoutesFetcher,
+ useDeleteUrlBuilder,
+ useErrors,
+ useExternalLinkCreator,
+ useFetcher,
+ useFetcherCacheKey,
+ useFetchUrlBuilder,
+ useGatewayFeatureSupported,
+ useHelpers,
+ useSchemaProvider,
+ useStringHelpers,
+ useTableState,
+ useTruncationDetector,
+ useValidators,
+} = composables
// Components
-export { EntityBaseConfigCard, ConfigCardItem, ConfigCardDisplay, InternalLinkItem, EntityBaseForm, EntityBaseTable, EntityDeleteModal, EntityFilter, EntityToggleModal, PermissionsWrapper, EntityFormSection, EntityLink, EntityEmptyState, JsonCodeBlock, TerraformCodeBlock, YamlCodeBlock, TableTags }
+export {
+ ConfigCardDisplay,
+ ConfigCardItem,
+ EntityBaseConfigCard,
+ EntityBaseForm,
+ EntityBaseTable,
+ EntityDeleteModal,
+ EntityEmptyState,
+ EntityFilter,
+ EntityFormSection,
+ EntityLink,
+ EntityToggleModal,
+ InternalLinkItem,
+ JsonCodeBlock,
+ PermissionsWrapper,
+ TableTags,
+ TerraformCodeBlock,
+ YamlCodeBlock,
+}
// Composables
-export { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useFetcherCacheKey, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators, useSchemaProvider, useTableState }
+export {
+ useAxios,
+ useDebouncedFilter,
+ useDeclarativeRoutesFetcher,
+ useDeleteUrlBuilder,
+ useErrors,
+ useExternalLinkCreator,
+ useFetcher,
+ useFetcherCacheKey,
+ useFetchUrlBuilder,
+ useGatewayFeatureSupported,
+ useHelpers,
+ useSchemaProvider,
+ useStringHelpers,
+ useTableState,
+ useTruncationDetector,
+ useValidators,
+}
// Types
export * from './types'
diff --git a/packages/entities/entities-shared/src/types/declarative-config.ts b/packages/entities/entities-shared/src/types/declarative-config.ts
new file mode 100644
index 0000000000..0f100d72fc
--- /dev/null
+++ b/packages/entities/entities-shared/src/types/declarative-config.ts
@@ -0,0 +1,41 @@
+export interface DeclarativePolicy {
+ name: string
+ id?: string
+ condition?: string
+ config: Record
+}
+
+export interface DeclarativeListener {
+ port: number
+ protocol: string
+ policies: DeclarativePolicy[]
+}
+
+export interface DeclarativeService {
+ name: string
+ policies: DeclarativePolicy[]
+}
+
+export interface DeclarativeRouteMatch {
+ path: string
+}
+
+export interface DeclarativeRoute {
+ name: string
+ match: DeclarativeRouteMatch
+ policies: DeclarativePolicy[]
+}
+
+export interface DeclarativeWasmComponent {
+ name: string
+ binary: string // base64 encoded binary
+}
+
+export interface DeclarativeConfig {
+ listeners: DeclarativeListener[]
+ routes: DeclarativeRoute[]
+ services: DeclarativeService[]
+ wasm_components?: DeclarativeWasmComponent[]
+}
+
+export type DeclarativeRouteSortableKey = keyof Pick
diff --git a/packages/entities/entities-shared/src/types/entity-base-table.ts b/packages/entities/entities-shared/src/types/entity-base-table.ts
index 4668299f3a..87b8f23195 100644
--- a/packages/entities/entities-shared/src/types/entity-base-table.ts
+++ b/packages/entities/entities-shared/src/types/entity-base-table.ts
@@ -1,6 +1,6 @@
+import type { TableState } from '@kong/kongponents/dist/types'
import type { RouteLocationRaw } from 'vue-router'
import type { Field, KongManagerConfig, KonnectConfig } from './index'
-import type { TableState } from '@kong/kongponents/dist/types'
export interface KonnectBaseTableConfig extends KonnectConfig {
/** Additional message to show when there are no records */
diff --git a/packages/entities/entities-shared/src/types/entity-filter.ts b/packages/entities/entities-shared/src/types/entity-filter.ts
index 3f6a2a286c..be751c2dc0 100644
--- a/packages/entities/entities-shared/src/types/entity-filter.ts
+++ b/packages/entities/entities-shared/src/types/entity-filter.ts
@@ -44,3 +44,6 @@ export interface FuzzyMatchFilterConfig extends BaseFilterConfig {
/** Fuzzy match filter schema */
schema: FilterSchema
}
+
+/** Structured filters for fuzzy matching */
+export type FuzzyMatchFilters = Record
diff --git a/packages/entities/entities-shared/src/types/index.ts b/packages/entities/entities-shared/src/types/index.ts
index 9567cfdf50..4a47afee4d 100644
--- a/packages/entities/entities-shared/src/types/index.ts
+++ b/packages/entities/entities-shared/src/types/index.ts
@@ -4,6 +4,7 @@
// Example:
export * from './app-config'
export * from './base'
+export * from './declarative-config'
export * from './entity-delete-modal'
export * from './entity-base-form'
export * from './entity-base-table'
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 812be6a943..f4ee277c1d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1071,6 +1071,9 @@ importers:
axios:
specifier: ^1.7.7
version: 1.7.7
+ js-yaml:
+ specifier: ^4.1.0
+ version: 4.1.0
vite-plugin-monaco-editor:
specifier: ^1.1.0
version: 1.1.0(monaco-editor@0.52.2)
@@ -1903,6 +1906,7 @@ packages:
'@evilmartians/lefthook@1.8.2':
resolution: {integrity: sha512-SZdQk3W9q7tcJwnSwEMUubQqVIK7SHxv52hEAnV7o3nPI+xKcmd+rN0hZIJg07wjBaJRAjzdvoQySKQQYPW5Qw==}
+ cpu: [x64, arm64, ia32]
os: [darwin, linux, win32]
hasBin: true
@@ -8270,7 +8274,7 @@ packages:
right-pad@1.0.1:
resolution: {integrity: sha512-bYBjgxmkvTAfgIYy328fmkwhp39v8lwVgWhhrzxPV3yHtcSqyYKe9/XOhvW48UFjATg3VuJbpsp5822ACNvkmw==}
engines: {node: '>= 0.10'}
- deprecated: Use String.prototype.padEnd() instead
+ deprecated: Please use String.prototype.padEnd() over this package.
rimraf@2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}