Skip to content

feat(server): Entity Expose API — unified field, filter, sort & relation control#1237

Merged
viniciusdacal merged 6 commits intomainfrom
viniciusdacal/docs-entity-fields
Mar 14, 2026
Merged

feat(server): Entity Expose API — unified field, filter, sort & relation control#1237
viniciusdacal merged 6 commits intomainfrom
viniciusdacal/docs-entity-fields

Conversation

@viniciusdacal
Copy link
Copy Markdown
Contributor

Summary

  • Replaces the old relations config on entities with a unified expose config that controls the entire VertzQL query surface
  • expose.select restricts which fields appear in API responses (with AccessRule descriptor support for conditional visibility)
  • expose.allowWhere / expose.allowOrderBy restrict which fields clients can filter/sort on
  • expose.include controls relation exposure with fractal structure mirroring the DB query API
  • Descriptor-guarded fields return null (not field omission) — typed as T | null in the SDK
  • Full documentation page at guides/server/entity-exposure

Public API Changes

Breaking (pre-v1)

  • EntityConfig.relations → removed, replaced by EntityConfig.expose
  • EntityDefinition.relations → removed, replaced by EntityDefinition.expose

Additions

  • ExposeConfig<TTable, TModel> — top-level expose configuration type
  • RelationExposeConfig<R> — relation-level expose with fractal structure
  • ExposeValidationConfig — interface for VertzQL validation
  • evaluateExposeDescriptors() — pre-evaluates AccessRule descriptors per request
  • nullGuardedFields() — sets descriptor-guarded fields to null in responses
  • applySelect() signature widened to Record<string, unknown>

Backwards compatible

  • Entities without expose continue to work as before (all non-hidden fields exposed)
  • Old include config shape ({ select: { field: true }, allowWhere, allowOrderBy }) works through expose.include

Phases

  1. Core types, factory, runtimeExposeConfig/RelationExposeConfig types, entity factory update, expose.select enforcement in CRUD pipeline, entity-level allowWhere/allowOrderBy validation in VertzQL parser
  2. Descriptor runtime evaluationevaluateExposeDescriptors() engine, nullGuardedFields(), route-generator wiring for per-request descriptor evaluation
  3. Documentation — Full entity-exposure.mdx guide covering select, filters, descriptors, null semantics, fractal structure

Known limitations

  • Relation-level AccessRule descriptors are not evaluated at runtime yet (entity-level only). Tracked for follow-up.

Test plan

  • 381 entity tests pass (0 failures)
  • Type-level tests for ExposeConfig generic inference, PublicColumnKeys, allowWhere/allowOrderBy field validity
  • expose.select restricts fields across all CRUD operations (list, get, create, update)
  • Entity-level allowWhere/allowOrderBy validation returns 400 for disallowed fields
  • Descriptor-guarded fields return null when descriptor fails
  • Evaluated descriptors override static validation for where/orderBy
  • Backwards compatibility: entities without expose expose all non-hidden fields
  • Server typecheck clean

🤖 Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 14, 2026

Codecov Report

❌ Patch coverage is 95.86466% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/server/src/entity/expose-evaluator.ts 86.58% 11 Missing ⚠️

📢 Thoughts on this report? Let us know!

vertz-dev-front[bot] and others added 6 commits March 14, 2026 09:56
…port

Replace `relations` config on EntityConfig/EntityDefinition with a new
`expose` API that unifies field exposure, relation config, filter/sort
allowlists, and AccessRule descriptors into a fractal structure mirroring
the DB query API shape (select, allowWhere, allowOrderBy, include).

- Add ExposeConfig and RelationExposeConfig types with full generics
- select values accept `true | AccessRule` for descriptor-guarded fields
- include constrains to model relation keys with typed target table columns
- Nested include supports recursive relation exposure
- Update entity() factory, crud-pipeline, route-generator, vertzql-parser
- Update extractAllowKeys() to handle object-shaped allowWhere/allowOrderBy
- Comprehensive type-level tests in expose-types.test-d.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…vel allowWhere/allowOrderBy validation, POCs

- Apply expose.select in crud-pipeline to restrict response fields (list, get, create, update)
- Add entity-level allowWhere/allowOrderBy validation to validateVertzQL()
- Wire expose config through route-generator to validateVertzQL()
- Export ExposeValidationConfig from vertzql-parser
- POC #1: allowWhere/allowOrderBy constrained to PublicColumnKeys at type level,
  subset-of-select enforced at runtime (fallback approach — avoids generic bloat)
- POC #2: T | null typing for descriptor-guarded fields via conditional mapped type
- Update design doc with POC results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add expose-evaluator.ts with evaluateExposeDescriptors() that pre-evaluates
  AccessRule descriptors once per request, producing static field sets
- Descriptor-guarded select fields return null when user lacks access
- Descriptor-guarded allowWhere/allowOrderBy fields reject with "not filterable"/"not sortable"
- Add nullGuardedFields() to field-filter for nulling descriptor-denied fields
- Wire evaluation through route-generator for all CRUD handlers
- Add EvaluatedExposeValidation to validateVertzQL() for dynamic field checks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (Phase 3)

- New page guides/server/entity-exposure.mdx covering expose config:
  select, allowWhere, allowOrderBy, include, descriptors, null semantics
- Add navigation entry in docs.json after entities
- Add card and feature row in server overview
- Cross-reference from entities.mdx to new guide

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…safe cast

- Change applySelect to accept Record<string, unknown> (it only checks
  key presence, not values — the Record<string, true> type was a lie
  when expose.select contains AccessRule descriptors)
- Remove exposeSelect cast to Record<string, true> in crud-pipeline
- Remove duplicate EvaluatedExposeValidation type — import EvaluatedExpose
  from expose-evaluator instead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant