Skip to content

feat(vfs): add VFS decorators and S3/database backends#21

Merged
tgiachi merged 8 commits into
developfrom
feature/vfs-backends
Jun 30, 2026
Merged

feat(vfs): add VFS decorators and S3/database backends#21
tgiachi merged 8 commits into
developfrom
feature/vfs-backends

Conversation

@tgiachi

@tgiachi tgiachi commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

New backends and composable decorators for SquidStd.Vfs, all implementing the unchanged IVirtualFileSystem.

Decorators (zero new dependencies, in SquidStd.Vfs)

  • ReadOnlyFileSystem — delegates reads, rejects every mutation.
  • ScopedFileSystem — roots an inner filesystem at a path prefix (chroot-like); list paths are scope-relative, with a boundary guard so a sibling scope sharing a name prefix (tenant10 vs tenant1) can't leak.
  • OverlayFileSystem — reads overlay-first then base; writes/deletes affect the overlay only; list is the union (overlay shadows base).
  • CachingFileSystem — read-through cache: reads prefer the remote and refresh the cache; on a transport failure they fall back to the (possibly stale) cache; writes are write-through (remote then cache) and fail when the remote is unreachable.

Backends (new adapter projects)

  • SquidStd.Vfs.S3 — S3-compatible object storage (AWS S3, MinIO, Cloudflare R2, Backblaze B2) via the MinIO SDK, mirroring the existing S3StorageService. Path = object key.
  • SquidStd.Vfs.Database — files stored as rows via SquidStd.Database / FreeSql (path-keyed, unique index, upsert).

Headline composition — offline resilience

// S3 with a local disk cache so reads keep working when the connection is unstable.
var fs = new CachingFileSystem(remote: s3FileSystem, cache: new PhysicalFileSystem("/var/cache/app"));

The seam was verified end-to-end: S3FileSystem maps not-found → null/false but lets MinIO transport errors propagate; CachingFileSystem catches only transport exceptions and falls back, while a genuine not-found passes through without serving stale cache.

Shared

  • DeferredWriteStream (in SquidStd.Vfs.Abstractions) backs OpenWriteAsync for the deferred backends (flush-once on dispose).

Notes

  • The two backend projects do NOT depend on SquidStd.Vfs (only *.Abstractions + Core + their SDK).
  • DatabaseFileSystem upsert is read-then-write (single-writer assumption), with a unique index as a hard DB-level guard.

Test plan

  • Solution builds (0 errors)
  • Full suite green — 971 tests, 0 failures (31 new VFS tests)
  • Decorators unit-tested with an in-memory inner (incl. scope-boundary leak guard and an offline-list cache-fallback regression test)
  • SquidStd.Vfs.S3 — 9 integration tests against a real MinIO container (Testcontainers)
  • SquidStd.Vfs.Database — 7 integration tests against SQLite (FreeSql)

tgiachi added 8 commits June 30, 2026 15:27
- new project SquidStd.Vfs.S3 (net10.0, PublishNuget=true, Minio 7.0.0)
- S3FileSystemOptions: Aws (AwsConfigEntry) + Bucket, mirrors S3StorageOptions shape
- S3FileSystem: IVirtualFileSystem + IDisposable over MinIO client
  - lazy EnsureBucketAsync (BucketExistsArgs / MakeBucketArgs) with SemaphoreSlim
  - ExistsAsync via StatObjectArgs, ObjectNotFoundException → false
  - ReadAllBytesAsync via GetObjectArgs + callback stream, ObjectNotFoundException → null
  - OpenReadAsync: same buffering, ObjectNotFoundException → FileNotFoundException
  - WriteAllBytesAsync via PutObjectArgs with MemoryStream + WithObjectSize
  - OpenWriteAsync returns DeferredWriteStream flushing through WriteAllBytesAsync
  - DeleteAsync: stat-then-remove, returns whether object existed
  - ListAsync: ListObjectsEnumAsync with WithRecursive(true), yields VfsEntry(Key, Size, LastModifiedDateTime)
  - CreateClient mirrors S3StorageService (URI parsing, WithSSL, optional WithRegion)
  - Dispose last, Interlocked-guarded
- RegisterS3FileSystemExtensions: extension(IContainer) block using RegisterDelegate<IVirtualFileSystem> directly (no SquidStd.Vfs dep)
- integration tests: MinioCollection fixture reused, 9 tests all green (Docker available locally)
- slnx + test csproj updated to include new project
- New project SquidStd.Vfs.Database targeting net10.0 with FreeSql 3.5.310
- VfsFileEntity: BaseEntity subclass with Path/Content/Size; unique index on
  Path via FreeSql class-level [Index("uq_vfs_file_path", "Path", true)]
- DatabaseFileSystem: IVirtualFileSystem backed by IDataAccess<VfsFileEntity>;
  query-then-write upsert in WriteAllBytesAsync; StartsWith predicate for
  ListAsync prefix filtering; DeferredWriteStream for OpenWriteAsync
- RegisterDatabaseFileSystemExtensions: extension(IContainer) DryIoc helper
- Integration tests (SQLite, no Docker): write-read round-trip, upsert
  same-path deduplication, ExistsAsync, DeleteAsync, ListAsync with/without
  prefix — all 7 pass
- Added SquidStd.Vfs.Database project reference to SquidStd.Tests
var uri = new Uri(options.Aws.ServiceUrl!, UriKind.Absolute);
var endpoint = uri.IsDefaultPort ? uri.Host : $"{uri.Host}:{uri.Port}";

var minio = new MinioClient()

public async Task InitializeAsync()
{
_dbPath = Path.Combine(Path.GetTempPath(), "squidstd-vfsdb-" + Guid.NewGuid().ToString("N") + ".db");
@tgiachi
tgiachi merged commit 1821c56 into develop Jun 30, 2026
3 checks passed
@tgiachi
tgiachi deleted the feature/vfs-backends branch June 30, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants