Skip to content

Make exists() a read-only check - #39

Merged
ryanmitchell merged 2 commits into
statamic-rad-pack:mainfrom
edalzell:fix/read-only-exists
Oct 8, 2026
Merged

ryanmitchell merged 2 commits into
statamic-rad-pack:mainfrom
edalzell:fix/read-only-exists

Conversation

@edalzell

@edalzell edalzell commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

The problem

Index::exists() called getOrCreateIndex(), so it created the collection it was asked to check and then reported true. Core's ensureExists() and Search::updateWithinIndexes() both rely on exists() to notice a missing index and call update(). Since it was never false, a collection that had gone missing (a Typesense restart with an empty data dir, say) was quietly recreated empty and treated as ready. Searches returned nothing, and nothing errored or logged.

Search::index('default')->ensureExists()->search('foo'); // creates an empty collection, returns no hits, never rebuilds

The fix

Follows the approach Duncan laid out in #36: exists() is a read-only check, matching the Comb and Algolia drivers in core, and only update() and insertDocuments() create the collection.

  • exists() asks the client's memoised Collection::exists() and nothing else. On v6 the rebuild ensureExists() then triggers is queued per chunk by core's insertMultiple(), and update() creates the collection up front, so only the first save or search after a wipe starts a rebuild.
  • delete() and deleteIndex() no-op when the collection is missing instead of creating it first.
  • searchUsingApi() returns empty results for a missing collection rather than creating it, same as a search against an index that's still being built.
  • getTypesenseSchemaFields() and getCount() also went through getOrCreateIndex(). Query calls the former on every search, so these are read-only too or the collection would be recreated through the back door.

A private collection() accessor returns the client's cached Collection for the index name without any HTTP, and the read paths use it in place of getOrCreateIndex().

Tests

tests/Unit/ExistsTest.php mocks ApiCall so each test asserts on the requests made, with no Typesense server needed. Six of the eight fail on main because POST /collections fires where it shouldn't. The mocked-client helper the existing tests duplicated has moved to TestCase.

Closes #36

@ryanmitchell
ryanmitchell merged commit 494c976 into statamic-rad-pack:main Oct 8, 2026
7 checks passed
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.

exists() creates the collection it is asked to check, so ensureExists() never rebuilds an empty index

2 participants