diff --git a/src/GameCult.Caching/Contracts/cultcache-store-composition.md b/src/GameCult.Caching/Contracts/cultcache-store-composition.md index bb67855c..ea67142d 100644 --- a/src/GameCult.Caching/Contracts/cultcache-store-composition.md +++ b/src/GameCult.Caching/Contracts/cultcache-store-composition.md @@ -349,24 +349,28 @@ no serialization; a CultMath encoding, if added, uses this shape. ## Cacheless single-file access -A single-file store file can be read and written without a cache, through two public members of `SingleFileBackingStore` (for -example `SingleFileMessagePackBackingStore`): `ReadStoreSnapshot()` and `WriteRecord(record, catalogEntry, holdsIds, registry)`. +A single-file store file can be read and written without a cache, through public members of `SingleFileBackingStore` (for +example `SingleFileMessagePackBackingStore`): `ReadStoreSnapshot()`, `WriteRecord(record, catalogEntry, holdsIds, registry)`, +`WriteDocument(key, document, storedAt, registry)` and `DecodeDocument(key, payload, registry)`. - `ReadStoreSnapshot()` reads the file once, under the store's gate, and returns the durable snapshot (records, schema catalog, format stamp), or null when nothing is at the path. It throws `CultStoreUnreadableException` for a file the reader refuses. - `WriteRecord` writes one record under the store's lock: the record replaces the one at its key, every other record and catalog - entry is copied forward as stored, and the file is replaced atomically. + entry is copied forward as stored, and the file is replaced atomically. It takes a payload the caller already encoded, so a + payload it cannot read leaves the header as the file has it (`holdsIds` null). +- `WriteDocument` writes one typed document as a cache write does: it plans element ids, encodes the document with the + registry, and stores it through `WriteRecord`. A failed write gives the minted ids back. +- `DecodeDocument` decodes a payload read from the store as a cache load does: it mints the ids a load mints and refuses what + a cache load refuses. They share everything with a cache's store and nothing is re-implemented beside it: one reader (the stamp is judged before the body, and a file the reader refuses is refused by a read and by a write alike), the store's lock, the atomic replace, and the header rule (`holdsIds` true marks a v3 store; false leaves it as the rule decides; null, for a payload the caller cannot read, keeps whatever the file already says). Ownership by declaration binds a cacheless write too: a record at the key under a schema id the written catalog entry does not own or list as compatible is refused with `CultSchemaConflictException`, and the file is left as -it was. A typed writer plans element ids with `CultElementIds.Plan` before it serializes and passes `CultElementIds.Holds` as -`holdsIds`. - -A store attached to a cache refuses `WriteRecord` with `InvalidOperationException`: write through the cache, whose staged writes -land through the same private write. CultMesh's single-file document helpers (`CultMesh.WriteSingleFileDocument`, +it was. Element-id planning (`CultElementIds`) is internal to the library: a caller never plans or marks ids, the store does. A store +attached to a cache refuses `WriteRecord` and `WriteDocument` (write through the cache, whose staged writes land through the +same private write) and `DecodeDocument` (read through the cache) with `InvalidOperationException`. CultMesh's single-file document helpers (`CultMesh.WriteSingleFileDocument`, `ReadSingleFileDocument` and their payload forms) are built on these members and nothing else of the store. ## Runtime status diff --git a/unity/org.gamecult.cultlib/CHANGELOG.md b/unity/org.gamecult.cultlib/CHANGELOG.md index b55c4fa3..ff8d0767 100644 --- a/unity/org.gamecult.cultlib/CHANGELOG.md +++ b/unity/org.gamecult.cultlib/CHANGELOG.md @@ -19,12 +19,12 @@ Releases as 3.0.0 (major). - `GameCult.Mesh`: `AsSchemaAlias()` is removed from `CultMeshDocumentHandle` and `CultMeshCollectionHandle`. Read the handle by the schema you mean. - The element types of owned lists in `GameCult.Mesh` (`CultMeshBodies`, `CultMeshCdn`, `CultMeshDiscoveryService` and `CultMeshEntityPrefabs`) and `GameCult.Networking` (`CultWitnessArtifactBundle`) gain a public string `Id` member with `[CultElementId]`, so their lists merge by identity. Code that matched those elements positionally by shape must set or read `Id`. - `CultNetDocumentRegistry.Register` refuses a binding that claims a document type or a schema id another binding already holds, with `CultSchemaConflictException`; binding a type to the schema id it already holds still replaces that binding. -- `InternalsVisibleTo GameCult.Mesh` is removed from `GameCult.Caching`: CultMesh's single-file document helpers use the public `ReadStoreSnapshot`, `WriteRecord` and `CultElementIds` below. +- `InternalsVisibleTo GameCult.Mesh` is removed from `GameCult.Caching`: CultMesh's single-file document helpers use the public `ReadStoreSnapshot`, `WriteDocument`, `DecodeDocument` and `WriteRecord` below. ### Added - `SingleFileBackingStore.ReadStoreSnapshot()` reads a store file once, under the store's lock, with the reader a cache's store opens with, and returns the durable snapshot or null when nothing is at the path. `SingleFileBackingStore.WriteRecord(record, catalogEntry, holdsIds, registry)` writes one record without a cache: every other record and catalog entry is copied as stored, the file is replaced atomically, and the header is decided as a flush decides it. It refuses on a store attached to a cache. -- `CultElementIds.Plan`, `CultElementIds.Holds` and `CultElementIds.IdPlan` are public: a cacheless typed writer plans element ids before it serializes and passes `Holds` as `holdsIds`. +- `SingleFileBackingStore.WriteDocument(key, document, storedAt, registry)` writes one typed document without a cache, as a cache write does: it plans element ids, encodes the document and marks the store when it holds an id; a failed write gives the minted ids back. `SingleFileBackingStore.DecodeDocument(key, payload, registry)` decodes a stored payload as a cache load does. Both refuse on a store attached to a cache. `CultElementIds` stays internal. - A store written by 1.0.x with a record-reference list in a slot still opens (from 2.0.1). ## [2.0.1] - 2026-10-22