dmlab / module-typesense-core
Typesense access layer for Magento 2: HTTP client, collection lifecycle, aliases, document writes, synonym sets, schema reconciler, diagnostic CLI. No catalog or search-subsystem knowledge.
Package info
github.com/dmlab-work/module-typesense-core
Type:magento2-module
pkg:composer/dmlab/module-typesense-core
Requires
- php: ~8.2.0||~8.3.0||~8.4.0||~8.5.0
- ext-curl: *
- laminas/laminas-http: ^2.15
- magento/framework: ^103.0.7
- psr/log: ^2.0||^3.0
- symfony/console: ^6.4||^7.4
Requires (Dev)
- phpunit/phpunit: ^11.5||^12.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Keeps a Typesense-powered Magento store answering when a search node drops by failing over to the next node.
Swaps a rebuilt index in with no downtime, and gives you three CLI checks: node health, which index is live, and
leftover collections to clean up. typesense-search and typesense-indexer install it for you, so you rarely
require it directly.
Install
composer require dmlab/module-typesense-core bin/magento module:enable DmLab_TypesenseCore bin/magento setup:upgrade
The usual path is composer require dmlab/module-typesense-search, which pulls in core and the indexer.
Requirements
Magento 2.4.7+ · PHP 8.2–8.5 · ext-curl · Typesense ≥ 30.0 (synonym sets API), verified on 30.2.
CLI
bin/magento dmlab:typesense:ping— checks every node and the API key; exits non-zero if a node is down or unhealthy, the key is rejected, or no key is configured.bin/magento dmlab:typesense:alias:status— shows which collection each index name serves right now, and counts leftover copiescollections:prunewould clean up.bin/magento dmlab:typesense:collections:prune [--dry-run] [--min-age=86400]— drops old index copies nothing serves anymore; copies younger than--min-ageseconds are kept, as a rebuild may still be waiting to go live.
Pairs with typesense-indexer, typesense-search and typesense-instant-search.
Integration tests
Test/Integration/ runs against a real Typesense and is skipped unless TYPESENSE_TEST_URL and
TYPESENSE_TEST_API_KEY are set:
docker exec -w /var/www/html/m2-modules/module-typesense-core -e MAGENTO_ROOT=/var/www/html \ -e TYPESENSE_TEST_URL=http://typesense:8108 -e TYPESENSE_TEST_API_KEY=<key> \ magento-app php /var/www/html/vendor/bin/phpunit -c Test/Integration/phpunit.xml
Architecture
L0 of the DMLab Typesense suite: pure transport with no catalog knowledge, and it reads no configuration of its own.
What it provides
- HTTP client — node failover, typed errors;
magento/frameworkonly, no SDK. - Health check — cached and non-throwing, over
GET /health;verifyConnection()returns an uncachedConnectionStatus(ok/unreachable/unauthorized) and checks the key againstGET /debug, since/healthaccepts any key. Pass aConnectionSettingsInterfaceto check unsaved settings. - Collections — create / get / exists / drop / PATCH /
getList(), plus zero-downtime aliasswap()(per-alias lock; refuses a missing target). - Documents — upsert, delete, delete-by-filter, chunked import; an unencodable document is a per-document failure, not an aborted batch.
- Synonyms —
SynonymSetManagerupsert / delete / exists over Typesense synonym sets. - Schema reconciler — decides in-place PATCH vs rebuild; holds no documents itself.
- CLI details —
pingreportsOK/DOWN/UNHEALTHY/KEY REJECTEDper node; without a key it still probes/healthand exits non-zero.collections:prunedrops versioned<alias>_<ts>_<hex>collections no alias points to, never those of a removed alias, and rechecks each candidate under the per-alias lockAliasManager::swap()takes.
Resilience
- Deadline per call:
deadlineoption, defaulttimeout × 2; each attempt's timeout is capped by what remains. - Timeouts are retried at most once per node.
- Node memory: a failed node is cached as down for 30 s and meanwhile tried once per call, only after every unmarked node has failed.
- Backoff: jittered sleep between rounds (100 ms base, 1 s cap).
- Logging: one warning per node failure (URL + status/error, never headers).
- Writes fail over only when the request provably never left (curl errno 6/7 or a connect-phase timeout);
otherwise a
POST/PATCHis sent once, as a replay could 409 a create that succeeded.replaySafe:null= by method (GET/HEAD/PUT/DELETEretry),true= opt in,false= opt out. Upserts andupsert/update/emplaceimports opt in when every document carries a stringid.
Config seams
Everything is injected by typesense-indexer:
Api\ConnectionSettingsInterface— nodes (each with its ownhttp/https), decrypted API key, timeouts, retry, health-cache TTL. Core binds aNotConfigureddefault that throws until the indexer supplies the reader.Model\Schema\ReconcilePolicy—Reconciler::reconcile()receives its rebuild threshold and decision override as an argument, not from config.
Extending the schema
FieldSpec models name/type/facet/index/sort/optional; any other Typesense field property (embed,
num_dim, hnsw_params, …) passes through its extra map verbatim. There is no per-field weight — Typesense
weights are a query-time concern.
License
OSL-3.0 © DMLab.
