From 71c3201002e68125736ad3510187f92bb3ec5687 Mon Sep 17 00:00:00 2001
From: Victor Conner <victor@conner.dev>
Date: Mon, 8 Jul 2024 12:33:30 +0200
Subject: [PATCH] Improve the documentation for the public API

---
 .golangci.yml   | 54 ++++++---------------------------
 cache.go        | 81 ++++++++++++++++++++++++++++++++++++++++++++-----
 distribution.go |  8 ++---
 fetch.go        | 73 ++++++++++++++++++++++++++++++++++++++------
 keys.go         | 54 ++++++++++++++++++++++++---------
 keys_test.go    |  4 +--
 metrics.go      | 10 +++---
 options.go      |  2 +-
 passthrough.go  | 69 +++++++++++++++++++++++++++++++++++++----
 shard.go        | 20 +++++++++++-
 10 files changed, 282 insertions(+), 93 deletions(-)

diff --git a/.golangci.yml b/.golangci.yml
index 1f3005c..c9313c7 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -36,18 +36,16 @@ linters:
     - asciicheck # checks that your code does not contain non-ASCII identifiers
     - bidichk # checks for dangerous unicode character sequences
     - bodyclose # checks whether HTTP response body is closed successfully
-    # - cyclop # checks function and package cyclomatic complexity
+    - cyclop # checks function and package cyclomatic complexity
     - dupl # tool for code clone detection
     - durationcheck # checks for two durations multiplied together
     - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
     - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
-    - execinquery # checks query string in Query function which reads your Go src files and warning it finds
     - exhaustive # checks exhaustiveness of enum switch statements
     - exportloopref # checks for pointers to enclosing loop variables
     - forbidigo # forbids identifiers
     - funlen # tool for detection of long functions
     - gocheckcompilerdirectives # validates go compiler directive comments (//go:)
-    # - gochecknoglobals # checks that no global variables exist
     - gochecknoinits # checks that no init functions are present in Go code
     - gochecksumtype # checks exhaustiveness on Go "sum types"
     - gocognit # computes and checks the cognitive complexity of functions
@@ -98,36 +96,28 @@ linters:
     - decorder # checks declaration order and count of types, constants, variables and functions
     - dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
     - paralleltest #  detects missing usage of t.Parallel() method in your Go test
+    - tagalign # checks that struct tags are well aligned
 
-    ## you may want to enable
+    ## may want to enable
+    # - wrapcheck # checks that errors returned from external packages are wrapped
     #- gci # controls golang package import order and makes it always deterministic
     #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega
     #- godox # detects FIXME, TODO and other comment keywords
     #- goheader # checks is file header matches to pattern
     #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters
     #- interfacebloat # checks the number of methods inside an interface
-    #- ireturn # accept interfaces, return concrete types
-    #- tagalign # checks that struct tags are well aligned
-    #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
-    #- wrapcheck # checks that errors returned from external packages are wrapped
-    #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event
+    # - ireturn # accept interfaces, return concrete types
+    # - varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
     # - nlreturn # checks for a new line before return and branch statements to increase code clarity
 
     ## disabled
     # - lll # reports long lines
-    # - exhaustruct # [highly recommend to enable] checks if all structure fields are initialized
-    #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages
-    #- dupword # [useless without config] checks for duplicate words in the source code
-    #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted
-    #- forcetypeassert # [replaced by errcheck] finds forced type assertions
+    # - exhaustruct # checks if all structure fields are initialized
     #- goerr113 # [too strict] checks the errors handling expressions
-    #- gofmt # [replaced by goimports] checks whether code was gofmt-ed
-    #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed
     #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase
     #- grouper # analyzes expression groups
     #- importas # enforces consistent import aliases
     #- maintidx # measures the maintainability index of each function
-    #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
     #- tagliatelle # checks the struct tags
     #- contextcheck # Check whether the function uses a non-inherited context.
 
@@ -163,33 +153,9 @@ linters-settings:
   nlreturn:
     block-size: 8
 
-  exhaustruct:
-    # Default: []
-    exclude:
-      - ".*_test.go$"
-      # std libs
-      - "^net/http.Client$"
-      - "^net/http.Cookie$"
-      - "^net/http.Request$"
-      - "^net/http.Response$"
-      - "^net/http.Server$"
-      - "^net/http.Transport$"
-      - "^net/url.URL$"
-      - "^os/exec.Cmd$"
-      - "^reflect.StructField$"
-      # public libs
-      - "^github.com/Shopify/sarama.Config$"
-      - "^github.com/Shopify/sarama.ProducerMessage$"
-      - "^github.com/mitchellh/mapstructure.DecoderConfig$"
-      - "^github.com/prometheus/client_golang/.+Opts$"
-      - "^github.com/spf13/cobra.Command$"
-      - "^github.com/spf13/cobra.CompletionOptions$"
-      - "^github.com/stretchr/testify/mock.Mock$"
-      - "^github.com/testcontainers/testcontainers-go.+Request$"
-      - "^github.com/testcontainers/testcontainers-go.FromDockerfile$"
-      - "^golang.org/x/tools/go/analysis.Analyzer$"
-      - "^google.golang.org/protobuf/.+Options$"
-      - "^gopkg.in/yaml.v3.Node$"
+  gosec:
+    excludes:
+      - G404 # Ignore insecure random number source.
 
   funlen:
     # Checks the number of lines in a function.
diff --git a/cache.go b/cache.go
index 65034a7..447e10e 100644
--- a/cache.go
+++ b/cache.go
@@ -135,6 +135,14 @@ func (c *Client[T]) getWithState(key string) (value T, exists, markedAsMissing,
 }
 
 // Get retrieves a single value from the cache.
+//
+// Parameters:
+//
+//	key - The key to be retrieved.
+//
+// Returns:
+//
+//	The value corresponding to the key and a boolean indicating if the value was found.
 func (c *Client[T]) Get(key string) (T, bool) {
 	shard := c.getShard(key)
 	val, ok, markedAsMissing, refresh := shard.get(key)
@@ -143,6 +151,14 @@ func (c *Client[T]) Get(key string) (T, bool) {
 }
 
 // GetMany retrieves multiple values from the cache.
+//
+// Parameters:
+//
+//	keys - The list of keys to be retrieved.
+//
+// Returns:
+//
+//	A map of keys to their corresponding values.
 func (c *Client[T]) GetMany(keys []string) map[string]T {
 	records := make(map[string]T, len(keys))
 	for _, key := range keys {
@@ -155,9 +171,18 @@ func (c *Client[T]) GetMany(keys []string) map[string]T {
 
 // GetManyKeyFn follows the same API as GetOrFetchBatch and PassthroughBatch.
 // You provide it with a slice of IDs and a keyFn, which is applied to create
-// the cache key. The returned map uses the IDs as keys instead of the cache key.
-// If you've used ScanKeys to retrieve the actual keys, you can retrieve the records
-// using GetMany instead.
+// the cache key. The returned map uses the IDs as keys instead of the cache
+// key. If you've used ScanKeys to retrieve the actual keys, you can retrieve
+// the records using GetMany instead.
+//
+// Parameters:
+//
+//	ids - The list of IDs to be retrieved.
+//	keyFn - A function that generates the cache key for each ID.
+//
+// Returns:
+//
+//	A map of IDs to their corresponding values.
 func (c *Client[T]) GetManyKeyFn(ids []string, keyFn KeyFn) map[string]T {
 	records := make(map[string]T, len(ids))
 	for _, id := range ids {
@@ -168,7 +193,16 @@ func (c *Client[T]) GetManyKeyFn(ids []string, keyFn KeyFn) map[string]T {
 	return records
 }
 
-// Set writes a single value to the cache. Returns true if it triggered an eviction.
+// Set writes a single value to the cache.
+//
+// Parameters:
+//
+//	key - The key to be set.
+//	value - The value to be associated with the key.
+//
+// Returns:
+//
+//	A boolean indicating if the set operation triggered an eviction.
 func (c *Client[T]) Set(key string, value T) bool {
 	shard := c.getShard(key)
 	return shard.set(key, value, false)
@@ -181,7 +215,15 @@ func (c *Client[T]) StoreMissingRecord(key string) bool {
 	return shard.set(key, zero, true)
 }
 
-// SetMany writes a map of key value pairs to the cache.
+// SetMany writes a map of key-value pairs to the cache.
+//
+// Parameters:
+//
+//	records - A map of keys to values to be set in the cache.
+//
+// Returns:
+//
+//	A boolean indicating if any of the set operations triggered an eviction.
 func (c *Client[T]) SetMany(records map[string]T) bool {
 	var triggeredEviction bool
 	for key, value := range records {
@@ -193,9 +235,18 @@ func (c *Client[T]) SetMany(records map[string]T) bool {
 	return triggeredEviction
 }
 
-// SetManyKeyFn follows the same API as GetOrFetchBatch and PassThroughBatch. It
-// takes a map of records where the keyFn is applied to each key in the map
+// SetManyKeyFn follows the same API as GetOrFetchBatch and PassthroughBatch.
+// It takes a map of records where the keyFn is applied to each key in the map
 // before it's stored in the cache.
+//
+// Parameters:
+//
+//	records - A map of IDs to values to be set in the cache.
+//	cacheKeyFn - A function that generates the cache key for each ID.
+//
+// Returns:
+//
+//	A boolean indicating if any of the set operations triggered an eviction.
 func (c *Client[T]) SetManyKeyFn(records map[string]T, cacheKeyFn KeyFn) bool {
 	var triggeredEviction bool
 	for id, value := range records {
@@ -208,6 +259,10 @@ func (c *Client[T]) SetManyKeyFn(records map[string]T, cacheKeyFn KeyFn) bool {
 }
 
 // ScanKeys returns a list of all keys in the cache.
+//
+// Returns:
+//
+//	A slice of strings representing all the keys in the cache.
 func (c *Client[T]) ScanKeys() []string {
 	keys := make([]string, 0, c.Size())
 	for _, shard := range c.shards {
@@ -217,6 +272,10 @@ func (c *Client[T]) ScanKeys() []string {
 }
 
 // Size returns the number of entries in the cache.
+//
+// Returns:
+//
+//	An integer representing the total number of entries in the cache.
 func (c *Client[T]) Size() int {
 	var sum int
 	for _, shard := range c.shards {
@@ -226,12 +285,20 @@ func (c *Client[T]) Size() int {
 }
 
 // Delete removes a single entry from the cache.
+//
+// Parameters:
+//
+//	key: The key of the entry to be removed.
 func (c *Client[T]) Delete(key string) {
 	shard := c.getShard(key)
 	shard.delete(key)
 }
 
 // NumKeysInflight returns the number of keys that are currently being fetched.
+//
+// Returns:
+//
+//	An integer representing the total number of keys that are currently being fetched.
 func (c *Client[T]) NumKeysInflight() int {
 	c.inFlightMutex.Lock()
 	defer c.inFlightMutex.Unlock()
diff --git a/distribution.go b/distribution.go
index 6e1d6f8..ec67fd5 100644
--- a/distribution.go
+++ b/distribution.go
@@ -159,7 +159,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
 	}
 
 	return func(ctx context.Context, ids []string) (map[string]V, error) {
-		// We need to be able to lookup the ID of the record based on the key.
+		// We need to be able to look up the ID of the record based on the key.
 		keyIDMap := make(map[string]string, len(ids))
 		keys := make([]string, 0, len(ids))
 		for _, id := range ids {
@@ -193,7 +193,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
 
 			// If distributedStaleStorage isn't enabled it means all records are fresh, otherwise checked the CreatedAt time.
 			if !c.distributedEarlyRefreshes || c.clock.Since(record.CreatedAt) < c.distributedRefreshAfterDuration {
-				// We never wan't to return missing records.
+				// We never want to return missing records.
 				if !record.IsMissingRecord {
 					fresh[id] = record.Value
 				} else {
@@ -205,7 +205,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
 			idsToRefresh = append(idsToRefresh, id)
 			c.reportDistributedRefresh()
 
-			// We never wan't to return missing records.
+			// We never want to return missing records.
 			if !record.IsMissingRecord {
 				stale[id] = record.Value
 			} else {
@@ -218,7 +218,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
 		}
 
 		dataSourceResponses, err := fetchFn(ctx, idsToRefresh)
-		// Incase of an error, we'll proceed with the ones we got from the distributed storage.
+		// In case of an error, we'll proceed with the ones we got from the distributed storage.
 		if err != nil {
 			for i := 0; i < len(stale); i++ {
 				c.reportDistributedStaleFallback()
diff --git a/fetch.go b/fetch.go
index 07c94b8..09f24fb 100644
--- a/fetch.go
+++ b/fetch.go
@@ -57,15 +57,40 @@ func getFetch[V, T any](ctx context.Context, c *Client[T], key string, fetchFn F
 }
 
 // GetOrFetch attempts to retrieve the specified key from the cache. If the value
-// is absent, it invokes the "fetchFn" function to obtain it and then stores
-// the result. Additionally, when background refreshes is enabled, GetOrFetch
-// determines if the record needs refreshing and, if necessary, schedules this
-// task for background execution.
+// is absent, it invokes the fetchFn function to obtain it and then stores the result.
+// Additionally, when background refreshes are enabled, GetOrFetch determines if the record
+// needs refreshing and, if necessary, schedules this task for background execution.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	key - The key to be fetched.
+//	fetchFn - Used to retrieve the data from the underlying data source if the key is not found in the cache.
+//
+// Returns:
+//
+//	The value corresponding to the key and an error if one occurred.
 func (c *Client[T]) GetOrFetch(ctx context.Context, key string, fetchFn FetchFn[T]) (T, error) {
 	return getFetch[T, T](ctx, c, key, fetchFn)
 }
 
 // GetOrFetch is a convenience function that performs type assertion on the result of client.GetOrFetch.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	c - The cache client.
+//	key - The key to be fetched.
+//	fetchFn - Used to retrieve the data from the underlying data source if the key is not found in the cache.
+//
+// Returns:
+//
+//	The value corresponding to the key and an error if one occurred.
+//
+// Type Parameters:
+//
+//	V - The type returned by the fetchFn. Must be assignable to T.
+//	T - The type stored in the cache.
 func GetOrFetch[V, T any](ctx context.Context, c *Client[T], key string, fetchFn FetchFn[V]) (V, error) {
 	res, err := getFetch[V, T](ctx, c, key, fetchFn)
 	return unwrap[V](res, err)
@@ -104,16 +129,46 @@ func getFetchBatch[V, T any](ctx context.Context, c *Client[T], ids []string, ke
 	return cachedRecords, nil
 }
 
-// GetOrFetchBatch attempts to retrieve the specified ids from the cache. If any
-// of the values are absent, it invokes the fetchFn function to obtain them and
-// then stores the result. Additionally, when background refreshes is enabled,
-// GetOrFetch determines if any of the records needs refreshing and, if
+// GetOrFetchBatch attempts to retrieve the specified ids from the cache. If
+// any of the values are absent, it invokes the fetchFn function to obtain them
+// and then stores the result. Additionally, when background refreshes are
+// enabled, GetOrFetch determines if any of the records need refreshing and, if
 // necessary, schedules this to be performed in the background.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	ids - The list of IDs to be fetched.
+//	keyFn - Used to generate the cache key for each ID.
+//	fetchFn - Used to retrieve the data from the underlying data source if any IDs are not found in the cache.
+//
+// Returns:
+//
+//	A map of IDs to their corresponding values and an error if one occurred.
 func (c *Client[T]) GetOrFetchBatch(ctx context.Context, ids []string, keyFn KeyFn, fetchFn BatchFetchFn[T]) (map[string]T, error) {
 	return getFetchBatch[T, T](ctx, c, ids, keyFn, fetchFn)
 }
 
-// GetOrFetchBatch is a convenience function that performs type assertion on the result of client.GetOrFetchBatch.
+// GetOrFetchBatch is a convenience function that performs type assertion on the
+// result of client.GetOrFetchBatch.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	c - The cache client.
+//	ids - The list of IDs to be fetched.
+//	keyFn - Used to prefix each ID in order to create a unique cache key.
+//	fetchFn - Used to retrieve the data from the underlying data source.
+//
+// Returns:
+//
+//	A map of ids to their corresponding values and an error if one occurred.
+//
+// Type Parameters:
+//
+//	V - The type returned by the fetchFn. Must be assignable to T.
+//	T - The type stored in the cache.
+
 func GetOrFetchBatch[V, T any](ctx context.Context, c *Client[T], ids []string, keyFn KeyFn, fetchFn BatchFetchFn[V]) (map[string]V, error) {
 	res, err := getFetchBatch[V, T](ctx, c, ids, keyFn, fetchFn)
 	return unwrapBatch[V](res, err)
diff --git a/keys.go b/keys.go
index 0820a9a..fd41153 100644
--- a/keys.go
+++ b/keys.go
@@ -76,13 +76,22 @@ func (c *Client[T]) handleTime(v reflect.Value) string {
 	return "empty-time"
 }
 
-// Permutated key takes a prefix, and a struct where the fields are
-// concatenated with in order to make a unique cache key. Passing
-// anything but a struct for "permutationStruct" will result in a panic.
+// PermutatedKey takes a prefix and a struct where the fields are concatenated
+// in order to create a unique cache key. Passing anything but a struct for
+// "permutationStruct" will result in a panic. The cache will only use the
+// EXPORTED fields of the struct to construct the key. The permutation struct
+// should be FLAT, with no nested structs. The fields can be any of the basic
+// types, as well as slices and time.Time values.
 //
-// The cache will only use the EXPORTED fields of the struct to construct the key.
-// The permutation struct should be FLAT, with no nested structs. The fields can
-// be any of the basic types, as well as slices and time.Time values.
+// Parameters:
+//
+//	prefix - The prefix for the cache key.
+//	permutationStruct - A struct whose fields are concatenated to form a unique cache key.
+//	Only exported fields are used.
+//
+// Returns:
+//
+//	A string to be used as the cache key.
 func (c *Client[T]) PermutatedKey(prefix string, permutationStruct interface{}) string {
 	var sb strings.Builder
 	sb.WriteString(prefix)
@@ -148,8 +157,17 @@ func (c *Client[T]) PermutatedKey(prefix string, permutationStruct interface{})
 	return sb.String()
 }
 
-// BatchKeyFn provides a function for that can be used in conjunction with "GetOrFetchBatch".
-// It takes in a prefix, and returns a function that will append an ID suffix for each item.
+// BatchKeyFn provides a function that can be used in conjunction with
+// "GetOrFetchBatch". It takes in a prefix and returns a function that will
+// append the ID as a suffix for each item.
+//
+// Parameters:
+//
+//	prefix - The prefix to be used for each cache key.
+//
+// Returns:
+//
+//	A function that takes an ID and returns a cache key string with the given prefix and ID.
 func (c *Client[T]) BatchKeyFn(prefix string) KeyFn {
 	return func(id string) string {
 		return fmt.Sprintf("%s-ID-%s", prefix, id)
@@ -157,13 +175,21 @@ func (c *Client[T]) BatchKeyFn(prefix string) KeyFn {
 }
 
 // PermutatedBatchKeyFn provides a function that can be used in conjunction
-// with GetOrFetchBatch. It takes a prefix, and a struct where the fields are
-// concatenated with the id in order to make a unique cache key. Passing
-// anything but a struct for "permutationStruct" will result in a panic.
+// with GetOrFetchBatch. It takes a prefix and a struct where the fields are
+// concatenated with the ID in order to make a unique cache key. Passing
+// anything but a struct for "permutationStruct" will result in a panic. The
+// cache will only use the EXPORTED fields of the struct to construct the key.
+// The permutation struct should be FLAT, with no nested structs. The fields
+// can be any of the basic types, as well as slices and time.Time values.
+//
+// Parameters:
+//
+//	prefix - The prefix for the cache key.
+//	permutationStruct - A struct whose fields are concatenated to form a unique cache key. Only exported fields are used.
+//
+// Returns:
 //
-// The cache will only use the EXPORTED fields of the struct to construct the key.
-// The permutation struct should be FLAT, with no nested structs. The fields can
-// be any of the basic types, as well as slices and time.Time values.
+//	A function that takes an ID and returns a cache key string with the given prefix, permutation struct fields, and ID.
 func (c *Client[T]) PermutatedBatchKeyFn(prefix string, permutationStruct interface{}) KeyFn {
 	return func(id string) string {
 		key := c.PermutatedKey(prefix, permutationStruct)
diff --git a/keys_test.go b/keys_test.go
index 9cb2b04..f86c6fe 100644
--- a/keys_test.go
+++ b/keys_test.go
@@ -73,7 +73,7 @@ func TestTimeBasedKeys(t *testing.T) {
 	}
 }
 
-func TestPermutatedRelativeTimeKeys(t *testing.T) {
+func TestPermutedTimeKeys(t *testing.T) {
 	t.Parallel()
 
 	c := sturdyc.New[any](100, 1, time.Hour, 5,
@@ -169,7 +169,7 @@ func TestPermutatedKeyHandlesEmptySlices(t *testing.T) {
 	}
 }
 
-func TestPermutatedBatchKeyFn(t *testing.T) {
+func TestPermutedBatchKeyFn(t *testing.T) {
 	t.Parallel()
 
 	c := sturdyc.New[any](100, 1, time.Hour, 5,
diff --git a/metrics.go b/metrics.go
index ba9f11f..55ea6c5 100644
--- a/metrics.go
+++ b/metrics.go
@@ -8,18 +8,18 @@ type MetricsRecorder interface {
 	// Refresh is called when a get operation results in a refresh.
 	Refresh()
 	// MissingRecord is called every time the cache is asked to
-	// lookup a key which has been marked as missing.
+	// look up a key which has been marked as missing.
 	MissingRecord()
 	// ForcedEviction is called when the cache reaches its capacity, and has to
 	// evict keys in order to write a new one.
 	ForcedEviction()
-	// EntiresEvicted is called when the cache evicts keys from a shard.
+	// EntriesEvicted is called when the cache evicts keys from a shard.
 	EntriesEvicted(int)
 	// ShardIndex is called to report which shard it was that performed an operation.
 	ShardIndex(int)
 	// CacheBatchRefreshSize is called to report the size of the batch refresh.
 	CacheBatchRefreshSize(size int)
-	// ObserveCacheSize is called to report the size of the cache.metri
+	// ObserveCacheSize is called to report the size of the cache.
 	ObserveCacheSize(callback func() int)
 }
 
@@ -27,12 +27,12 @@ type DistributedMetricsRecorder interface {
 	MetricsRecorder
 	// DistributedCacheHit is called for every key that results in a cache hit.
 	DistributedCacheHit()
-	// DistributedCacheHit is called for every key that results in a cache miss.
+	// DistributedCacheMiss is called for every key that results in a cache miss.
 	DistributedCacheMiss()
 	// DistributedRefresh is called when we retrieve a record from
 	// the distributed storage that should be refreshed.
 	DistributedRefresh()
-	// DistributetedMissingRecord is called when we retrieve a record from the
+	// DistributedMissingRecord is called when we retrieve a record from the
 	// distributed storage that has been marked as a missing record.
 	DistributedMissingRecord()
 	// DistributedFallback is called when you are using a distributed storage
diff --git a/options.go b/options.go
index eb72750..7322f02 100644
--- a/options.go
+++ b/options.go
@@ -91,7 +91,7 @@ func WithRelativeTimeKeyFormat(truncation time.Duration) Option {
 
 // WithLog allows you to set a custom logger for the cache. The cache isn't chatty,
 // and will only log warnings and errors that would be a nightmare to debug. If you
-// absolutely don't want any logs, you can pass in the sturydc.NoopLogger.
+// absolutely don't want any logs, you can pass in the sturdyc.NoopLogger.
 func WithLog(log Logger) Option {
 	return func(c *Config) {
 		c.log = log
diff --git a/passthrough.go b/passthrough.go
index ac1a521..829fe53 100644
--- a/passthrough.go
+++ b/passthrough.go
@@ -4,8 +4,18 @@ import (
 	"context"
 )
 
-// Passthrough is always going to try and retrieve the latest data by calling the
-// fetchFn. The cache is used as a fallback if the fetchFn returns an error.
+// Passthrough attempts to retrieve the latest data by calling the provided fetchFn.
+// If fetchFn encounters an error, the cache is used as a fallback.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	key - The key to be fetched.
+//	fetchFn - Used to retrieve the data from the underlying data source.
+//
+// Returns:
+//
+//	The value and an error if one occurred and the key was not found in the cache.
 func (c *Client[T]) Passthrough(ctx context.Context, key string, fetchFn FetchFn[T]) (T, error) {
 	res, err := callAndCache(ctx, c, key, fetchFn)
 	if err == nil {
@@ -19,14 +29,43 @@ func (c *Client[T]) Passthrough(ctx context.Context, key string, fetchFn FetchFn
 	return res, err
 }
 
-// Passthrough is a convenience function that performs type assertion on the result of client.Passthrough.
+// Passthrough is a convenience function that performs type assertion on the
+// result of client.PassthroughBatch.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	c - The cache client.
+//	key - The key to be fetched.
+//	fetchFn - Used to retrieve the data from the underlying data source.
+//
+// Returns:
+//
+//	The value and an error if one occurred and the key was not found in the cache.
+//
+// Type Parameters:
+//
+//	V - The type returned by the fetchFn. Must be assignable to T.
+//	T - The type stored in the cache.
 func Passthrough[T, V any](ctx context.Context, c *Client[T], key string, fetchFn FetchFn[V]) (V, error) {
 	value, err := c.Passthrough(ctx, key, wrap[T](fetchFn))
 	return unwrap[V](value, err)
 }
 
-// PassthroughBatch is always going to try and retrieve the latest data by calling
-// the fetchFn. The cache is used as a fallback if the fetchFn returns an error.
+// PassthroughBatch attempts to retrieve the latest data by calling the provided fetchFn.
+// If fetchFn encounters an error, the cache is used as a fallback.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	ids - The list of IDs to be fetched.
+//	keyFn - Used to prefix each ID in order to create a unique cache key.
+//	fetchFn - Used to retrieve the data from the underlying data source.
+//
+// Returns:
+//
+//	A map of IDs to their corresponding values, and an error if one occurred and
+//	none of the IDs were found in the cache.
 func (c *Client[T]) PassthroughBatch(ctx context.Context, ids []string, keyFn KeyFn, fetchFn BatchFetchFn[T]) (map[string]T, error) {
 	res, err := callAndCacheBatch(ctx, c, callBatchOpts[T, T]{ids, keyFn, fetchFn})
 	if err == nil {
@@ -41,7 +80,25 @@ func (c *Client[T]) PassthroughBatch(ctx context.Context, ids []string, keyFn Ke
 	return res, err
 }
 
-// Passthrough is a convenience function that performs type assertion on the result of client.PassthroughBatch.
+// PassthroughBatch is a convenience function that performs type assertion on the
+// result of client.PassthroughBatch.
+//
+// Parameters:
+//
+//	ctx - The context to be used for the request.
+//	c - The cache client.
+//	ids - The list of IDs to be fetched.
+//	keyFn - Used to prefix each ID in order to create a unique cache key.
+//	fetchFn - Used to retrieve the data from the underlying data source.
+//
+// Returns:
+//
+//	A map of ids to their corresponding values and an error if one occurred.
+//
+// Type Parameters:
+//
+//	V - The type returned by the fetchFn. Must be assignable to T.
+//	T - The type stored in the cache.
 func PassthroughBatch[V, T any](ctx context.Context, c *Client[T], ids []string, keyFn KeyFn, fetchFn BatchFetchFn[V]) (map[string]V, error) {
 	res, err := c.PassthroughBatch(ctx, ids, keyFn, wrapBatch[T](fetchFn))
 	return unwrapBatch[V](res, err)
diff --git a/shard.go b/shard.go
index 09e05c2..f3813bf 100644
--- a/shard.go
+++ b/shard.go
@@ -6,6 +6,7 @@ import (
 	"time"
 )
 
+// shard is a thread-safe data structure that holds a subset of the cache entries.
 type shard[T any] struct {
 	sync.RWMutex
 	*Config
@@ -15,6 +16,7 @@ type shard[T any] struct {
 	evictionPercentage int
 }
 
+// newShard creates a new shard and returns a pointer to it.
 func newShard[T any](capacity int, ttl time.Duration, evictionPercentage int, cfg *Config) *shard[T] {
 	return &shard[T]{
 		Config:             cfg,
@@ -25,6 +27,7 @@ func newShard[T any](capacity int, ttl time.Duration, evictionPercentage int, cf
 	}
 }
 
+// size returns the number of entries in the shard.
 func (s *shard[T]) size() int {
 	s.RLock()
 	defer s.RUnlock()
@@ -66,6 +69,18 @@ func (s *shard[T]) forceEvict() {
 	s.reportEntriesEvicted(entriesEvicted)
 }
 
+// get retrieves attempts to retrieve a value from the shard.
+//
+// Parameters:
+//
+//	key: The key for which the value is to be retrieved.
+//
+// Returns:
+//
+//	val: The value associated with the key, if it exists.
+//	exists: A boolean indicating if the value exists in the shard.
+//	markedAsMissing: A boolean indicating if the key has been marked as a missing record.
+//	refresh: A boolean indicating if the value should be refreshed in the background.
 func (s *shard[T]) get(key string) (val T, exists, markedAsMissing, refresh bool) {
 	s.RLock()
 	item, ok := s.entries[key]
@@ -106,7 +121,8 @@ func (s *shard[T]) get(key string) (val T, exists, markedAsMissing, refresh bool
 	return item.value, true, item.isMissingRecord, false
 }
 
-// set sets a key-value pair in the shard. Returns true if it triggered an eviction.
+// set writes a key-value pair to the shard and returns a
+// boolean indicating whether an eviction was performed.
 func (s *shard[T]) set(key string, value T, isMissingRecord bool) bool {
 	s.Lock()
 	defer s.Unlock()
@@ -147,12 +163,14 @@ func (s *shard[T]) set(key string, value T, isMissingRecord bool) bool {
 	return evict
 }
 
+// delete removes a key from the shard.
 func (s *shard[T]) delete(key string) {
 	s.Lock()
 	defer s.Unlock()
 	delete(s.entries, key)
 }
 
+// keys returns all non-expired keys in the shard.
 func (s *shard[T]) keys() []string {
 	s.RLock()
 	defer s.RUnlock()