This guide describes supported extension points for applications that need custom persistence, codecs, chunking, compression, crypto, or observability.
Most users should start with the facade:
import GitPlumbing from '@git-stunts/plumbing';
import ContentAddressableStore from '@git-stunts/git-cas';
const plumbing = await GitPlumbing.createDefault({ cwd: '.' });
const cas = ContentAddressableStore.createJson({ plumbing });Construct CasService directly only when you own the complete adapter boundary
or are writing focused tests.
| Area | Preferred Entry | Contract |
|---|---|---|
| Persistence | Custom GitPersistencePort implementation |
Blob/tree read and write, plus streaming reads |
| Refs | Custom GitRefPort implementation |
Ref resolution, commit creation, atomic updates |
| Codecs | codec option or direct CasService injection |
encode(), decode(), and extension |
| Chunking | Facade chunking config or ChunkingPort injection |
Async chunk iteration with stable strategy metadata |
| Compression | compressionAdapter injection |
gzip-compatible buffer and stream methods |
| Crypto | crypto injection |
SHA-256, AES-GCM, HMAC, KDF, and stream capability reporting |
| Observability | observability injection |
metric(), log(), and span() |
See ADVANCED_GUIDE.md for the full direct-construction contract and docs/API.md for method-level API details.
Direct construction requires every domain dependency. The facade supplies these defaults for normal Git-backed use; direct callers must supply them explicitly.
import {
FixedChunker,
JsonCodec,
NodeCompressionAdapter,
NodeCryptoAdapter,
SilentObserver,
} from '@git-stunts/git-cas';
import CasService from '@git-stunts/git-cas/service';
const service = new CasService({
persistence,
codec: new JsonCodec(),
crypto: new NodeCryptoAdapter(),
observability: new SilentObserver(),
chunker: new FixedChunker({ chunkSize: 256 * 1024 }),
compressionAdapter: new NodeCompressionAdapter(),
formatVersion: '6.0.0',
});The domain byte contract is Uint8Array. Node Buffer values work at Node
boundaries because Buffer extends Uint8Array, but portable adapters should
not require Buffer-only APIs.
Custom persistence adapters must preserve Git-like object semantics:
writeBlob(bytes)stores immutable bytes and returns the blob OIDreadBlob(oid, maxBytes?)returns the exact bytes written for that OID and should reject invalid positive-integer limits before opening the blob streamwriteTree(entries)writes named tree entries and returns a tree OIDreadTree(treeOid)returns mode/type/OID/name entriesreadBlobStream(oid)returns an async iterable or readable stream of bytessetMaxBlobSize(maxBlobSize)optionally applies the service-level metadata blob safety limit inside adapters that can enforce it natively
readBlobStream() is required for bounded restore paths. Encrypted or
compressed restores can otherwise require full ciphertext buffering and will
fail with PERSISTENCE_CAPABILITY_REQUIRED when the adapter cannot provide the
streaming read capability safely.
A codec serializes and deserializes manifest objects:
const encoder = new TextEncoder();
const decoder = new TextDecoder();
class MyCodec {
get extension() {
return 'myfmt';
}
encode(value) {
return encoder.encode(JSON.stringify(value));
}
decode(bytes) {
return JSON.parse(decoder.decode(bytes));
}
}Codec output is part of the manifest integrity hash. Keep encoding deterministic across platforms and versions. If a codec changes its canonical output, old manifests may fail integrity verification.
Chunkers expose chunk(source) and return ordered chunks with byte content and
metadata. Stable ordering is mandatory because manifests record chunk order.
Use FixedChunker for predictable block boundaries. Use CdcChunker for
deduplication workloads where insertions and deletions should not shift every
downstream chunk.
Compression adapters provide both buffer and stream forms:
compressBuffer(bytes)decompressBuffer(bytes)compressStream(source)decompressStream(source)
The manifest schema currently supports gzip. Custom adapters should preserve
the same algorithm name unless a future schema explicitly adds another
compression algorithm.
Crypto adapters implement hashing, random bytes, AES-256-GCM, HMAC, KDF, and stream encryption/decryption where supported. Runtime limitations must surface as explicit capability errors rather than silent fallback.
Notable requirements:
- AES-GCM nonces are 12 bytes.
- GCM tags are 16 bytes.
- PBKDF2 must derive 32-byte keys with policy-bounded parameters.
- scrypt support is runtime-dependent; Web Crypto runtimes should report a capability error instead of pretending to support it.
Observability is intentionally narrow:
const observer = {
metric(name, value, tags) {},
log(level, message, context) {},
span(name, context) {
return { end(status) {} };
},
};Use this for progress, warnings, and release-readiness telemetry. Do not put business logic in observers; observers should be replaceable without changing storage behavior.
Before shipping a custom adapter:
- run the relevant unit tests against the adapter
- verify
storeFile()andrestoreFile()round trips with binary data - verify encrypted
whole,framed, andconvergentrestores - verify gzip restore if compression is enabled
- verify large restores do not exceed configured buffer limits
- verify errors are
CasErrorinstances with actionablecodevalues - document runtime-specific limits and unsupported methods