Repository navigation
Stack checks a typed collection's write expectation #408
Description
Problem
The typed collection planned in #409 must refuse a write to a record outside its Type family, and must notice when a content write targets a record stored at an older version of the family (a patch is validated against the record's stored Type).
From outside Stack, both need a read first. For content writes that's the read typedMutate() does today. But associate(), grantAccess() and the other content-free verbs only learn the Type from the record the write returns, so the alternative is throwing after the write has landed. A prototype collection built on the public StackClient API had to choose between an extra round trip per write (the cost typed verbs are supposed to remove) and "…not in the book collection. The write was applied."
Decision
The collection passes Stack an internal write expectation: the handle's family on every write, plus its exact typeId on a write that touches content. Stack checks it against the record it already reads before every write, the read that confirms the record exists, isn't deleted, and that permissions allow the write. A collection write then costs exactly what the untyped write costs.
- A family mismatch is refused with
StackNotFoundError, before anything is written. A typedget()of another family's ID answersnull, so "Book not found" is the same answer from the write side. - A version mismatch on a content write is refused with an internal error carrying the stored record. The collection catches it to migrate the record and then patch, as decided in Typed access through collections, with records that don't fit reported rather than thrown #409.
- Not on the wire. A record's family never changes:
typeIdmoves only through a migration, and a migration stays within the family. So the check can't race and needs no server-side counterpart, unlikeifVersion, which needsIf-Match. OverAPIAdapter, the check runs in the client'sStackagainst the read it already makes. - Not public API. The expectation travels under a module-private symbol key on the write verbs' options, so it never appears on
StackClient's visible surface.
Rejected
- A public
ifBaseIdoption on every write. It adds an option to about a dozen verbs and their spec, for untyped callers who would be better served by a collection. - Reading first in the collection. Duplicates
Stack's own read on every write. - Checking the record a write returns. Throws only after the write has landed.
Actions
- A module-private symbol key accepted on the options of every write verb (
mutate,patchContent,associate,dissociate,amendAssociations,grantAccess,revokeAccess,amendAccess,delete,undelete,restoreVersion), carrying aBaseIdand optionally an exactTypeId. -
Stackchecks it against the record it reads before writing:StackNotFoundErroron a family mismatch; the internal stored-version error, carrying the stored record, on atypeIdmismatch for a write that touches content. -
ScopedStackpasses it through toStack. - Tests (importing the symbol from its module): a mismatched family is refused before writing for every write verb, on
StackandScopedStack, with version and journal unchanged; a version mismatch carries the stored record; a write carrying an expectation makes no more adapter reads than one without. - Changeset:
patchfor@haverstack/core(nothing a consumer can observe until the collection uses it).
Order
Before #409, so the collection lands without extra reads. Independent of #407; both must land before #409. It has no public surface of its own, so it's safe to merge ahead of its consumer.
Activity