Ecosystem ↗

Meilisearch adapter

Use bounded tenant-scoped Meilisearch queries and explicit asynchronous task receipts.

↗ View Markdown

Register the requested package#

The package is named @zeruxjs/search-mille. It uses official SDK 0.62.0, requires Node 22.12+, and targets Meilisearch 1.x without claiming every feature or version has live qualification. Imports open no connections.

ts
import {createMilleSearchFactory} from '@zeruxjs/search-mille';
import {registerSearchService} from '@zeruxjs/search/adapters';

const registration = registerSearchService({
  factories: {mille: createMilleSearchFactory({
    index: 'catalog', taskKey: hostTaskKey, taskScope: 'production-search',
    fields: {category: 'string'}, filterable: ['category'],
  })},
  connections: [{name:'catalog',backend:'mille',endpoint:'https://search.example.com',
    allowedEndpoints:['https://search.example.com'],
    credentials:{apiKey:process.env.MEILI_API_KEY!}, requirePersistent:true}],
  authorize(context) {
    const tenant = verifiedTenant(context);
    return tenant ? {tenant} : null;
  },
});

hostTaskKey is a stable private 32-byte random host secret; verifiedTenant is host authentication/authorization, not a supplied library helper. Choose a distinct stable taskScope for each independent engine. Register with framework search:{enabled:true,registration} or start/drain standalone. Master/admin keys never belong in browser configuration. No automatic fallback is installed.

Treat task acceptance separately#

Index creation, settings changes, document upserts and deletes enqueue native tasks. An accepted receipt does not mean the data is searchable. Direct adapters expose createIndex/configureIndex plus taskStatus/waitForTask. Wait for setup tasks to succeed before ingestion, and inspect terminal state: failed/canceled are terminal outcomes too. Polling has one shared operation slot/deadline, a 10–1000 ms interval and at most 100 polls; it does not create unbounded background polling after the caller stops waiting. Task references are tenant/index/scope-bound HMACs; raw numeric UIDs and foreign references reject before requests.

Keep receipts in application-owned storage if status must survive request loss. Stable key/scope allow another authorized instance to read them. Changing keys or native task retention may make old status unavailable. Cancellation stops waiting, not a queued engine write. Ambiguous acceptance is never automatically replayed; this is not an exactly-once or transactional outbox implementation.

Restrict fields and engine access#

Explicit searchable/filterable/sortable lists are checked against live settings. Every query independently ANDs the verified tenant digest; composite engine IDs bind writes/deletes to tenant and logical ID. Returned internal fields are verified then removed. Shared engine statistics remain shared; dedicated indexes provide stronger isolation when required.

Filters are structured values, not a saved expression language. String filters require caseSensitive:false because Meilisearch equality/IN is natively case-insensitive. Unknown fields, raw filters, nulls, unsupported operators and unsafe integer values reject. Counts are native estimates with partial coverage; typo tolerance and ranking are not Elasticsearch or local-index parity.

Own cancellation and resources#

Always await close. It aborts cooperative request/wait scopes and drains actual settlement; injected SDK clients, Fetch transports and global pools remain borrowed. Owned requests enforce HTTPS/allowlists, redirects, deadlines and preparse response byte limits through the pinned SDK's public transport hook. Backend/task messages and document bodies are excluded from public diagnostics. Requalify that hook on SDK upgrades because it is upstream-deprecated.

Fixtures exercise the official SDK with an injected transport and bounded task states. Real acceptance requires an isolated HTTPS ZX_TEST_MEILI_URL, scoped API key and trusted CA setup; without those, the dedicated disposable-index test skips. No live browser, engine outage/restart or all-version qualification is inferred.

Public entry points

Manifest reference for @zeruxjs/search-mille@0.0.4. Each target describes a published entry, not automatic framework registration.

ImportTarget
@zeruxjs/search-mille
{
  "types": "./dist/index.d.ts",
  "import": "./dist/index.js"
}
Runtime requirements and dependencies
{
  "engines": {
    "node": ">=22.12.0"
  },
  "dependencies": {
    "@zeruxjs/search": "0.0.4",
    "meilisearch": "0.62.0"
  },
  "peerDependencies": {},
  "bin": {}
}
Full package README

@zeruxjs/search-mille#

Explicit server-side Meilisearch adapter for @zeruxjs/search/adapters. The requested npm/path spelling is search-mille. This package does not change the standalone local index or install a global search client.

Setup and supported surface#

The runtime dependency is official meilisearch 0.62.0. This adapter requires Node 22.12+, probes the engine's major version, and targets Meilisearch 1.x. Missing settings/features fail closed; no all-version/native qualification is claimed. See the official SDK compatibility and APIs. No connection, index mutation or task starts on import.

The public-import example registers an explicit createMilleSearchFactory with the shared manager. Configure a private named connection with HTTPS endpoint, exact allowedEndpoints, and credentials:{apiKey}; use requirePersistent:true if process-memory fallback would violate requirements. The shared host authorizer must verify the session, tenant and operation permission. Framework registration uses the existing search:{enabled:true,registration} service; there is no package discovery.

taskKey is a stable host-owned 32-byte random secret, and taskScope is a stable, unique engine/deployment identifier. Keep both in private server configuration and use a different scope for independent task queues. Task references bind scope, index, native UID and verified tenant using HMAC. Retain the same key/scope across instances/restarts when status must remain readable. Changing either invalidates old references; no automatic key rotation or task rediscovery occurs. Persist acceptance receipts in application-owned storage when needed; this adapter is not a durable business outbox.

Explicit index/settings tasks#

Direct createMilleSearchAdapter(options) exposes privileged setup operations:

  1. createIndex(call) enqueues creation with primary key _zxId.
  2. waitForTask(receipt.taskId,call) must return succeeded before continuing.
  3. configureIndex(call) enqueues supported settings; wait for that task too.

These methods are trusted server APIs, not public HTTP handlers. Hosts must separately authorize provisioning; use a privileged setup key and narrower normal-operation keys. Runtime keys need the search/index-info/settings-read, document write/delete and task-read permissions used by their enabled operations; a native search-only key cannot run this full adapter. Existing indexes are not silently overwritten at startup. Before each data operation the adapter verifies the primary key and supported settings against its configuration. Settings changes require an explicit host migration; concurrent administrative changes are outside this check's atomicity. Disable index auto-creation privileges for ordinary writers after provisioning.

fields declares at most 32 scalar string/number/boolean fields in addition to required id and text. Strings and safe finite numbers are validated before sending; nested objects, arrays, nulls, accessors, unknown fields and unsafe integers reject. Searchable fields are explicit strings (default text); filterable fields default to none; sortable fields default to id and must retain it. Internal tenant/ID fields cannot be configured by callers. Displayed fields are fixed to declared fields plus internal verification metadata; distinct is disabled and maxTotalHits is 10000. Searchable order is preserved. Meilisearch is schemaless: external writers/administrators must preserve this homogeneous data contract too.

Accepted versus completed#

index is a full-document upsert, delete deletes tenant-bound IDs, and neutral partial update rejects. Both return state:'accepted' with an opaque taskId, not searchable completion. Batches contain 1–100 distinct logical IDs and at most 1 MiB of encoded documents/IDs; each document caps at 128 KiB. Duplicate logical IDs within a batch reject before enqueue. Deletes of missing documents follow native task semantics.

taskStatus(taskId,call) returns enqueued/processing/succeeded/failed/canceled. waitForTask(taskId,call,{intervalMs,maxPolls}) polls with 10–1000 ms intervals, at most 100 polls and the same bounded operation deadline. It returns a terminal status including failed/canceled; callers must inspect state. Failures expose only allowlisted error codes and optional numeric received/indexed counts, never native messages, bodies or credentials. Unknown error codes become task_failed. Task UID/index responses are verified. Foreign/forged task references reject before HTTP. Native task expiration/cleanup causes a backend error, never invented success. See official asynchronous task management.

Canceling a wait/request or closing the adapter does not cancel an already queued engine task. A lost enqueue acknowledgement has an unknown outcome; there is no automatic retry, exactly-once promise, task cancellation, redrive or bulk rollback. A succeeded engine task confirms processing, not application-level transaction atomicity or the host's backup/durability policy.

Tenant/filter/ranking semantics#

Direct calls require already verified {tenant,signal,deadline} context. Remote callers must go through the host-authenticated manager/HTTP layer. Every search ANDs a server-generated tenant digest filter outside user expressions; writes and deletes hash tenant and logical ID into the engine primary key. Returned tenant and composite IDs are verified and removed before returning documents. Hashes namespace records; they do not authenticate a caller or hide shared engine term statistics. Use dedicated indexes when stronger statistical isolation is required.

Only declared filterable attributes and eq/neq/in/exists/numeric gte/lte translate. There is no raw filter string/DSL option. Values are validated and quoted as literal values; an empty IN yields a server-generated contradiction. Native string filters are case-insensitive, so string comparisons/IN require explicit caseSensitive:false; omitted/true reject rather than pretending local exact matching. Null, contains/prefix, control characters and unknown attributes reject. See official native filter semantics.

Text uses native q; typo tolerance, tokenization and ranking remain Meilisearch semantics, with no Elasticsearch/local-score parity. No numeric relevance score is invented. Pagination is offset based, pages 1–100 and offset+limit <=10000. Estimated hit counts always carry coverage:'partial'; they are not exact totals. Sort accepts one configured field and an ID tie key; native ordering semantics remain authoritative. Cursors, exhaustive scans, vectors and ranking-rule editing are deliberately unavailable.

Transport, credentials and cleanup#

Owned SDK instances require verified HTTPS root endpoints, exact allowlists and private nonempty API keys. Redirects reject; no insecure TLS flag or arbitrary SDK options bag is exposed. Normal OS/Node CA trust applies; a trusted injected fetch may own an explicit CA/dispatcher policy. Egress/DNS controls remain host-owned. Master/admin keys must never be sent to browsers. Prefer narrowly scoped server keys. Engine search-only keys alone are not tenant authorization; native tenant-token search rules/expiry are a separate host integration. This package does not issue browser tokens or expose unrestricted native task APIs. See Meilisearch security boundaries.

The pinned SDK's public httpRequest surface propagates per-call AbortSignal. Its supported but upstream-deprecated httpClient hook is deliberately used for owned instances to reject redirects and cap response bytes at 1 MiB before JSON parsing. No private SDK fields or global Fetch patches are used. Future SDK upgrades require requalification of that hook. Request bodies and task failures remain bounded/redacted; the adapter adds no retry loop except explicit status polling. Native/injected transport causes stay private.

client injection borrows the official client's HTTP surface and cannot be mixed with endpoint/key/fetch options. The host must bound that client's response parsing, TLS/egress behavior and honor extraRequestInit.signal. close() rejects new calls, aborts cooperative work, waits for actual settlement and zeros its copied task key. It does not close borrowed clients or the global Fetch pool. The official SDK has no client-close API: the adapter owns request scopes, not a dedicated socket pool. Host-injected fetch/dispatchers remain borrowed. Deadlines default to 5000 ms (1–60000), pending operations to 32 (1–128). A wait occupies one slot including poll delays. Closing waits for that operation’s actual settlement before zeroing this wrapper’s key copy; it never zeroes the host’s original key buffer. Noncooperative callbacks can delay direct calls/close; the shared manager separately bounds caller observation while retaining native ownership.

Checks and live prerequisites#

Build common search, then this package, and run node --experimental-strip-types --test packages/@zeruxjs/search-mille/test/*.test.ts. Fixtures cover accepted/completed/failed tasks, token tampering, tenant/query isolation, invalid settings/attributes, unavailable backends, cancellation/drain, and the actual official SDK with injected Fetch byte/error/cleanup checks. These are not live engine or browser acceptance.

Opt-in live testing requires an isolated ZX_TEST_MEILI_URL HTTPS engine and ZX_TEST_MEILI_API_KEY scoped to disposable zx058-* indexes/tasks. Normal Node CA trust must validate its certificate. The test creates/configures a random index, waits for processing, verifies search/tenant/delete behavior and awaits index deletion in cleanup. Without those settings it reports a skip. Live failure injection, crash recovery and all engine-version qualification remain outstanding.

The borrowed client.httpRequest contract requires only the official get, post, put and patch method signatures used by this adapter. A normal official SDK client remains compatible. Results at that generic JSON boundary are independently validated by the adapter; method typings do not establish tenant authorization or trusted response contents.

Implementation sources

Implementation files for the APIs described on this page.

ZeruxJS · Open source · MIT OR Apache-2.0

Make yourself at home.

Every shortcut has a visible control too. Reading and navigation work without JavaScript.

Ctrl / ⌘ K
Search all documentation
/
Search, outside editable fields
?
This help, outside editable fields
↑ / ↓
Move through search results
Esc
Close a dialog or version menu
Progress, privacy, offline reading and controls →

Reset this learning record?

This removes course answers, exercise confirmations, bookmarks and recent pages for this docs version in this browser. Export a copy first if you want to keep it.

Search documentation

Type at least two characters.