feat(content): support indexed custom field sorting - #2212
Conversation
🦋 Changeset detectedLatest commit: 4e682e7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Scope checkThis PR changes 920 lines across 35 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
a3f5bcc to
9509332
Compare
9509332 to
3a15f15
Compare
3a15f15 to
138f0cd
Compare
138f0cd to
33ed15e
Compare
There was a problem hiding this comment.
The server-side sorting approach is sound: it adds physical SQLite indexes on real ec_* columns for scalar field types, keeps pagination cursor-stable with explicit null ordering, and validates the indexed flag against a whitelist of indexable types. That fits EmDash’s schema-in-the-database architecture.
Two things block a clean sign-off:
-
AGENTS.md Discussion requirement. The PR introduces a new user-facing feature (indexed custom-field sorting) but the description explicitly states that the linked Discussion #1717 only covers
admin.listColumns(#2194) and “no Discussion has been opened for indexed sorting yet.” EmDash requires a maintainer-approved Discussion before merging a feature. This needs to be opened/approved before merge. -
FieldEditor bug when changing an indexed field to a non-indexable type. The editor sends
indexed: undefinedwhen the selected type is not indexable. The backend interpretsundefinedas “preserve existing value,” so an already-indexed field that is changed to a non-indexable type (e.g.string→text) fails withFIELD_NOT_INDEXABLEinstead of dropping the index. Fix by explicitly sendingfalsefor non-indexable types.
Other notes:
- The diff still includes the stacked #2194
admin.listColumnscommits/changeset. Review comments here focus on the indexed-sorting additions; the list-columns pieces should land via #2194. - Server support for custom-field sorting is complete, but the admin content list does not yet expose sort controls on custom columns (the new columns are display-only). If admin UI for selecting a custom sort is intended for this PR, it is missing.
- I did not run tests/lint/typecheck (no shell/tooling). Test coverage looks reasonable for null ordering, equal-value stability, unindexed-field rejection, and index lifecycle; the query-plan assertions are brittle but directly verify the stated performance goal.
| required, | ||
| unique, | ||
| searchable: isSearchableType ? searchable : undefined, | ||
| indexed: isIndexableType ? indexed : undefined, |
There was a problem hiding this comment.
[needs fixing] When the selected type is not indexable, this sends indexed: undefined. In updateField the backend computes nextIndexed = input.indexed ?? field.indexed, so an already-indexed field that is changed to a non-indexable type (e.g. string → text, same column type) keeps indexed = true and assertIndexableField throws FIELD_NOT_INDEXABLE. The UI gives the user no way to recover because the Indexed switch has disappeared.
Send false explicitly for non-indexable types so the index is dropped and the type change can succeed:
| indexed: isIndexableType ? indexed : undefined, | |
| indexed: isIndexableType ? indexed : false, |
| "@emdash-cms/admin": minor | ||
| --- | ||
|
|
||
| Adds opt-in database indexes for scalar custom fields and stable cursor pagination when ordering content lists by those fields. |
There was a problem hiding this comment.
[needs fixing] This changeset describes a new user-facing feature, but AGENTS.md requires a prior maintainer-approved Discussion for features. The PR description acknowledges that Discussion #1717 covers admin.listColumns (#2194) only and that no Discussion has been opened for indexed custom-field sorting yet. Please open and get approval for a Discussion covering this feature before merging.
What does this PR do?
Adds server-backed sorting for custom scalar fields that are explicitly marked
indexed.Collection fields gain an opt-in
indexedflag. EmDash validates that flag against supported scalar field types, maintains a normalized index table when content changes, and allows the existingorderBycontract to sort by those field slugs. Sorting remains database-backed and cursor-safe, so it applies to the complete result set instead of only the currently loaded admin page.This enables uses such as SEO score, ticket priority, workflow rank, or other numeric and textual metadata without introducing full-table scans.
This PR is stacked on #2194 to keep the five related content-list contributions conflict-free and reviewable at the same time. #2194 should merge first; GitHub will then narrow this PR to its unique sorting commit automatically.
Addresses the structured sorting portion of #2179.
Discussion: #1717
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runadmin.listColumns(feat(admin): show configured fields in content lists #2194); no Discussion has been opened for indexed sorting yetAI-generated code disclosure
Screenshots / test output
Validated on EmDash 0.31 with Node 24 in WSL2:
pnpm typecheckpnpm lint:quickpnpm format:checkpnpm --filter emdash --filter @emdash-cms/admin build