Runtime: Beignet requires Node.js 22.12 or newer. Bun is optional.
Meilisearch-backed SearchPort provider for Beignet applications.
The provider installs ctx.ports.search and adapts Beignet's provider-neutral
search API to Meilisearch indexes, documents, filters, sorting, facets, and
offset pagination.
createMeilisearchSearchProvider(...) returns the stable
MeilisearchSearchProvider type. MeilisearchConfig describes its validated
config; the Zod schema remains internal.
bun add @beignet/provider-search-meilisearch @beignet/core
// server/providers.ts
import { createMeilisearchSearchProvider } from "@beignet/provider-search-meilisearch";
export const providers = [
createMeilisearchSearchProvider({
indexPrefix: "my_app",
}),
];
Set MEILISEARCH_HOST for the default env-backed provider. Set
MEILISEARCH_API_KEY when your Meilisearch instance requires one. Environment
variables:
MEILISEARCH_HOST (required unless you pass host or client)MEILISEARCH_API_KEYMEILISEARCH_INDEX_PREFIXMEILISEARCH_TIMEOUT_MSMEILISEARCH_TIMEOUT_MS and the timeoutMs factory option must be integers
from 1 through 2,147,483,647 milliseconds, the JavaScript timer range.
beignet doctor --strict checks that installed Meilisearch providers are
registered in server/providers.ts and that MEILISEARCH_HOST is present in
app env examples or config when the env-backed provider is used.
You can also pass an existing client or direct connection options:
createMeilisearchSearchProvider({
host: "https://search.example.com",
apiKey: process.env.MEILISEARCH_API_KEY,
});
import { defineSearchIndex } from "@beignet/core/search";
type IssueSearchDocument = {
id: string;
tenantId: string;
key: string;
title: string;
status: "open" | "resolved";
createdAt: string;
};
export const issueSearchIndex = defineSearchIndex<IssueSearchDocument>(
"issues",
{
searchableAttributes: ["key", "title"],
filterableAttributes: ["tenantId", "status"],
sortableAttributes: ["createdAt"],
},
);
await ctx.ports.search.indexDocuments(issueSearchIndex, {
id: issue.id,
tenantId: issue.tenantId,
key: issue.key,
title: issue.title,
status: issue.status,
createdAt: issue.createdAt,
});
const results = await ctx.ports.search.search(issueSearchIndex, {
query: "billing",
filters: { tenantId },
sort: ["createdAt:desc"],
limit: 20,
});
The Meilisearch adapter validates query fields before sending a request:
filters and facets must use fields declared in filterableAttributes,
sort must use fields declared in sortableAttributes, and field names must be
simple identifiers or dotted paths. Map raw request input to app-owned
allow-listed field names before passing it to SearchPort.
createMeilisearchSearch(options)Creates a SearchPort from a Meilisearch-compatible client. Use this for tests
or custom provider composition.
createMeilisearchClient(options)Creates the small fetch-backed client used by the provider. It sends JSON
requests to Meilisearch and throws MeilisearchHttpError for non-2xx responses.
createMeilisearchSearchProvider(options)Creates a Beignet lifecycle provider that contributes:
ctx.ports.search, the standard Beignet SearchPortctx.ports.meilisearch, an escape hatch with the raw client, index prefix,
and checkHealth() helpercreateMeilisearchSearchProvider()Ready-to-register provider using MEILISEARCH_* environment variables.
When @beignet/devtools or another provider instrumentation sink is installed
before this provider, indexing, deleting, search, and settings operations appear
under the Search watcher. Events include the index, operation, document count,
query length, duration, and success or failure status. Query text and document
bodies are not recorded.
The env-backed provider throws during startup when MEILISEARCH_HOST is
missing. Meilisearch non-2xx responses throw MeilisearchHttpError, including
the request path, response status, and parsed or raw response body when
available. Plain-text or HTML proxy responses are preserved on the error's
body instead of surfacing as an unrelated JSON parse failure. Indexing
operations in Meilisearch are asynchronous: a successful write means the task
was accepted, not that the document is immediately searchable.
Use ctx.ports.meilisearch.checkHealth() from app-owned readiness endpoints to
call Meilisearch's /health endpoint without indexing or searching documents.
Use a fake or in-memory SearchPort in use-case tests so tests can assert
search intent without depending on Meilisearch task timing. Use the direct
createMeilisearchSearch(...) factory with a test client for provider adapter
tests.
Treat Meilisearch as a read model. Feed it from committed app state through outbox, listeners, jobs, or backfill tasks, and make stale-search behavior acceptable in the UI and API.
Search indexes are read models. Keep transactional truth in your database and use search for discovery, filtering, ranking, and faceted browsing. For durable indexing, pair this port with Beignet outbox/listener workflows or operational backfill tasks.