Docs: DocumentPayload stops direct reads and assignment, not runtime narrowing or a cast

The cultcache-ts README, the DocumentPayload comment (comment only) and the cultmesh-browser README say plainly what the
opaque type guarantees and what it does not; documentPayloadBytes is the escape for bytes.

All commits build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 20:37:29 +02:00
commit b25d86a873
3 changed files with 16 additions and 1 deletions

View file

@ -39,6 +39,14 @@ realtime frame. At run time it is the same `Uint8Array`, so nothing on the wire
time it is opaque: it is assignable to and from neither `Uint8Array` nor `ArrayLike<number>`, so no
MessagePack decoder accepts it.
What the type guarantees is that a payload cannot be read or assigned directly: a decoder call, an
assignment to `Uint8Array` and `new Uint8Array(payload)` fail to compile. It does not stop what the
runtime value allows. Because the value is a `Uint8Array`, a check such as `payload instanceof
Uint8Array` narrows it back to bytes, and a cast does the same; a read made that way sees the raw
bytes, which for a codec or formatter document are not the document. Read through
`decodeDocumentPayload`, and when you need the bytes themselves (hashing, signing, a store write)
take them with `documentPayloadBytes`.
- `decodeDocumentPayload(definition, payload)` reads a payload through the definition's formatter,
codec or plain map and then its schema. The definition is required.
- `encodeDocumentPayload(definition, value)` is the writer: the schema first, then the formatter,

View file

@ -102,6 +102,11 @@ const plainDocumentFormatter: CultCacheDocumentFormatter<unknown> = {
* `documentPayloadBytes` where they leave (a store write, a frame encode, hashing or signing).
*
* The key is a plain string, not a unique symbol, so two installed copies of this package stay compatible.
*
* What the type guarantees: a payload cannot be read or assigned directly (a decoder call, `const b: Uint8Array = p`,
* `new Uint8Array(p)` fail to compile). What it does not: the runtime value is a `Uint8Array`, so runtime narrowing
* (`p instanceof Uint8Array`) or a cast gets the raw bytes back, and a codec or formatter document read that way is not
* the document. The escape for bytes is `documentPayloadBytes`; the reader is `decodeDocumentPayload`.
*/
export interface DocumentPayload {
readonly "cultcache.documentPayload": true;

View file

@ -34,7 +34,9 @@ A record's `payload` is a `DocumentPayload`, an opaque type over the same bytes,
`decodeCultNetPayload(record, definition)` is how it is read: through the CultCache document
definition that says whether the bytes are a plain map, a codec's slot record or a
formatter's, then through the definition's schema. A plain MessagePack decode of a payload
does not compile, and `decodeCultNetPayload` takes no definition-less form. The definition
does not compile, and `decodeCultNetPayload` takes no definition-less form. The type stops direct reads
and assignment; it does not stop runtime narrowing (`payload instanceof Uint8Array`) or a cast, which
would read the raw bytes. For the bytes themselves use `documentPayloadBytes`. The definition
comes from `@gamecult/cultcache-ts` (this package imports only its `./document` entry, which
holds no Node built-ins).