diff --git a/datanode/client.go b/datanode/client.go index e4e3994..b2c00cd 100644 --- a/datanode/client.go +++ b/datanode/client.go @@ -36,7 +36,10 @@ var ( } ) -// Medium is an in-memory storage backend backed by a Borg DataNode. +// Example: medium := datanode.New() +// _ = medium.Write("jobs/run.log", "started") +// snapshot, _ := medium.Snapshot() +// // All paths are relative (no leading slash). Thread-safe via RWMutex. type Medium struct { dataNode *borgdatanode.DataNode @@ -44,10 +47,8 @@ type Medium struct { mu sync.RWMutex } -// New creates an in-memory Medium that snapshots to tar. -// -// medium := datanode.New() -// _ = medium.Write("jobs/run.log", "started") +// Example: medium := datanode.New() +// _ = medium.Write("jobs/run.log", "started") func New() *Medium { return &Medium{ dataNode: borgdatanode.New(), diff --git a/io.go b/io.go index 83b85cb..1ab88a7 100644 --- a/io.go +++ b/io.go @@ -10,16 +10,18 @@ import ( "dappco.re/go/core/io/local" ) -// Medium defines the standard interface for a storage backend. -// This allows for different implementations (e.g., local disk, S3, SFTP) -// to be used interchangeably. +// Medium is the storage boundary used across CoreGO. +// +// medium, _ := io.NewSandboxed("/srv/app") +// _ = medium.Write("config/app.yaml", "port: 8080") +// backup, _ := io.NewSandboxed("/srv/backup") +// _ = io.Copy(medium, "data/report.json", backup, "daily/report.json") type Medium interface { Read(path string) (string, error) Write(path, content string) error - // WriteMode saves content with explicit file permissions. - // Use 0600 for sensitive files (keys, secrets, encrypted output). + // Example: _ = medium.WriteMode("keys/private.key", key, 0600) WriteMode(path, content string, mode fs.FileMode) error EnsureDir(path string) error @@ -46,18 +48,16 @@ type Medium interface { Append(path string) (goio.WriteCloser, error) - // ReadStream returns a reader for the file content. - // Use this for large files to avoid loading the entire content into memory. + // Example: reader, _ := medium.ReadStream("logs/app.log") ReadStream(path string) (goio.ReadCloser, error) - // WriteStream returns a writer for the file content. - // Use this for large files to avoid loading the entire content into memory. + // Example: writer, _ := medium.WriteStream("logs/app.log") WriteStream(path string) (goio.WriteCloser, error) - // Exists checks if a path exists (file or directory). + // Example: ok := medium.Exists("config/app.yaml") Exists(path string) bool - // IsDir checks if a path exists and is a directory. + // Example: ok := medium.IsDir("config") IsDir(path string) bool } @@ -98,9 +98,9 @@ func (de DirEntry) Type() fs.FileMode { return de.mode.Type() } func (de DirEntry) Info() (fs.FileInfo, error) { return de.info, nil } -// Local is a pre-initialised medium for the local filesystem. -// It uses "/" as root, providing unsandboxed access to the filesystem. -// For sandboxed access, use NewSandboxed with a specific root path. +// Local is the unsandboxed filesystem medium rooted at "/". +// +// io.Local.Read("/etc/hostname") var Local Medium var _ Medium = (*local.Medium)(nil) @@ -171,7 +171,10 @@ func Copy(source Medium, sourcePath string, destination Medium, destinationPath // --- MockMedium --- -// MockMedium is an in-memory implementation of Medium for testing. +// MockMedium is an in-memory Medium for tests. +// +// medium := io.NewMockMedium() +// _ = medium.Write("config/app.yaml", "port: 8080") type MockMedium struct { Files map[string]string Dirs map[string]bool diff --git a/local/client.go b/local/client.go index 149c280..29ba6ec 100644 --- a/local/client.go +++ b/local/client.go @@ -13,16 +13,15 @@ import ( core "dappco.re/go/core" ) -// Medium is a local filesystem storage backend. +// Medium is the local filesystem backend returned by New. type Medium struct { filesystemRoot string } var unrestrictedFileSystem = (&core.Fs{}).NewUnrestricted() -// New creates a filesystem rooted at root. -// -// Pass "/" for full filesystem access, or a project path to sandbox. +// local.New("/") exposes the full filesystem. +// local.New("/srv/app") confines access to a project root. // // medium, _ := local.New("/srv/app") // _ = medium.Write("config/app.yaml", "port: 8080") diff --git a/node/node.go b/node/node.go index bf8d9e8..71fd217 100644 --- a/node/node.go +++ b/node/node.go @@ -23,7 +23,11 @@ import ( coreio "dappco.re/go/core/io" ) -// Node is an in-memory filesystem that satisfies coreio.Medium and fs.FS. +// Example: nodeTree := node.New() +// nodeTree.AddData("config/app.yaml", []byte("port: 8080")) +// snapshot, _ := nodeTree.ToTar() +// restored, _ := node.FromTar(snapshot) +// // Directories are implicit: they exist whenever a file path contains a "/". type Node struct { files map[string]*dataFile @@ -33,10 +37,8 @@ type Node struct { var _ coreio.Medium = (*Node)(nil) var _ fs.ReadFileFS = (*Node)(nil) -// Use New when you need an in-memory filesystem that can be snapshotted. -// -// nodeTree := New() -// nodeTree.AddData("config/app.yaml", []byte("port: 8080")) +// Example: nodeTree := node.New() +// nodeTree.AddData("config/app.yaml", []byte("port: 8080")) func New() *Node { return &Node{files: make(map[string]*dataFile)} } @@ -135,7 +137,7 @@ func (n *Node) WalkNode(root string, fn fs.WalkDirFunc) error { return fs.WalkDir(n, root, fn) } -// WalkOptions configures WalkWithOptions. +// Example: options := node.WalkOptions{MaxDepth: 1, SkipErrors: true} type WalkOptions struct { // MaxDepth limits how many directory levels to descend. 0 means unlimited. MaxDepth int @@ -380,7 +382,7 @@ func (n *Node) FileSet(filePath, content string) error { return n.Write(filePath, content) } -// EnsureDir is a no-op because directories are implicit in Node. +// Example: _ = nodeTree.EnsureDir("config") func (n *Node) EnsureDir(_ string) error { return nil } diff --git a/s3/s3.go b/s3/s3.go index 19f0a39..4884522 100644 --- a/s3/s3.go +++ b/s3/s3.go @@ -33,7 +33,10 @@ type Client interface { CopyObject(ctx context.Context, params *awss3.CopyObjectInput, optFns ...func(*awss3.Options)) (*awss3.CopyObjectOutput, error) } -// Medium is the concrete io.Medium returned by New. +// Medium is the S3-backed io.Medium returned by New. +// +// medium, _ := s3.New(s3.Options{Bucket: "backups", Client: client, Prefix: "daily/"}) +// _ = medium.Write("reports/daily.txt", "done") type Medium struct { client Client bucket string @@ -42,7 +45,7 @@ type Medium struct { var _ coreio.Medium = (*Medium)(nil) -// Options configures New. +// Example: medium, _ := s3.New(s3.Options{Bucket: "backups", Client: client, Prefix: "daily/"}) type Options struct { // Bucket is the target S3 bucket name. Bucket string @@ -90,10 +93,8 @@ func normalisePrefix(prefix string) string { return clean } -// New opens an S3-backed medium for one bucket and optional prefix. -// -// medium, _ := s3.New(s3.Options{Bucket: "backups", Client: client, Prefix: "daily/"}) -// _ = medium.Write("reports/daily.txt", "done") +// Example: medium, _ := s3.New(s3.Options{Bucket: "backups", Client: client, Prefix: "daily/"}) +// _ = medium.Write("reports/daily.txt", "done") func New(options Options) (*Medium, error) { if options.Bucket == "" { return nil, core.E("s3.New", "bucket name is required", nil) @@ -167,17 +168,17 @@ func (m *Medium) Write(filePath, content string) error { return nil } -// WriteMode ignores the requested mode because S3 objects do not store POSIX permissions. +// Example: _ = medium.WriteMode("keys/private.key", key, 0600) func (m *Medium) WriteMode(filePath, content string, _ fs.FileMode) error { return m.Write(filePath, content) } -// EnsureDir is a no-op for S3 (S3 has no real directories). +// Example: _ = medium.EnsureDir("reports/2026") func (m *Medium) EnsureDir(_ string) error { return nil } -// IsFile checks if a path exists and is a regular file (not a "directory" prefix). +// Example: ok := medium.IsFile("reports/daily.txt") func (m *Medium) IsFile(filePath string) bool { key := m.objectKey(filePath) if key == "" { @@ -218,7 +219,7 @@ func (m *Medium) Delete(filePath string) error { return nil } -// DeleteAll removes all objects under the given prefix. +// Example: _ = medium.DeleteAll("reports/2026") func (m *Medium) DeleteAll(filePath string) error { key := m.objectKey(filePath) if key == "" { @@ -283,7 +284,7 @@ func (m *Medium) DeleteAll(filePath string) error { return nil } -// Rename moves an object by copying then deleting the original. +// Example: _ = medium.Rename("drafts/todo.txt", "archive/todo.txt") func (m *Medium) Rename(oldPath, newPath string) error { oldKey := m.objectKey(oldPath) newKey := m.objectKey(newPath) @@ -313,7 +314,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { return nil } -// List returns directory entries for the given path using ListObjectsV2 with delimiter. +// Example: entries, _ := medium.List("reports") func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { prefix := m.objectKey(filePath) if prefix != "" && !core.HasSuffix(prefix, "/") { @@ -386,7 +387,7 @@ func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { return entries, nil } -// Stat returns file information for the given path using HeadObject. +// Example: info, _ := medium.Stat("reports/daily.txt") func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { key := m.objectKey(filePath) if key == "" { @@ -456,8 +457,7 @@ func (m *Medium) Open(filePath string) (fs.File, error) { }, nil } -// Create creates or truncates the named file. Returns a writer that -// uploads the content on Close. +// Example: writer, _ := medium.Create("reports/daily.txt") func (m *Medium) Create(filePath string) (goio.WriteCloser, error) { key := m.objectKey(filePath) if key == "" { @@ -469,8 +469,7 @@ func (m *Medium) Create(filePath string) (goio.WriteCloser, error) { }, nil } -// Append opens the named file for appending. It downloads the existing -// content (if any) and re-uploads the combined content on Close. +// Example: writer, _ := medium.Append("reports/daily.txt") func (m *Medium) Append(filePath string) (goio.WriteCloser, error) { key := m.objectKey(filePath) if key == "" { @@ -514,7 +513,7 @@ func (m *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { return m.Create(filePath) } -// Exists checks if a path exists (file or directory prefix). +// Example: ok := medium.Exists("reports/daily.txt") func (m *Medium) Exists(filePath string) bool { key := m.objectKey(filePath) if key == "" { @@ -546,7 +545,7 @@ func (m *Medium) Exists(filePath string) bool { return len(listOut.Contents) > 0 || len(listOut.CommonPrefixes) > 0 } -// IsDir checks if a path exists and is a directory (has objects under it as a prefix). +// Example: ok := medium.IsDir("reports") func (m *Medium) IsDir(filePath string) bool { key := m.objectKey(filePath) if key == "" { diff --git a/s3/s3_test.go b/s3/s3_test.go index c8c9228..c4fad8e 100644 --- a/s3/s3_test.go +++ b/s3/s3_test.go @@ -295,7 +295,7 @@ func TestS3_ReadWrite_Prefix_Good(t *testing.T) { func TestS3_EnsureDir_Good(t *testing.T) { m, _ := newTestMedium(t) - // EnsureDir is a no-op for S3 + // Example: err := m.EnsureDir("any/path") err := m.EnsureDir("any/path") assert.NoError(t, err) } diff --git a/sigil/crypto_sigil.go b/sigil/crypto_sigil.go index 6d3fca6..8903868 100644 --- a/sigil/crypto_sigil.go +++ b/sigil/crypto_sigil.go @@ -207,8 +207,8 @@ func (s *ShuffleMaskObfuscator) deriveMask(entropy []byte, length int) []byte { return mask } -// ChaChaPolySigil is returned by NewChaChaPolySigil and -// NewChaChaPolySigilWithObfuscator. +// Example: cipherSigil, _ := sigil.NewChaChaPolySigil(key) +// Example: cipherSigil, _ := sigil.NewChaChaPolySigilWithObfuscator(key, &sigil.ShuffleMaskObfuscator{}) type ChaChaPolySigil struct { Key []byte Obfuscator PreObfuscator diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go index 9e3c72e..9fd936d 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -18,7 +18,10 @@ import ( _ "modernc.org/sqlite" // Pure Go SQLite driver ) -// Medium is a SQLite-backed storage backend implementing the io.Medium interface. +// Medium stores filesystem-shaped content in SQLite. +// +// medium, _ := sqlite.New(sqlite.Options{Path: ":memory:"}) +// _ = medium.Write("config/app.yaml", "port: 8080") type Medium struct { database *sql.DB table string @@ -26,7 +29,7 @@ type Medium struct { var _ coreio.Medium = (*Medium)(nil) -// Options configures a SQLite-backed Medium. +// Example: medium, _ := sqlite.New(sqlite.Options{Path: ":memory:", Table: "files"}) type Options struct { // Path is the SQLite database path. Use ":memory:" for tests. Path string @@ -41,10 +44,8 @@ func normaliseTableName(table string) string { return table } -// New opens a SQLite-backed Medium at the provided database path. -// -// medium, _ := sqlite.New(sqlite.Options{Path: ":memory:", Table: "files"}) -// _ = medium.Write("config/app.yaml", "port: 8080") +// Example: medium, _ := sqlite.New(sqlite.Options{Path: ":memory:", Table: "files"}) +// _ = medium.Write("config/app.yaml", "port: 8080") func New(options Options) (*Medium, error) { if options.Path == "" { return nil, core.E("sqlite.New", "database path is required", nil) @@ -125,7 +126,7 @@ func (m *Medium) Write(filePath, content string) error { return m.WriteMode(filePath, content, 0644) } -// WriteMode saves the given content with explicit permissions. +// Example: _ = medium.WriteMode("keys/private.key", key, 0600) func (m *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { key := normaliseEntryPath(filePath) if key == "" { @@ -143,7 +144,7 @@ func (m *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { return nil } -// EnsureDir makes sure a directory exists, creating it if necessary. +// Example: _ = medium.EnsureDir("config") func (m *Medium) EnsureDir(filePath string) error { key := normaliseEntryPath(filePath) if key == "" { @@ -186,7 +187,7 @@ func (m *Medium) FileSet(filePath, content string) error { return m.Write(filePath, content) } -// Delete removes a file or empty directory. +// Example: _ = medium.Delete("config/app.yaml") func (m *Medium) Delete(filePath string) error { key := normaliseEntryPath(filePath) if key == "" { @@ -231,7 +232,7 @@ func (m *Medium) Delete(filePath string) error { return nil } -// DeleteAll removes a file or directory and all its contents recursively. +// Example: _ = medium.DeleteAll("config") func (m *Medium) DeleteAll(filePath string) error { key := normaliseEntryPath(filePath) if key == "" { @@ -255,7 +256,7 @@ func (m *Medium) DeleteAll(filePath string) error { return nil } -// Rename moves a file or directory from oldPath to newPath. +// Example: _ = medium.Rename("drafts/todo.txt", "archive/todo.txt") func (m *Medium) Rename(oldPath, newPath string) error { oldKey := normaliseEntryPath(oldPath) newKey := normaliseEntryPath(newPath) @@ -353,7 +354,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { return tx.Commit() } -// List returns the directory entries for the given path. +// Example: entries, _ := medium.List("config") func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { prefix := normaliseEntryPath(filePath) if prefix != "" { diff --git a/store/medium.go b/store/medium.go index 54db6bf..3a548d3 100644 --- a/store/medium.go +++ b/store/medium.go @@ -10,7 +10,10 @@ import ( coreio "dappco.re/go/core/io" ) -// Medium wraps a Store to satisfy the io.Medium interface. +// Example: medium, _ := store.NewMedium(store.Options{Path: "config.db"}) +// _ = medium.Write("app/theme", "midnight") +// entries, _ := medium.List("app") +// // Paths are mapped as group/key - the first segment is the group, // the rest is the key. List("") returns groups as directories, // List("group") returns keys as files. @@ -20,10 +23,8 @@ type Medium struct { var _ coreio.Medium = (*Medium)(nil) -// NewMedium exposes a Store as an io.Medium. -// -// medium, _ := store.NewMedium(store.Options{Path: "config.db"}) -// _ = medium.Write("app/theme", "midnight") +// Example: medium, _ := store.NewMedium(store.Options{Path: "config.db"}) +// _ = medium.Write("app/theme", "midnight") func NewMedium(options Options) (*Medium, error) { store, err := New(options) if err != nil { @@ -77,12 +78,12 @@ func (m *Medium) Write(entryPath, content string) error { return m.store.Set(group, key, content) } -// WriteMode ignores the requested mode because key-value entries do not store POSIX permissions. +// Example: _ = medium.WriteMode("app/theme", "midnight", 0600) func (m *Medium) WriteMode(entryPath, content string, _ fs.FileMode) error { return m.Write(entryPath, content) } -// EnsureDir is a no-op — groups are created implicitly on Set. +// Example: _ = medium.EnsureDir("app") func (m *Medium) EnsureDir(_ string) error { return nil } @@ -149,8 +150,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { return m.store.Delete(oldGroup, oldKey) } -// List returns directory entries. Empty path returns groups. -// A group path returns keys in that group. +// Example: entries, _ := medium.List("app") func (m *Medium) List(entryPath string) ([]fs.DirEntry, error) { group, key := splitGroupKeyPath(entryPath) @@ -187,7 +187,7 @@ func (m *Medium) List(entryPath string) ([]fs.DirEntry, error) { return entries, nil } -// Stat returns file info for a group (dir) or key (file). +// Example: info, _ := medium.Stat("app/theme") func (m *Medium) Stat(entryPath string) (fs.FileInfo, error) { group, key := splitGroupKeyPath(entryPath) if group == "" { diff --git a/store/store.go b/store/store.go index 9b5a265..1929093 100644 --- a/store/store.go +++ b/store/store.go @@ -13,21 +13,21 @@ import ( // NotFoundError is returned when a key does not exist in the store. var NotFoundError = errors.New("key not found") -// Store is returned by New for grouped key/value access. +// Store is the grouped key/value database returned by New. +// +// keyValueStore, _ := store.New(store.Options{Path: ":memory:"}) type Store struct { database *sql.DB } -// Options configures New. +// Example: keyValueStore, _ := store.New(store.Options{Path: ":memory:"}) type Options struct { // Path is the SQLite database path. Use ":memory:" for tests. Path string } -// New opens a SQLite-backed key/value store. -// -// keyValueStore, _ := store.New(store.Options{Path: ":memory:"}) -// _ = keyValueStore.Set("app", "theme", "midnight") +// Example: keyValueStore, _ := store.New(store.Options{Path: ":memory:"}) +// _ = keyValueStore.Set("app", "theme", "midnight") func New(options Options) (*Store, error) { if options.Path == "" { return nil, core.E("store.New", "database path is required", fs.ErrInvalid) diff --git a/workspace/service.go b/workspace/service.go index 6b93ae4..c2bfc88 100644 --- a/workspace/service.go +++ b/workspace/service.go @@ -11,7 +11,9 @@ import ( "dappco.re/go/core/io" ) -// Workspace is the interface returned by New. +// Workspace is the workspace service interface returned by New. +// +// service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider}) type Workspace interface { CreateWorkspace(identifier, password string) (string, error) SwitchWorkspace(workspaceID string) error @@ -24,7 +26,7 @@ type CryptProvider interface { CreateKeyPair(name, passphrase string) (string, error) } -// Options configures New. +// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider}) type Options struct { // Core is the Core runtime used by the service. Core *core.Core @@ -32,7 +34,7 @@ type Options struct { Crypt CryptProvider } -// Service is the concrete Workspace implementation. +// Service is the Workspace implementation returned by New. type Service struct { core *core.Core crypt CryptProvider @@ -44,10 +46,8 @@ type Service struct { var _ Workspace = (*Service)(nil) -// New creates an encrypted workspace service. -// -// service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider}) -// workspaceID, _ := service.CreateWorkspace("alice", "pass123") +// Example: service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: cryptProvider}) +// workspaceID, _ := service.CreateWorkspace("alice", "pass123") func New(options Options) (*Service, error) { home := resolveWorkspaceHomeDirectory() if home == "" {