yetidevworks/yetisearch
YetiSearch is a Laravel package that brings fast, flexible full-text search to your Eloquent models. Index records, run relevance-ranked queries, apply filters, and paginate results through a simple, expressive API—no external search service required.
YetiSearch::stemmingFor($index) makes stemming a first-class, queryable property rather than a config detail. This unlocks user-facing decisions that previously required reading config files: labeling results ("Searching in French"), routing a query to a locale-specific index, warning users when a language has no stemmer (returned lower-cased, as given), or suppressing "stemmed match" affordances when null is returned. It also cleanly degrades: storages that don't implement ProvidesStemming return null rather than throwing, so multi-storage deployments don't need special-casing.rebuildFts() path tokenized raw JSON, so field names (title, content, route, …) became searchable words in every document. That is a user-visible correctness bug — searching "content" returns the entire corpus. 2.6.1 self-heals on first write, which means we can ship the upgrade without a coordinated maintenance window or a bespoke repair script.{id}#chunk{N} rows searchable, so a word removed from a document still surfaced it. Any product with editable documents, versioned content, or chunked ingestion (transcripts, PDFs, long articles) was silently returning stale hits. 2.6.1 removes stale chunks in the same transaction as the write — a failed write keeps them, so there is no window of inconsistency.insert(), insertBatch(), update(), delete(), and deleteByIdPrefix() now join an open caller transaction via savepoints instead of nesting their own. This is the enabler for "create the record and index it" as a single atomic unit — a real architectural decision (denormalized write-through indexing) that previously forced either no transactions or manual orchestration. Failure isolation is now correct too: a failed index write rolls back only the index write, not the caller's surrounding work.enable_spatial => false could not delete documents at all in 2.5.6/2.6.0 — delete() and deleteByIdPrefix() threw no such table: {index}_spatial (or {index}_id_map for own-content indexes). If we disabled spatial search to save space, document deletion was simply broken. This is a "must upgrade to keep the feature flag viable" item, and it also covers hand-dropped spatial tables, which matters for on-call recovery.Adopt (or upgrade to 2.6.1) when:
enable_spatial => false, or you support self-managed deployments where tables may be dropped manually — 2.6.1 is effectively required.Look elsewhere when:
stemmingFor() is introspection, not an analyzer plugin system.deleteByIdPrefix() tooling against the same database after 2.5.x. That operation re-introduces the field-name corruption, and the key cannot detect it. Audit any operational runbooks before assuming the upgrade fixes existing state.yetisearch_pro_fts_built key, untouched by this release, so verify Pro compatibility independently rather than assuming 2.6.1 covers it.rebuildFts() once after prefix deletes on external-content indexes — 2.6.1 supersedes that advice, so any documentation, runbook, or migration step referencing it should be considered stale.Adoption criteria that should gate the rollout:
There is already an active transaction" to "join via savepoint," which is strictly better but is a behavior change worth noting in release comms.{index}_meta table exists on any index you expect to heal. An index with no meta table "does not say how it is stored" and is left to createIndex() — those will not self-heal.One-liner: A hardening release. 2.6.1 makes our embedded search index self-repairing, safe to write inside application transactions, and correct when documents are edited or re-ingested — with no new infrastructure and no manual repair step.
For executives: This is a risk-reduction release on a component we depend on. It closes three user-visible correctness bugs — field names leaking into search results, deleted text still matching documents, and document deletion being entirely broken when spatial search is disabled — and it removes a maintenance window from the upgrade path because indexes repair themselves on first write. It also unlocks a small but visible product win: search can now tell the UI which language an index is stemmed in, and degrade gracefully when it isn't. No new services, no new vendors, no cost line change.
For engineering: Upgrade straight to 2.6.1, not 2.6.0. Highlights: (1) External-content indexes inherit a self-healing rebuild driven by an fts_built_by key in {index}_meta, run under the write lock in a transaction, re-checked after lock acquisition, rolled back cleanly on failure, with no-op behavior when the key is already set — and no rebuild on read paths. (2) Stale chunks are removed on rewrite via an indexed id range (id >= '{id}#chunk' AND id < '{id}#chunl'), not LIKE, so it is safe against ids containing #chunk, _, and %, and it covers all three storage layouts. (3) Batch calls dedupe repeated ids, keeping the last group. (4) insert/insertBatch/update/delete/deleteByIdPrefix join an open caller transaction with a savepoint instead of throwing and rolling back the caller's work. (5) Delete paths share one routine that skips missing tables, fixing spatial-disabled and own-content indexes with no {index}_id_map.
Caveats to state plainly: The self-heal only fires on write; read-only deployments keep any pre-existing corruption. Re-running 2.5.x deleteByIdPrefix() tooling against the same database reintroduces the corruption and the key cannot detect it — audit runbooks. The 2.6.0 advice to run rebuildFts() once after prefix deletes is superseded. YetiSearch Pro uses a separate key and needs independent verification. And the underlying constraint is unchanged: this is single-node SQLite search, not a horizontally scalable search tier — if scale or relevance tuning enters the roadmap, the build-vs-buy question reopens regardless of how solid 2.6.1 is.
How can I help you explore Laravel packages today?