Add fragment-based query utilities for Keystone migration#359
Merged
Conversation
Adds defineFragment, runQuery, and runQueryOne to @opensaas/stack-core.
These utilities provide composable, type-safe query helpers that replace
context.graphql.run() for teams migrating from KeystoneJS:
- defineFragment<T>()({ field: true, rel: nestedFragment }) — creates
a reusable field-selection descriptor with full TypeScript inference
- ResultOf<typeof fragment> — infers the exact result shape, analogous
to gql.tada's ResultOf (no GraphQL codegen step required)
- runQuery(context, listKey, fragment, args?) — executes findMany through
context.db, respecting all access control rules
- runQueryOne(context, listKey, fragment, where) — executes findFirst
Also adds:
- specs/keystone-migration.md — comprehensive migration guide for both
humans and AI agents covering all context.graphql patterns
- docs/content/guides/migration.md — new "Migrating context.graphql.run"
section with before/after examples
- claude-plugins migration skills updated to recommend fragments for
nested data patterns
https://claude.ai/code/session_012ismTCY2S84rCjRWL4jnwU
- Add `context.db.post.findMany({ query: fragment, where, orderBy, take, skip })`
and `context.db.post.findUnique({ where, query: fragment })` as the primary
API for type-safe, fragment-scoped queries
- Add `RelationSelector` type: union of Fragment shorthand or
`{ query, where?, orderBy?, take?, skip? }` for nested relationship filtering
- Export `buildInclude`, `pickFields`, `isFragment` helpers from query module
- Add `AugmentedFindMany`, `AugmentedFindUnique`, `FindManyQueryArgs` types to
access/types.ts and re-export from public API
- Add `orderBy` support to `createFindMany` (previously silently ignored)
- 42 tests covering all new paths: RelationSelector buildInclude, pickFields,
runQuery integration, factory function (variables) pattern, isFragment guard
https://claude.ai/code/session_012ismTCY2S84rCjRWL4jnwU
…gment API
- Primary API is now context.db.post.findMany({ query: fragment, where, orderBy, ... })
and context.db.post.findUnique({ where, query: fragment })
- Document RelationSelector pattern: { query, where?, orderBy?, take?, skip? }
- Document factory function (variables) pattern
- Update changeset to describe full feature set including new primary API
- Update migrate-context-calls and opensaas-migration skills
- Update specs/keystone-migration.md with patterns H (RelationSelector) and I (factory fn)
https://claude.ai/code/session_012ismTCY2S84rCjRWL4jnwU
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: c6f12d8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 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 |
…nique The eslint-disable directive was two lines above the `any` annotation, making it unused (triggering a lint error). Moved it to the line directly above `query?: any`. https://claude.ai/code/session_012ismTCY2S84rCjRWL4jnwU
- Fix `SelectedFields` mapped type: use `[ExtractFragment<T>] extends [never]`
tuple-wrapping to correctly identify scalar fields; previously `never extends
Fragment<any,any>` was vacuously true, causing scalar fields to resolve to
`never` under strict tsc (vitest skips type-checking so this was hidden)
- Fix test: use `null` instead of `undefined` in findFirst mock (undefined is
not assignable to `{} | null`)
- Fix test: remove invalid `commentWithAuthorFrag._fields.post` access (the
fragment was defined without a `post` field)
https://claude.ai/code/session_012ismTCY2S84rCjRWL4jnwU
Contributor
Coverage Report for Core Package Coverage (./packages/core)
File CoverageNo changed files found. |
Contributor
Coverage Report for UI Package Coverage (./packages/ui)
File CoverageNo changed files found. |
Contributor
Coverage Report for CLI Package Coverage (./packages/cli)
File CoverageNo changed files found. |
Contributor
Coverage Report for Auth Package Coverage (./packages/auth)
File CoverageNo changed files found. |
Contributor
Coverage Report for Storage Package Coverage (./packages/storage)
File CoverageNo changed files found. |
Contributor
Coverage Report for RAG Package Coverage (./packages/rag)
File CoverageNo changed files found. |
Contributor
Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)
File CoverageNo changed files found. |
Contributor
Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)
File CoverageNo changed files found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces
defineFragment,runQuery, andrunQueryOne— composable, type-safe query utilities that replacecontext.graphql.run()for developers migrating from KeystoneJS. These utilities provide the same benefits as GraphQL fragments (reusability, type inference, nested relationship support) without requiring a GraphQL runtime.Key Changes
New query module (
packages/core/src/query/index.ts):defineFragment<T>()— factory function to create reusable, typed field selectionsrunQuery()— execute list queries with where/orderBy/pagination, respecting access controlrunQueryOne()— fetch a single record by where clauseResultOf<F>— type helper to infer the exact shape returned by a fragmentQueryArgs— standardized query argument type (where, orderBy, take, skip)Comprehensive test suite (
packages/core/src/query/index.test.ts):Migration documentation:
specs/keystone-migration.md) — 615-line comprehensive guide covering config migration, fragment patterns (A–G), access control, hooks, field mapping, M2M join tables, auth, and a migration checklistdocs/content/guides/migration.mdwith quick reference and examplesrunQueryrecommendationsPublic API export — added to
packages/core/src/index.tsChangeset documenting the feature for release notes
Implementation Details
includemaps and usespickFields()to strip unselected fields from results, ensuring runtime shape matchesResultOftypecontext.db[listKey].findMany/findFirst, so all configured access rules are enforcedMigration Path
Developers migrating from Keystone can now:
defineFragment<T>()(fields)context.graphql.run()calls withrunQuery()orrunQueryOne()ResultOf<typeof fragment>instead of GraphQL codegen typeshttps://claude.ai/code/session_012ismTCY2S84rCjRWL4jnwU