Daravelv1.25.0
ENID GitHub

Search (daravel_scout)

daravel_scout is Laravel Scout's database engine: the search runs on the model's own table, with no index to keep in sync. On PostgreSQL with pgvector, or on MariaDB 11.7 or newer, it also searches by meaning.

dart pub add daravel_scout

Searching#

Mix Searchable into a model and name the columns to search. A fresh model has no attributes, so toSearchableArray must list them, as in Scout.

import 'package:daravel_scout/daravel_scout.dart';

final class Post extends Model with Searchable {
  // ... definition and accessors

  @override
  Map<String, Object?> toSearchableArray() => {'title': null, 'body': null};

  @override
  List<String> get searchUsingFullText => const ['body'];

  @override
  List<String> get searchUsingPrefix => const ['title'];
}

final posts = await Scout.search(Post.definition, 'laravel')
    .where('published', true)
    .take(10)
    .get();

Each column matches with LIKE (ILIKE on PostgreSQL), query% for a prefix column. A numeric query also matches the key. Results come newest first; with full text columns on PostgreSQL, by relevance. whereIn, whereNotIn, orderBy, first, keys, paginate, and constrain for more of the database query work as in Scout.

searchUsingFullText is Scout's #[SearchUsingFullText]: PostgreSQL tsvector and MySQL or MariaDB MATCH. fullTextOptions sets the language and the mode. SQLite has no full text search, so a full text column throws there.

Search by meaning#

Set the vector column and what to embed, and give Scout a function that turns texts into vectors. Scout uses laravel/ai for that; here it is yours to plug in.

@override
String? get searchableEmbeddingColumn => 'embedding';

@override
Object? toSearchableEmbedding() => body;   // or a List<num> you already have

Scout.embedder = (texts) => myEmbeddingService.embed(texts);
await Scout.makeSearchable(posts);         // writes the vectors

final similar = await Scout.search(Post.definition, 'deploying apps')
    .semantic(0.7)
    .get();

final both = await Scout.search(Post.definition, 'deploying apps')
    .hybrid(textWeight: 1, semanticWeight: 2)
    .get();

semantic keeps rows at least that alike (0.6 by default), nearest first. hybrid fuses the text and the meaning ranks with reciprocal rank fusion, as Scout does. Neither combines with orderBy. The column comes from table.vector (Database); on SQLite these throw.

Laravel equivalents#

More of Laravel that this works with, and the API to look up on pub.dev.

Laravel Daravel API Note
use Searchable; public function toSearchableArray() with Searchable; toSearchableArray() Searchable.toSearchableArray the database engine only
#[SearchUsingFullText(['body'])] List<String> get searchUsingFullText => ['body'] Searchable.searchUsingFullText