Skip to content

Add list cache support#894

Merged
abnegate merged 56 commits into
mainfrom
feat/cached-find
Jun 23, 2026
Merged

Add list cache support#894
abnegate merged 56 commits into
mainfrom
feat/cached-find

Conversation

@premtsd-code

@premtsd-code premtsd-code commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • extend Database::withCache() as a generic cache-aside helper that keeps caller usage to key, callback, and optional hash
  • make query-document cache payload handling internal to withCache():
    • scalar/static values are cached and returned as-is
    • single Document values are stored as arrays and restored on cache hits
    • homogeneous top-level Document[] values are stored as arrays and restored on cache hits
    • mixed or nested Document payloads are not cached, so the callback shape is preserved
  • keep query cache behavior aligned with Appwrite list-cache TTL: read-only, role-scoped cache fields
  • return null from getQueryCacheField() for non-read permission modes so callers can bypass cache and run the callback directly
  • add QueryCache helpers for collection/query-scoped cached values:
    • getQueryCacheKey()
    • getQueryCacheField()
    • purgeQueryCache()
  • keep query-cache key/field primitives in Database so collection schema, query state, filters, relationships, auth roles, and field type remain hashed consistently
  • keep Query::orderRandom() cache bypass as caller policy: callers should skip withCache() for random ordering

Usage

Callers cache read query-derived values by composing the Database-owned key helpers and withCache():

$collection = $db->getCollection('wafRules');
$key = $db->getQueryCacheKey($collection->getId(), '_' . $projectSequence);
$field = $db->getQueryCacheField(
    collection: $collection,
    queries: $queries,
    field: 'documents',
);

$rules = $db->getAuthorization()->skip(fn () => $db->withCache(
    key: $key,
    hash: $field,
    callback: fn () => $db->find(
        collection: $collection->getId(),
        queries: $queries,
    ),
));

For variable permission modes, bypass cache when the field is null:

$field = $db->getQueryCacheField(
    collection: $collection,
    queries: $queries,
    field: 'documents',
    forPermission: $forPermission,
);

$callback = fn () => $db->find(
    collection: $collection->getId(),
    queries: $queries,
    forPermission: $forPermission,
);

$documents = $field === null
    ? $callback()
    : $db->withCache(key: $key, hash: $field, callback: $callback);

Invalidate cached query fields for the same collection namespace after writes with:

$db->purgeQueryCache('wafRules', '_' . $projectSequence);

For scalar values such as count, sum, or plain arrays, callers use the same withCache() API. Document payload serialization and restoration are internal.

Cache Shape

Query-cache entries use:

{cacheName}-cache:{hostname}:{namespace}:{tenant}:collection:{collection}:query

The hash field is:

{schemaHash}:{queryStateHash}:{field}

The query-state hash includes the active authorization roles, database id, query serialization, relationship state, and active filter signatures. This follows Appwrite's list-cache TTL model: cached query entries are read-oriented and role-scoped, so callers with different authorization contexts do not share entries.

The cached value envelope is:

['value' => $scalar]

or, for documents:

[
    'collection' => 'wafRules',
    'type' => 'document' | 'documents',
    'value' => $payload,
]

Tests

  • composer format
  • composer lint
  • vendor/bin/phpunit tests/unit/QueryCacheTest.php tests/unit/CacheKeyTest.php

composer check is currently blocked locally by the existing Connection.php PHPStan issue where Swoole\\Database\\DetectsLostConnections is not discovered by the local analyzer setup.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a generic withCache() cache-aside method to Database, along with public getFindCacheKey() and getFindCacheField() helpers backed by deterministic internal helpers (serializeFindCacheQuery, getFindCacheSchemaHash, getActiveFilterSignatures). Implements public cachedFind() and purgeCachedFind() APIs that wrap find queries in cache-aside logic, skipping cache for random-order queries and re-validating permissions on cache hits. Updates getCacheKeys() to use the centralized filter-signature helper. Tests cover both the cache-aside behavior, find cache-key stability, end-to-end cachedFind lifecycle, and include custom in-memory cache adapters with TTL support.

Changes

Cache-aside helper and find cache-key infrastructure

Layer / File(s) Summary
withCache() cache-aside implementation
src/Database/Database.php
Adds withCache(key, callback, hash) implementing cache-aside: loads by key/hash, treats false/missing as a miss, purges rejected entries, invokes the callback on miss, and best-effort saves results that are not false, with warning-level error handling.
Find cache-key/field helpers and filter-signature centralization
src/Database/Database.php
Updates getCacheKeys() to source the filters signature from new getActiveFilterSignatures(). Adds public getFindCacheKey() and getFindCacheField(), and internal serializeFindCacheQuery(), normalizeFindCacheQueryValue(), getFindCacheSchemaHash(), and getActiveFilterSignatures() for deterministic, stable find cache keys.
cachedFind() and purgeCachedFind() public APIs
src/Database/Database.php
Adds cachedFind(collection, queries, namespace, forPermission) wrapping find queries in cache-aside logic, bypassing cache for random-order queries, converting cached payloads back to document instances with permission revalidation and casting. Adds purgeCachedFind(collection, namespace) to purge all cached find entries.
CacheKeyTest: getFindCacheKey and getFindCacheField coverage
tests/unit/CacheKeyTest.php
Adds Document/Query imports and extends createDatabase() helper; adds eight tests asserting correct key format with :find suffix, namespace override, schema-based field derivation, sensitivity to roles/queries/cursors/ambient state/authorization context, and QueryException on invalid query types.
ListCacheTest: withCache() behaviors, cachedFind() lifecycle, and cache adapters
tests/unit/ListCacheTest.php
Adds withCache() tests asserting callback invocation on miss, caching of empty/null values, payload separation by hash, and non-caching of false. Adds comprehensive cachedFind() tests verifying cache persistence/purge, role/permission separation, document recasting, double-decode prevention, random-query bypass, permission revalidation, and nested document rehydration. Implements HashMemoryCache and JsonHashMemoryCache in-memory cache adapters with TTL-aware operations.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Database
  participant Cache as Cache Adapter
  participant Callback

  Client->>Database: cachedFind(collection, queries)
  Database->>Database: getFindCacheKey()
  Database->>Database: getFindCacheField()
  Database->>Cache: load(key, hash)
  alt Cache hit and value exists
    Cache-->>Database: cached document arrays
  else Cache miss or false
    Cache-->>Database: null
    Database->>Callback: invoke callback via withCache()
    Callback->>Database: find() results
  end
  Database->>Database: recreate documents + authorize
  Database-->>Client: documents
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • utopia-php/database#717: The main PR's cachedFind() explicitly bypasses caching for random-order queries, which depends on this PR's introduction of the TYPE_ORDER_RANDOM query type and SQL handling.
  • utopia-php/database#828: The main PR's deterministic find cache-field/key logic and active filter signature handling depends on per-filter signature metadata via getInstanceFilters(), which this PR enables.

Suggested reviewers

  • abnegate

Poem

🐇 Hop, hop through the cache we go,
A key, a hash, a miss—oh no!
The callback runs, the result is stored,
False values tossed, the rest adored.
With stable keys and schema hash,
My finds are cached in quite a flash! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Add list cache support" is vague and doesn't clearly specify what aspect of list caching is being implemented or what component is affected. Clarify the title to be more specific, e.g., "Add cached find support to Database layer" or "Implement cache-aside pattern for Database::find()" to better convey the main change.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cached-find

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a withCache() cache-aside helper and companion helpers (getQueryCacheKey, getQueryCacheField, purgeCachedQueries) that provide a clean, caller-owned list-caching API for the database layer, and refactors the shared getActiveFilterSignatures() logic into a private method.

  • withCache() handles Document serialization and restoration internally (single or list), applies type casting on cache hits, gates document-security re-checks against the cached payload, and treats false as an uncacheable sentinel and null hash as a cache-bypass signal.
  • getQueryCacheField() produces a stable, role-scoped hash by including auth roles, query serialization (with full cursor document payloads), relationship and filter state, and a collection schema hash; it returns null for non-PERMISSION_READ modes so callers can short-circuit directly to their callback.
  • The 1,034-line QueryCacheTest.php covers miss/hit, empty-result caching, null caching, hash separation, type casting, cursor collision, random-order bypass, document-security filtering, and invalid-payload refresh.

Confidence Score: 5/5

The change is additive and the new public methods are well-covered by a comprehensive test suite.

The new withCache, getQueryCacheKey, getQueryCacheField, and purgeCachedQueries methods handle all edge cases verified by the 1,034-line test file. The only findings are minor inconsistencies that do not affect correctness in expected usage patterns.

The withCache method in src/Database/Database.php is the most complex new addition; in particular the shouldRefreshCache purge path and schema-hash concatenation.

Important Files Changed

Filename Overview
src/Database/Database.php Adds withCache() cache-aside helper, getQueryCacheKey/Field/purgeCachedQueries helpers, and refactors getActiveFilterSignatures() into a private method. Minor inconsistency in json_encode error handling within schema hash computation.
tests/unit/QueryCacheTest.php New 1,034-line test file covering withCache semantics (miss/hit, null, empty array, false sentinel, per-hash separation, document security filtering, type casting, cursor collision, random-order bypass, invalid payload refresh, nested document rehydration).
tests/unit/CacheKeyTest.php Extends existing CacheKeyTest with 8 new test cases covering query cache key format, field hash distinctness, auth-context separation, null forPermission bypass, cursor payload inclusion, ambient state, and query type validation.

Reviews (47): Last reviewed commit: "Merge branch 'main' into feat/cached-fin..." | Re-trigger Greptile

Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Database/Database.php`:
- Around line 8625-8642: The cache hit path in the conditional block starting
with if ($cached !== null && $cached !== false && \is_array($cached)) only wraps
cached arrays into Document instances using createDocumentInstance, but does not
hydrate relationship fields that may have been serialized as arrays. This
results in different document shapes being returned compared to the cache miss
path which uses find() that returns fully processed documents. Either ensure the
cached hit path recursively hydrates relationship fields to match the shape
returned by find(), or add logic to bypass caching for finds that request
relationship-resolved data to avoid returning inconsistent types between cache
hits and misses.
- Around line 9563-9564: The cache key generation logic in the findCached method
is incorrectly dropping the serialized queries when a $key is provided. Change
the ternary expression that sets the queries field to always call
serializeFindCacheQueries($queries) instead of returning null when $key is set.
The caller key should add an additional cache dimension alongside the query
fingerprint, not replace it, so both the serialized queries and the key must be
included in the cache identifier to prevent different queries with the same key
from returning incorrect cached results.
- Around line 9572-9584: The getFindCacheSchemaHash method currently only
includes collection attributes and indexes in the hash calculation, but the
find() authorization check also validates collection permissions and
documentSecurity settings. When collection authorization policies change, cached
results remain valid incorrectly because the schema hash doesn't reflect these
changes. Modify the getFindCacheSchemaHash method to include the collection's
permissions and documentSecurity flag in the hash payload alongside the existing
attributes and indexes. Additionally, replace the MD5 hash algorithm with a
stronger digest function such as SHA256 to address the static analysis flag
regarding weak hashing.

In `@tests/unit/FindCacheTest.php`:
- Around line 140-144: The `HashMemoryCache::save` method incorrectly prevents
caching of empty arrays because the condition `empty($data)` returns true for
empty arrays and rejects them. Modify the validation logic to only reject empty
strings while allowing empty arrays to be cached as valid results. Change the
condition to specifically check if `$data` is an empty string or if `$key` is
empty, rather than using `empty()` which treats empty arrays the same as empty
strings.
- Around line 83-99: The test testFindCachedTriggersFindEventOnCacheHit does not
actually verify a cache hit because both cache hits and cache misses emit
EVENT_DOCUMENT_FIND. To fix this, add data mutation between the two findCached
calls to force a true cache hit scenario. After the first findCached call on
line 92, modify the seeded project data in the database, then verify that the
second findCached call on line 93 returns the stale cached result from the first
call rather than the modified data, which would definitively prove the second
call was a cache hit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0eed82b-9eca-489f-a0fd-19748bc2694c

📥 Commits

Reviewing files that changed from the base of the PR and between cfba533 and 1d615f5.

📒 Files selected for processing (4)
  • src/Database/Database.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
  • tests/unit/CacheKeyTest.php
  • tests/unit/FindCacheTest.php

Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread tests/unit/FindCacheTest.php Outdated
Comment thread tests/unit/FindCacheTest.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Database/Database.php (2)

8611-8617: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate queries before serializing the cache key.

findCached() reaches serializeQueriesForFindCache() before find() runs checkQueryTypes(), so malformed query arrays can fail outside the normal QueryException path on cached calls.

Proposed fix
         if ($collectionDocument->isEmpty()) {
             throw new NotFoundException('Collection not found');
         }
 
+        $this->checkQueryTypes($queries);
+
         [$findKey, $findField] = $this->getFindCacheKeys($collectionDocument->getId(), $queries, $key, $forPermission, $collectionDocument);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Database.php` around lines 8611 - 8617, In the findCached()
method, validate the queries before they are serialized for the cache key. Call
checkQueryTypes() on the queries parameter before invoking getFindCacheKeys(),
which calls serializeQueriesForFindCache(). This ensures malformed query arrays
are caught early with proper QueryException handling rather than failing during
serialization.

8617-8639: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid serving document-security caches after permission revocation.

The cache hit path returns serialized documents without re-reading current document permissions. If a cached document later loses $permissions, the same role can still receive it until the find TTL expires. Until permission-changing writes purge affected find variants, bypass caching for documentSecurity collections.

Conservative fix
         if ($collectionDocument->isEmpty()) {
             throw new NotFoundException('Collection not found');
         }
 
+        if ($collectionDocument->getAttribute('documentSecurity', false)) {
+            return $this->find($collectionDocument->getId(), $queries, $forPermission);
+        }
+
         [$findKey, $findField] = $this->getFindCacheKeys($collectionDocument->getId(), $queries, $key, $forPermission, $collectionDocument);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Database/Database.php` around lines 8617 - 8639, The current caching
logic in the find method returns cached documents without re-validating
permissions, which creates a security vulnerability for documentSecurity
collections where permissions may have been revoked. Add a check using the
$collectionDocument to determine if the collection is a documentSecurity
collection, and if so, skip the entire cache loading and cache return logic (the
try-catch block for $this->cache->load() and the conditional check for $cached
!== null) to force a fresh permission check on every query. This ensures that
permission changes are respected immediately rather than serving stale cached
results until the TTL expires.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Database/Database.php`:
- Around line 8611-8617: In the findCached() method, validate the queries before
they are serialized for the cache key. Call checkQueryTypes() on the queries
parameter before invoking getFindCacheKeys(), which calls
serializeQueriesForFindCache(). This ensures malformed query arrays are caught
early with proper QueryException handling rather than failing during
serialization.
- Around line 8617-8639: The current caching logic in the find method returns
cached documents without re-validating permissions, which creates a security
vulnerability for documentSecurity collections where permissions may have been
revoked. Add a check using the $collectionDocument to determine if the
collection is a documentSecurity collection, and if so, skip the entire cache
loading and cache return logic (the try-catch block for $this->cache->load() and
the conditional check for $cached !== null) to force a fresh permission check on
every query. This ensures that permission changes are respected immediately
rather than serving stale cached results until the TTL expires.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29c2ae6a-250e-4626-8ae6-59effc86c769

📥 Commits

Reviewing files that changed from the base of the PR and between 6fad393 and 5275b33.

📒 Files selected for processing (5)
  • src/Database/Database.php
  • tests/e2e/Adapter/Base.php
  • tests/e2e/Adapter/RedisTest.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
  • tests/unit/FindCacheTest.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/e2e/Adapter/Base.php
  • tests/e2e/Adapter/RedisTest.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Database/Database.php`:
- Line 8659: The condition on line 8659 checks `if ($touchOnHit &&
!$hasExpiredDocuments)`, but the `!$hasExpiredDocuments` part is redundant
because the code already guarantees this value through an early return statement
in the preceding lines (8646-8657). Remove the `&& !$hasExpiredDocuments` clause
from the condition, leaving only `if ($touchOnHit)`, to eliminate the
always-true condition that is causing the PHPStan error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 647e586e-bf40-4233-b042-568a6b0b5436

📥 Commits

Reviewing files that changed from the base of the PR and between 5275b33 and 7cd9d19.

📒 Files selected for processing (4)
  • src/Database/Database.php
  • tests/e2e/Adapter/Scopes/DocumentTests.php
  • tests/unit/CacheKeyTest.php
  • tests/unit/FindCacheTest.php
💤 Files with no reviewable changes (1)
  • tests/unit/CacheKeyTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/Adapter/Scopes/DocumentTests.php
  • tests/unit/FindCacheTest.php

Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated
Comment thread src/Database/Database.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Database/Database.php`:
- Around line 8608-8615: The cachedFind method caches document payloads without
filtering TTL-expired documents, causing expired documents to remain visible
until the cache entry expires. Filter out TTL-expired documents in two places
within cachedFind: first, in the callback function shown (after the find call
returns documents but before they are array-mapped and stored in cache), and
second, in the cache hit scenario (around lines 8635-8647, before returning the
cached payload). Use the isTtlExpired method to identify and exclude expired
documents from both the cache miss result being saved and the cache hit payload
being returned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc6fd113-7a02-40de-8fe9-c1c00576e805

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac837b and ad49dba.

📒 Files selected for processing (3)
  • src/Database/Database.php
  • tests/unit/CacheKeyTest.php
  • tests/unit/ListCacheTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/CacheKeyTest.php

Comment thread src/Database/Database.php Outdated
@utopia-php utopia-php deleted a comment from coderabbitai Bot Jun 22, 2026
Comment thread src/Database/Database.php
@premtsd-code premtsd-code changed the title Add cached find support Add list cache support Jun 23, 2026
Comment thread src/Database/Database.php
Comment thread src/Database/Database.php Outdated
* @param string|null $namespace
* @return bool
*/
public function purgeQueryCache(string $collection, ?string $namespace = null): bool

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call this purgeCachedQueries for consistency with the other purge methods

@abnegate abnegate merged commit c71349d into main Jun 23, 2026
22 checks passed
@abnegate abnegate deleted the feat/cached-find branch June 23, 2026 13:36
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