feat(filters): JSONPath field filters, operators, AST groups, fragments, and test-match

Add a field filter type reading any payload value by JSONPath with array expansion, 12 comparison operators (eq/ne/contains/startsWith/endsWith/regex/gt/gte/lt/lte/in/exists), a visual AST builder (all/any/not) in the route editor, chip-based multi-value input, a stateless POST /admin/api/test-match dry-run, and named filter fragments stored in D1 (d1_fragments, migration 0010) inlined into route ASTs on insert.
This commit is contained in:
RhenCloud 2026-08-18 12:19:08 +08:00
parent c955db03c3
commit c090281cb2
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
30 changed files with 1793 additions and 422 deletions

View file

@ -35,18 +35,10 @@
</div>
<div class="route-card-filters">
<span
v-for="(f, i) in route.filters"
:key="i"
class="route-chip"
:class="{ exclude: f.exclude }"
>
<span class="route-chip-type"
>{{ f.exclude ? t("routeEditor.not") + " " : "" }}{{ t("filter." + f.type) }}</span
>
<span class="route-chip-val">{{ fmtMatch(f.match) }}</span>
<span v-if="summary" class="route-chip">
<span class="route-chip-val">{{ summary }}</span>
</span>
<span v-if="!route.filters.length" class="route-chip route-chip-empty">
<span v-else class="route-chip route-chip-empty">
<span class="route-chip-type">{{ t("route.noFilters") }}</span>
</span>
</div>
@ -151,8 +143,8 @@
</template>
<script setup lang="ts">
import type { Route } from "~/types";
import { fmtMatch } from "~/types";
import type { FilterNode, Route } from "~/types";
import { describeNode } from "~/composables/useFilterNode";
const { t } = useI18n();
@ -162,6 +154,11 @@ const props = defineProps<{
atLast?: boolean;
readonly?: boolean;
}>();
const summary = computed(() => {
const node: FilterNode = props.route.ast ?? { all: props.route.filters };
return describeNode(node, t);
});
const emit = defineEmits<{
(e: "toggle", route: Route): void;
(e: "edit", route: Route): void;