# 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`-shaped `PluginDescriptor` factory that mounts the search routes at a configurable `routePrefix` (default `/search`) and registers a `searchIndexes` service in the host `ServiceRegistry`. - ADDED: `SearchPluginOptionsSchema` / `SearchPluginOptions` / `SearchPluginConfig` — Zod-validated factory options (`routePrefix`, `maxIndexes`, `maxDocumentsPerIndex`, `defaultAnalyzer`). - ADDED: In-memory `SearchService` functions (`createSearchIndex` / `deleteSearchIndex` / `getSearchIndex` / `listSearchIndexes` / `indexDocument` / `bulkIndexDocuments` / `removeDocument` / `search` / `suggest`) plus `DEFAULT_SEARCH_CONFIG` and the `SearchServiceConfig` type. - ADDED: Four text analyzers via factory functions (`createStandardAnalyzer`, `createSimpleAnalyzer`, `createWhitespaceAnalyzer`, `createKeywordAnalyzer`), plus exported primitives `tokenize`, `removeStopWords`, `stem`, `getAnalyzer`, and `listAnalyzers`. The `TextAnalyzer` and `AnalyzerOptions` interfaces 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 via `defineCodes("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-search` meter — `search.queries_total{outcome}`, `search.query_duration_ms` (histogram), `search.documents_indexed_total{op}`, `search.indexes_total{op}` and `search.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`, and `search:query:executed` — payloads strongly typed; no `as any` casts on the emitter. - ADDED: `searchIndexes` slot on the canonical `@suaveplan/api-plugin/service-registry` via module augmentation, typed as `Map` 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 `SearchAdapter` driver against `@suaveplan/api-plugin-database` once that package lands; the public `SearchAdapter` interface 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 + `SearchError` cause 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` — `SearchPluginOptionsSchema` defaults 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`, custom `routePrefix`, missing `app` service. - `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)