cultcache-ts 0.14.2: version, CHANGELOG, README persistence model, store-composition contract
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e6d8e7e30b
commit
ed432b59da
5 changed files with 37 additions and 9 deletions
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1184,7 +1184,7 @@
|
|||
},
|
||||
"packages/cultcache-ts": {
|
||||
"name": "@gamecult/cultcache-ts",
|
||||
"version": "0.14.1",
|
||||
"version": "0.14.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.1.3",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,26 @@
|
|||
All notable changes to `@gamecult/cultcache-ts` are recorded here, following
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [0.14.2]
|
||||
|
||||
### Added
|
||||
- `SingleFileMessagePackBackingStore` takes a cross-process write lock on `<file>.lock`,
|
||||
interoperable with the C# and Rust runtimes (`flock` on Unix, `LockFileEx` on Windows).
|
||||
Each write reads the current file, merges and replaces it under the lock, so concurrent
|
||||
writers no longer lose each other's records.
|
||||
- The staged file is fsynced before the replace, and on Unix the directory after it. A
|
||||
directory sync failure after the replace is reported as replaced but not durable.
|
||||
|
||||
### Changed
|
||||
- The Windows replace is a POSIX-semantics rename first (it lands while C# and Rust readers
|
||||
hold the store open), falling back to a write-through `MoveFileExW`, with 25 attempts 20 ms
|
||||
apart on errors 5, 32 and 33. Writers wait up to 30 s for the lock.
|
||||
- Opening a legacy store that needs healing rewrites it under the lock, from a read taken
|
||||
under the lock.
|
||||
|
||||
### Dependencies
|
||||
- `koffi` 3.3.0, loaded only on the first write.
|
||||
|
||||
## [0.14.1]
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -358,7 +358,8 @@ That is the TypeScript version of the original CultCache trick:
|
|||
- each record is `[key, schemaId, storedAt, payload]`
|
||||
- each catalog entry is `[schemaId, schemaName, schemaVersion, contentHash, canonicalSchemaJson, compatibleSchemaIds, members]`
|
||||
- it still reads the older TS/Rust-style envelope array and rewrites repaired legacy payloads as v1 snapshots
|
||||
- it queues local writes so one process does not step on its own shoelaces
|
||||
- it queues local writes in order, then takes the shared `<file>.lock` (`flock` on Unix, `LockFileEx` on Windows, via koffi, loaded on the first write) for each one, so C#, Rust and TypeScript writers exclude each other; the read a write merges from is taken under that lock
|
||||
- it fsyncs the staged file and, on Unix, the directory after the replace; on Windows it replaces with a POSIX-semantics rename and falls back to a write-through `MoveFileExW`, retrying briefly on sharing violations
|
||||
- it persists raw MessagePack payload bytes inside schema-addressed records
|
||||
|
||||
It is a control-plane store, not a tiny god. If multiple processes hammer the same file, you still need a coordinator or an external lock instead of prayer.
|
||||
It is a control-plane store, not a tiny god: every write rewrites the whole file.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@gamecult/cultcache-ts",
|
||||
"version": "0.14.1",
|
||||
"version": "0.14.2",
|
||||
"description": "CultCache-style typed persistence for TypeScript with schema-backed polymorphic MessagePack storage.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
|||
|
|
@ -240,8 +240,13 @@ replaces; writers bump a minted timestamp by one tick when it is not later.
|
|||
wait bound (30 seconds).
|
||||
- A conditional commit on a store with staged single-record writes throws:
|
||||
flush first.
|
||||
- Single-file stores lock a sidecar `<path>.lock` opened exclusively. Directory
|
||||
stores use their existing commit lease.
|
||||
- Single-file stores lock a sidecar `<path>.lock`: C# by an exclusive
|
||||
(share-none) open, Rust and TypeScript by an exclusive `LockFileEx` on a
|
||||
shared open on Windows, and every runtime by `flock(LOCK_EX)` on Unix. Each
|
||||
excludes the others (TypeScript tests: "a TypeScript write waits while a C#
|
||||
writer holds the store lock", "... a Rust writer ...", "a C# writer waits
|
||||
while a TypeScript writer holds the store lock", "a Rust writer waits ...").
|
||||
Directory stores use their existing commit lease.
|
||||
- A directory store with no `.commit.lock` (no writer has created it yet, or
|
||||
the store was copied without it) gives a pull no lease, and opening it
|
||||
creates none. Such a pull re-reads the manifest after loading its pages and
|
||||
|
|
@ -265,9 +270,11 @@ against another writer; processes sharing a store must all use it.
|
|||
in that store, and leaves the store clean.
|
||||
- A conditional commit lands the batch onto the file as it is under the lock.
|
||||
|
||||
A store is written by one runtime at a time. Whether a Rust `fs2` lock and a C#
|
||||
exclusive open of the same lock file exclude each other is not established, and
|
||||
another runtime need not obey the `storedAt` rule.
|
||||
C#, Rust and TypeScript writers exclude each other on the shared lock file;
|
||||
Python takes no cross-process lock yet (`variants:follow_up:py-shared-write-lock`),
|
||||
and Rust's open fails rather than waits while a C# writer holds the lock
|
||||
(`variants:follow_up:rust-lock-open-waits`). Another runtime need not obey the
|
||||
`storedAt` rule.
|
||||
|
||||
## Serialization options
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue