Repository navigation
feat(core): declarative migrations, so content queries span a family's versions - #449
Merged
Merged
Conversation
…s versions A migration is now a list of declared steps — rename, map, add, drop, derive — instead of a function. migration() checks them against both handles' schemas when it runs, and keeps the field origins they imply. A collection uses those origins to carry a content filter or sort into each older version's shape, sent as filter.contentByType and sort.contentFieldByType. A version that does not translate is narrowed and reported in skipped.older, as before. Both shapes are capped by new capabilities; local adapters declare 64, and a server declaring 0 gets the handle-only query. Spec, conformance fixtures, MemoryAdapter, sqlite-shared and the wire parser move together. Closes #448 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LREkawquacdfohXcxKRYWa
🦋 Changeset detectedLatest commit: a40cee0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 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 |
…sions Review on #449: - A field the handle declares and no step produces is read under its own name at an older version, since migrate() carries every field no step names; only a name a step removed is "no value". An older version that gained the field in place was otherwise silently dropped from a filter and sorted last. - A content query's cursor now carries the versions its first page chose, so a version defined mid-pagination cannot change the sort map a cursor resumes, and a later page no longer lists types before querying. - Shorter comments, one withoutContent() helper, validate.ts's isPlainObject() reused, and no "legacy" field in tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LREkawquacdfohXcxKRYWa
Merged
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
Closes #448.
migration(from, to, [rename, map, add, drop, derive])buildsmigrate()from the steps and keeps them on theMigration. It checks them against both handles' schemas at construction: a step naming a field that isn't there, a carried field the target would refuse, or a required field nothing produces throwsStackMigrationError.deriveis the one opaque step (type changes, splits, computed values); its output is still validated at write time.renameandaddtranslate;maptranslates filters (not sorts);derivenarrows. Narrowed versions setskipped.olderas feat(core): type a collection's content filters and sorts from its content type #447 did; newer versions stay out of reach.filter.contentByType(a flat content arm pertypeId) andsort.contentFieldByType({ field }or a pre-reduced{ value }pertypeId). Each is gated by a capability that is a cap on keys;0declines it, and the collection falls back to feat(core): type a collection's content filters and sorts from its content type #447's narrowing. Local adapters declare64.MemoryAdapter,sqlite-shared(both SQLite adapters),parseQueryBody(),normalizeCapabilities();adapter-apicarries both in the POST body unchanged.Spec
docs/spec/data-model.md— new § Migration steps, § Content queries across versions, § Content by type; § Type migrations, § Type handles, § Sorting and pagination, § Sorting by a content field, § Capability-gated filters updated.docs/spec/adapters.md§ Adapter capabilities — the two caps and the local-required rule.docs/spec/wire-format.md— discovery example, § Records (body-only shapes and their 400s), § Bounding query cost.apps.mdandversioning.md("migrations", not "migration functions").Verification
All six from AGENTS.md pass locally:
format:check,check:refs,lint,test(3,209 tests),build,typecheck.New coverage: step construction checks and origin composition (
type-handle,migration-stepstests); collection translation across rename/map/add/derive, a three-version chain, pagination under a translated sort, cap fallback and hand-built migrations, onStackandScopedStack; validation of both shapes; conformance-suite cases for both shapes (pass on MemoryAdapter and native SQLite); wire fixtures for both shapes and twobad_requestcases.Notes for reviewers
sort.contentFieldByTypemirroring the filter, rather than overloadingcontentFieldwith a map — every reader ofcontentFieldtreats it as a string.{}matches every record at that typeId, no wider thantypeIdalone); an empty map is refused. Anaddconstant that matches a filter needs this.mapkeeps unlisted values, somap('status', { done: 'finished' })has two v1 sources for'finished'. The arm asks for'done'; the first page probes for the other source and setsskipped.olderon a match, rather than narrowing the issue's headline case. A second such key in one arm narrows.Migrationnow requiressteps; one built by hand has no origins and is narrowed.🤖 Generated with Claude Code
https://claude.ai/code/session_01LREkawquacdfohXcxKRYWa
Generated by Claude Code