@statewalker/webrun-storage 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +81 -31
  2. package/package.json +6 -2
package/README.md CHANGED
@@ -1,58 +1,108 @@
1
1
  # @statewalker/webrun-storage
2
2
 
3
- Domain-neutral byte-persistence seam: an immutable content-addressed `BlobStore` and a mutable `KvStore` with atomic compare-and-set.
3
+ ## What it is
4
4
 
5
- ## Overview
5
+ A small byte-persistence layer with two primitives: an immutable `BlobStore` keyed by a
6
+ caller-supplied id, and a mutable `KvStore` with atomic compare-and-set. A `RefStore` facade
7
+ stores string ids on top of a `KvStore`. Adapters are included for memory and for any
8
+ `FilesApi` from `@statewalker/webrun-files`.
6
9
 
7
- `storage` is the persistence seam both axes of the two-axis VCS architecture plug into. It defines two small low-level primitives — a `BlobStore` (immutable, keyed by a caller-supplied id, with ranged reads and byte `size`) and a `KvStore` (mutable, keyed, with an atomic `cas`) — plus a thin typed `RefStore` facade over the KV primitive. It stores bytes and nothing more: it knows nothing of git objects, chunks, or hashing algorithms (**the caller owns every id**).
10
+ ## Why it exists
8
11
 
9
- Adapters implement the two primitives over different backends; `content-store` and `vcs-core` ride on top. This package ships an in-memory adapter and a `FilesApi`-backed adapter; SQL / KV backends are separate adapters over the same interfaces.
12
+ Higher layers (a chunked content store, a git object store, ref tables) all need the same two
13
+ things from a backend: write an immutable blob under a known id, and update a pointer atomically.
14
+ This package defines exactly that contract and nothing more. A new backend (SQL, KV, cloud
15
+ object storage) implements two interfaces, and every layer above works on it unchanged.
10
16
 
11
- ## Installation
17
+ ## How to use
12
18
 
13
19
  ```bash
14
20
  pnpm add @statewalker/webrun-storage
15
21
  ```
16
22
 
17
- ## Quick Start
23
+ | Import | Contents |
24
+ | --- | --- |
25
+ | `@statewalker/webrun-storage` | `memBlobStore`, `memKvStore`, `filesBlobStore`, `refStore`, and the types `BlobStore`, `KvStore`, `RefStore`, `ObjectStore`, `ByteStream`, `FilesBlobStoreOptions`. ESM, runs in browsers, Node and workers. |
26
+
27
+ `BlobStore`:
28
+
29
+ - `put(id, bytes, opts?)`: stores a byte stream. Re-putting the same id is idempotent.
30
+ `opts.verify(bytes)` re-derives the id; on mismatch `put` rejects and stores nothing.
31
+ - `get(id, range?)`: byte stream; `range` is `{ start?, end? }` with `end` exclusive. An absent id
32
+ yields an empty stream.
33
+ - `has(id)`, `remove(id)` (resolves `false` if absent), `size(id)` (`-1` if absent),
34
+ `list(prefix?)`.
35
+
36
+ `KvStore`: `get`, `put`, `remove`, `list(prefix?)`, and `cas(key, expected, next)`, which writes
37
+ and returns `true` only when the current value equals `expected` byte for byte. `expected ===
38
+ undefined` means "must not exist"; `next === undefined` deletes.
39
+
40
+ `RefStore`: `read(name)`, `compareAndSet(name, expected, next)`, `list(prefix?)`, all with string
41
+ ids. `ObjectStore` is an alias of `BlobStore`.
42
+
43
+ ## Examples
44
+
45
+ Blobs in memory and over a `FilesApi`:
18
46
 
19
47
  ```typescript
20
48
  import { MemFilesApi } from "@statewalker/webrun-files-mem";
21
- import { filesBlobStore, memBlobStore, memKvStore, refStore } from "@statewalker/webrun-storage";
22
-
23
- // Immutable, content-addressed blobs (in-memory, or over any FilesApi):
24
- const blobs = memBlobStore();
25
- const overFiles = filesBlobStore(new MemFilesApi());
49
+ import { filesBlobStore, memBlobStore } from "@statewalker/webrun-storage";
26
50
 
27
51
  async function* streamOf(...parts: string[]) {
28
52
  for (const p of parts) yield new TextEncoder().encode(p);
29
53
  }
30
54
 
31
- await blobs.put("a1", streamOf("hello ", "world")); // caller owns the id
32
- await blobs.has("a1"); // true
33
- await blobs.size("a1"); // 11, or -1 if absent
34
- const tail = blobs.get("a1", { start: 6 }); // ranged read -> "world"
55
+ const blobs = memBlobStore();
56
+ await blobs.put("a1b2c3", streamOf("hello ", "world")); // the caller owns the id
57
+ await blobs.size("a1b2c3"); // 11
58
+ const tail = blobs.get("a1b2c3", { start: 6 }); // "world"
59
+
60
+ const onDisk = filesBlobStore(new MemFilesApi(), { root: "/objects" });
61
+ await onDisk.put("a1b2c3", streamOf("hello")); // stored at /objects/a1/b2c3
62
+ ```
35
63
 
36
- // Mutable KV with atomic compare-and-set, and the ref facade over it:
37
- const kv = memKvStore();
38
- const created = await kv.cas("head", undefined, new TextEncoder().encode("v1")); // true (create)
64
+ Atomic pointer updates:
39
65
 
40
- const refs = refStore(kv);
41
- await refs.compareAndSet("refs/heads/main", undefined, "commit-1");
42
- const id = await refs.read("refs/heads/main"); // "commit-1"
66
+ ```typescript
67
+ import { memKvStore, refStore } from "@statewalker/webrun-storage";
68
+
69
+ const refs = refStore(memKvStore());
70
+ await refs.compareAndSet("refs/heads/main", undefined, "commit-1"); // true: created
71
+ await refs.compareAndSet("refs/heads/main", "commit-0", "commit-2"); // false: lost the race
72
+ await refs.read("refs/heads/main"); // "commit-1"
43
73
  ```
44
74
 
45
- ## API
75
+ ## Internals
76
+
77
+ ### Ids belong to the caller
78
+
79
+ `put` never hashes. A git store passes git object ids, a chunk store passes its own chunk hashes.
80
+ The package therefore depends on no hash library and works with any id scheme. Integrity checking
81
+ is opt-in through `verify`; a mismatch fails with
82
+ `blob id mismatch: supplied "<id>", verify computed "<computed>"`.
83
+
84
+ ### Compare-and-set is part of the contract
85
+
86
+ `cas` is a required method, not an optional capability, so `RefStore` can always offer atomic
87
+ updates without checking what the backend supports. The in-memory adapter is atomic because
88
+ JavaScript runs it in one turn.
89
+
90
+ ### One backend object per blob
91
+
92
+ `filesBlobStore` writes each blob to its own file, sharded like git's loose objects:
93
+ `<root>/<first two chars>/<rest>`. Ids shorter than three characters produce odd paths; use
94
+ hash-like ids. Packing and chunk grouping are left to the layers above.
95
+
96
+ ### Ranged reads
97
+
98
+ `get(id, { start, end })` matches the usual "inclusive start, exclusive end" slice convention, so
99
+ random access into pack files and large blobs needs no full read.
46
100
 
47
- - **`BlobStore`** — `put(id, bytes, opts?)` (streaming, bounded-memory; `opts.verify` re-derives and checks the id), `get(id, range?)` with `{ start?, end? }` (end exclusive), `has(id)`, `remove(id)`, `size(id)` (`-1` when absent), `list(prefix?)`. Re-putting the same id is idempotent. `ObjectStore` is an alias.
48
- - **`KvStore`** — `get` / `put` / `remove` / `list`, plus `cas(key, expected, next)` (writes and returns `true` only if the current value deep-equals `expected`; `next === undefined` deletes).
49
- - **`RefStore`** — thin string-id facade over a `KvStore`: `read`, `compareAndSet`, `list`.
101
+ ### Dependencies
50
102
 
51
- **Adapters & facade:** `memBlobStore()`, `memKvStore()`, `filesBlobStore(files, opts?)`, `refStore(kv)`.
103
+ Only `@statewalker/webrun-files`, for the `FilesApi` type used by `filesBlobStore`. No runtime
104
+ dependencies.
52
105
 
53
- ## Notes
106
+ ## License
54
107
 
55
- - **Hash-agnostic.** `put(id, …)` takes the id from the caller — `content-store` hashes chunks, `vcs-core` computes git object ids. Optional integrity verification is a caller-injected `verify` on `put`.
56
- - **CAS is a required primitive** so `RefStore` always gets atomic compare-and-set with no per-consumer branching.
57
- - **One backend object per blob;** packing/CDC-grouping lives in the layers above (`vcs-core` packfiles, `content-store` chunks).
58
- - The same behavioural suite runs against every adapter (mem and files), and the seam is bounded-memory by construction. Built red/green TDD.
108
+ MIT
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-storage",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Domain-neutral byte-persistence seam (BlobStore + KvStore with CAS) over the webrun-files FilesApi",
5
+ "bugs": {
6
+ "url": "https://github.com/statewalker/webrun-sync/issues"
7
+ },
8
+ "homepage": "https://github.com/statewalker/webrun-sync/tree/main/packages/webrun-storage#readme",
5
9
  "license": "MIT",
6
10
  "repository": {
7
11
  "type": "git",
8
- "url": "https://github.com/statewalker/webrun-vcs.git",
12
+ "url": "git+https://github.com/statewalker/webrun-sync.git",
9
13
  "directory": "packages/webrun-storage"
10
14
  },
11
15
  "private": false,