Skip to content

Latest commit

 

History

History
174 lines (132 loc) · 5.95 KB

File metadata and controls

174 lines (132 loc) · 5.95 KB

Extending git-cas

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.

Extension Points

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 Service Construction

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.

Persistence Adapter Requirements

Custom persistence adapters must preserve Git-like object semantics:

  • writeBlob(bytes) stores immutable bytes and returns the blob OID
  • readBlob(oid, maxBytes?) returns the exact bytes written for that OID and should reject invalid positive-integer limits before opening the blob stream
  • writeTree(entries) writes named tree entries and returns a tree OID
  • readTree(treeOid) returns mode/type/OID/name entries
  • readBlobStream(oid) returns an async iterable or readable stream of bytes
  • setMaxBlobSize(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.

Codec Requirements

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.

Chunking Requirements

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 Adapter Requirements

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 Adapter Requirements

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 Adapter Requirements

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.

Compatibility Checklist

Before shipping a custom adapter:

  • run the relevant unit tests against the adapter
  • verify storeFile() and restoreFile() round trips with binary data
  • verify encrypted whole, framed, and convergent restores
  • verify gzip restore if compression is enabled
  • verify large restores do not exceed configured buffer limits
  • verify errors are CasError instances with actionable code values
  • document runtime-specific limits and unsupported methods