Repository navigation
feat(core): typed access through collections, with misfits reported - #446
Merged
Merged
Conversation
client.collection(handle) replaces the handle-first overloads on StackClient. A collection spans the handle's whole family, migrates each record in memory to the handle's version, and returns a typed record from every write verb without a re-read. A record stored at a newer version is a misfit: query() reports it beside the page, get() throws MisfitError, subscribe() carries it as `misfit`. A content write to an older record commits its migration as its own write first. Enums keep reading as their listed values or UnlistedValue, as settled in #433, so 'newer-version' is the only misfit reason. Closes #409 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt3xYmwF4FW5kEUAPqgjYo
🦋 Changeset detectedLatest commit: 340b91b The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 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 |
From review of #446: - subscribe() delivers a change that migrates a record out of the family without its record, instead of reading it as a newer-version misfit, and delivers a change whose migration function throws the same way. - A restoreVersion() under a write expectation refuses a snapshot from another family before writing, as a version not found. - query() drops typeId and presentAt from an untyped caller. - Spec: content filters and sorts apply to content as stored; a failed patch after a migration leaves the migration; the 500 row names the collection's StackMigrationError sources. - Changeset names @haverstack/commons, whose README and source changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt3xYmwF4FW5kEUAPqgjYo
5 tasks
From a second review of #446: - PatchOf is deleted. A collection's patch type is ContentPatch<C>, derived from its content type, and the spec now names it and StoredContent<C> alongside ContentOf and StoredContentOf. - A collection's subscribe() drops a filter's typeId from an untyped caller, as query() does, instead of narrowing the family to one version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt3xYmwF4FW5kEUAPqgjYo
This was referenced Oct 11, 2026
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 #409.
client.collection(handle)returns aCollection<C>typed by the handle's content, with one signature per verb:get,query,subscribe,create,mutate,patchContent,associate,dissociate,amendAssociations,grantAccess,revokeAccess,amendAccess,delete,undeleteandrestoreVersion. The handle-first overloads onStackClientare deleted, along withTypedRecord,TypedQuery,TypedChange,TypedChangeSet,TypedSubscribeOptions, thetyped*helpers andisTypeHandle.Stack's registry. It does not go throughpresentAt: 'latest'.query()'smisfits, and the page and cursor survive.get()throwsMisfitError, andsubscribe()setsmisfiton the change.MisfitErrorsits outside theStackErrortaxonomy.StackNotFoundErrorvia Stack checks a typed collection's write expectation #408's write expectation, andget()of the same ID answersnull.commitMigration(), then applies the patch, so the journal readsmigratethenpatch. The caller'sifVersionfences the migration, and the patch is fenced to the version the migration produced. A caller who may not migrate getsStackPermissionErrornaming the stored version, and nothing is written.collection()refuses a handle whosemigratesFromhas no registered migration.ExpectingClienttype inwrite-expectation.tsreplaces theas nevercasts.Spec
docs/spec/data-model.md § Type handlesis rewritten around the collection: scope, verbs, misfits, short pages, writes outside the family, and migrate-then-patch.docs/spec/data-model.md § Type migrationsgains a sentence on a collection's patch to an older record.docs/spec/wire-format.md § The taxonomy rootnow listsMisfitError.Verification
pnpm run format:check,check:refs,lint,test,buildandtypecheckall pass. The newpackages/core/tests/collection.test.tsruns against bothStackandScopedStackand covers everything under the issue's "Prototype results", plus:get()of another Type's ID isnullCollectionRecord<C>collection()throws when a migration is missingwriteis refused the migrate-then-patchNotes for reviewers
unknown-enummisfit. feat(core): make enum its own field kind #433, which merged after the issue was written, settled that reads type an enum as its listed values orUnlistedValue, so a record holding an unlisted value is an ordinary record. String enums can be declared open #411's premise needs revisiting as a result.Collection<C>is typed by write content (ContentOf<S>). The read content (StoredContent<C>) and the patch shape (ContentPatch<C>) are worked out fromC, so an app'sinterfacename is kept.CollectionQuery<C>doesn't useCyet; it is reserved for Collection filters and sorts are checked against the content type #410.recordinstead of throwing in the emitter.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nt3xYmwF4FW5kEUAPqgjYo
Generated by Claude Code