9.6 KiB
Add @suaveplan/api-plugin-search
Package: @suaveplan/api-plugin-search
Status: PROPOSED
Type: new-package
Design: ./design.md
Spec delta: ./specs/api-plugin-search/spec.md
Why
The Suaveplan API host needs an in-process full-text search plugin so consumer apps
can index domain documents (users, posts, products, audit events, ...) and serve
relevance-scored queries, facets, filters, highlights, and autocomplete suggestions
without a separate search-engine deployment. Upstream @suaveplan/api-plugin-search
provided this as an in-memory engine with TF-IDF scoring, four pluggable analyzers
(standard, simple, whitespace, keyword), faceted aggregation, boolean filters, and
optional highlighting. The genesis migration brings the full functional surface
across, swaps the upstream "old-style" @suaveplan/api Plugin contract for the
canonical @suaveplan/api-plugin PluginDescriptor shape, fixes the two as any
casts in the event bus call site by routing through typed payloads, replaces raw
throw new Error(...) sites with typed SearchError carrying stable SEARCH.*
codes, wires OpenTelemetry instruments (5 counters/histograms) onto every hot path,
and aligns the package with the rest of the genesis monorepo (5-files-per-feature,
sibling .md docs, runner-neutral test imports, 100% istanbul coverage).
What changes
- ADDED:
createSearchPlugin(options?)—@suaveplan/api-plugin-shapedPluginDescriptor<SearchPluginConfig>factory that mounts the search routes at a configurableroutePrefix(default/search) and registers asearchIndexesservice in the hostServiceRegistry. - ADDED:
SearchPluginOptionsSchema/SearchPluginOptions/SearchPluginConfig— Zod-validated factory options (routePrefix,maxIndexes,maxDocumentsPerIndex,defaultAnalyzer). - ADDED: In-memory
SearchServicefunctions (createSearchIndex/deleteSearchIndex/getSearchIndex/listSearchIndexes/indexDocument/bulkIndexDocuments/removeDocument/search/suggest) plusDEFAULT_SEARCH_CONFIGand theSearchServiceConfigtype. - ADDED: Four text analyzers via factory functions (
createStandardAnalyzer,createSimpleAnalyzer,createWhitespaceAnalyzer,createKeywordAnalyzer), plus exported primitivestokenize,removeStopWords,stem,getAnalyzer, andlistAnalyzers. TheTextAnalyzerandAnalyzerOptionsinterfaces are public so consumers can author and register their own analyzers. - ADDED:
createSearchRoutes(deps)— OpenAPIHono router exposing 9 routes:POST /query,POST /suggest,GET /indexes,POST /indexes,GET /indexes/:name,DELETE /indexes/:name,POST /indexes/:name/documents,POST /indexes/:name/bulk,DELETE /indexes/:name/documents/:docId. Every route validates request bodies/params with Zod via@hono/zod-openapi, and every error response is the canonical{ error: string }shape. - ADDED: 10
SEARCH.*error codes viadefineCodes("SEARCH", [...])—INDEX_NOT_FOUND,INDEX_ALREADY_EXISTS,INDEX_LIMIT_REACHED,DOCUMENT_LIMIT_REACHED,DOCUMENT_NOT_FOUND,UNKNOWN_ANALYZER,INVALID_OPTIONS,INVALID_QUERY,SERVICE_NOT_AVAILABLE,ROUTE_PREFIX_COLLISION— each thrown from at least one production code path. - ADDED: 5 OTel instruments under the
@suaveplan/api-plugin-searchmeter —search.queries_total{outcome},search.query_duration_ms(histogram),search.documents_indexed_total{op},search.indexes_total{op}andsearch.suggest_requests_total{outcome}. - ADDED: Typed event-bus extension for
search:initialized,search:ready,search:index:created,search:index:deleted,search:index:updated,search:document:indexed,search:bulk:completed,search:document:removed, andsearch:query:executed— payloads strongly typed; noas anycasts on the emitter. - ADDED:
searchIndexesslot on the canonical@suaveplan/api-plugin/service-registryvia module augmentation, typed asMap<string, SearchIndex>so sibling plugins can read/write indexes with full type safety.
Out of scope
- Persistent backend (SurrealDB FTS / Typesense / Meilisearch) — the upstream
package is in-memory only and the migration matches parity. A separate
follow-up change can introduce a
SearchAdapterdriver against@suaveplan/api-plugin-databaseonce that package lands; the publicSearchAdapterinterface ships here so the contract is stable. - Tenant scoping, auth gates, rate limiting, audit logging — upstream did not carry these; the enterprise audit document flags them as future work. They are not in this migration to preserve parity.
- Vector search, semantic ranking, learning-to-rank, saved searches, language-specific stemmers, query DSL — enterprise audit §4 enhancements, deferred to dedicated follow-up changes.
- Benchmark suite under
src/__benchmarks__/— landing as a follow-up change so this migration ships at parity scope first.
Source mapping
This change derives from ../suaveplan/enterprise/api-plugin-search.md and the
upstream source at ../suaveplan/packages/server/api-plugin-search/src/.
| Requirement | Source lines | Notes |
|---|---|---|
| In-memory search engine with TF-IDF scoring | enterprise L8-L13; src/services/search/search.service.ts#scoreDocuments L302-L336 |
verbatim parity |
| Four built-in analyzers (standard/simple/whitespace/keyword) | enterprise L8-L13; src/analyzers/text/text.analyzer.ts L156-L223 |
verbatim parity |
| Tokenize + stop-word + Porter-style stemmer | src/analyzers/text/text.analyzer.ts L52-L123 |
verbatim parity |
createSearchPlugin factory + four lifecycle hooks |
src/index.ts L39-L100 |
re-typed for @suaveplan/api-plugin |
SearchPluginConfigSchema (basePath/maxIndexes/maxDocumentsPerIndex/defaultAnalyzer) |
src/index.ts L21-L33 |
renamed to SearchPluginOptionsSchema for genesis convention |
| 9 OpenAPIHono routes with Zod request/response schemas | src/routes/search/search.ts L41-L479 |
verbatim route surface |
| Bulk index limit (1000 docs/request) | src/types.ts#BulkIndexSchema L113-L116 |
verbatim |
Faceted aggregation honouring IndexField.facet |
src/services/search/search.service.ts#buildFacets L431-L459 |
verbatim |
| Boolean / array filter pass | src/services/search/search.service.ts#applyFilters L338-L355 |
verbatim |
| Sort orders (relevance / date_asc / date_desc / alpha_asc / alpha_desc) | src/services/search/search.service.ts#sortResults L357-L398 |
verbatim |
| Highlighting with configurable HTML tag | src/services/search/search.service.ts#buildHighlights L400-L429 |
verbatim |
| Prefix-based suggest | src/services/search/search.service.ts#suggest L250-L291 |
verbatim |
| Per-index document limit enforcement | src/services/search/search.service.ts#indexDocument L117-L154 |
enforced via SEARCH.DOCUMENT_LIMIT_REACHED |
| Event-bus emission on lifecycle, query, index updates | src/index.ts L64-L93; src/routes/search/search.ts L92-L99/L350-L356/L414-L420/L465-L471 |
drop both as any casts (audit §3) |
Typed SearchQueryExecutedEvent / SearchIndexUpdatedEvent payloads |
src/types.ts L188-L218 |
preserved verbatim |
SearchAdapter interface (future SurrealFTS / Typesense backends) |
src/types.ts#SearchAdapter L172-L184 |
preserved verbatim |
Requirements with no source line
| Requirement | Justification |
|---|---|
Typed SEARCH.* error registry via defineCodes |
Genesis-wide rule (every package extends SuaveplanError); enterprise audit §3 explicitly calls for it. |
| OTel instruments on every hot path | Genesis-wide observability rule + enterprise audit §3. |
Runner-neutral test imports (@suaveplan/testing/runner) |
Genesis rule 11. |
| 100 / 100 / 100 / 100 istanbul coverage | Genesis-wide policy. |
Sibling .md documentation per implementation file |
Genesis-wide policy. |
| Subpath exports per feature module | Genesis-wide tree-shaking rule (§6). |
ServiceRegistry module augmentation for searchIndexes slot |
Genesis convention introduced by @suaveplan/api, @suaveplan/api-plugin-auth, @suaveplan/api-plugin-user. |
Verification approach
Every requirement in the spec delta carries a **Verified by:** line at draft
time pointing at the planned it(...) titles below.
src/error-codes/error-codes.test.ts—SEARCH.*registry shape +SearchErrorcause chain.src/otel/instruments.test.ts— meter binding, cache reset, instrument names.src/analyzer/analyzer.test.ts— tokenize / stop-words / stem / four analyzer factories / registry lookup / unknown-name failure.src/schemas/schemas.test.ts— every Zod schema parse/reject path.src/schemas/options.test.ts—SearchPluginOptionsSchemadefaults and bounds.src/service/service.test.ts— index lifecycle, document indexing, search, filters, sort, facets, highlighting, suggest, error codes, OTel emission.src/routes/build-routes.test.ts— every route (9 endpoints, success + error branches).src/plugin/plugin.test.ts— descriptor metadata,onInit/onReady/onClose, customroutePrefix, missingappservice.src/api-plugin-search.spec-conformance.test.ts— type-level public-surface pins + barrel exports + side-effect imports.
Migration impact
None — additive only. No existing genesis package depends on
@suaveplan/api-plugin-search.
Impact
Affected dependents
No dependents yet. After publication, sibling plugins (api-plugin-user,
api-plugin-cms, etc.) can read the augmented searchIndexes service slot.
- N/A — [x] no action needed (new package, zero pre-existing consumers in genesis)