@open-mercato/search 0.6.7-develop.6628.1.f9e4ecf70f → 0.6.7-develop.6631.1.9d64b49ddc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -537,6 +537,11 @@ curl "https://your-app.com/api/search?q=john%20doe&limit=20" \
537
537
  | `QUEUE_REDIS_URL` | When using a separate Redis for queues | Alternative to `REDIS_URL` for queue-specific connections |
538
538
  | `OM_SEARCH_ENABLED` | When you need to disable the search module entirely | Default: `true`; set to `false` to disable |
539
539
  | `OM_SEARCH_DEBUG` | When debugging search behavior | Enables verbose debug logging |
540
+ | `OM_SEARCH_MIN_LEN` | When tuning the Postgres `search_tokens` index | Default: `3`. Minimum token length + floor of prefix expansion. **Token strategy only** — no effect on fulltext/vector |
541
+ | `OM_SEARCH_ENABLE_PARTIAL` | When trading `search_tokens` size for prefix matching | Default: `true`. Prefix/partial expansion for tokens (Meilisearch unaffected); increases `search_tokens` size ~5–6×. **Token strategy only** |
542
+ | `OM_SEARCH_HASH_ALGO` | When choosing the token hash algorithm | Default: `sha256` (accepts `sha1`, `md5`). **Token strategy only** |
543
+ | `OM_SEARCH_STORE_RAW_TOKENS` | Almost never — debugging tokenization only | Default: `false`. Stores plaintext token alongside the hash — **security-sensitive**, retains plaintext of otherwise-hashed values. **Token strategy only** |
544
+ | `OM_SEARCH_FIELD_BLOCKLIST` | When extra fields must never be tokenized | Comma-separated field names, merged with built-in `password,token,secret,hash`. **Token strategy only** |
540
545
  | `SEARCH_EXCLUDE_ENCRYPTED_FIELDS` | When you need to keep encrypted fields out of fulltext | Set to `true` to exclude encrypted fields from fulltext index |
541
546
  | `OM_LOG_LEVEL` | When debugging presenter enrichment | Set to `debug` to surface presenter enricher diagnostics (replaces the former `DEBUG_SEARCH_ENRICHER` flag) |
542
547
 
@@ -637,6 +637,12 @@ const reindexHelpCli = {
637
637
  command: "reindex-help",
638
638
  async run() {
639
639
  console.log("Usage: yarn mercato search reindex [options]");
640
+ console.log("");
641
+ console.log(" Rebuilds the query_index projection (entity_indexes) + the Postgres search_tokens");
642
+ console.log(" index, and enqueues vector embedding jobs. It does NOT populate the fulltext");
643
+ console.log(" (Meilisearch) index \u2014 to rebuild fulltext use POST /api/search/reindex (feature");
644
+ console.log(" search.reindex) with a running `yarn mercato search worker fulltext-indexing`.");
645
+ console.log("");
640
646
  console.log(" --tenant <id> Optional tenant scope (required for purge & coverage).");
641
647
  console.log(" --org <id> Optional organization scope (requires tenant).");
642
648
  console.log(" --entity <module:entity> Reindex a single entity (defaults to all enabled entities).");
@@ -717,7 +723,8 @@ const helpCli = {
717
723
  console.log(" status Show search module status and available strategies");
718
724
  console.log(" query Execute a search query");
719
725
  console.log(" index Index a specific record");
720
- console.log(" reindex Reindex vector embeddings for entities");
726
+ console.log(" reindex Rebuild query_index projections + search_tokens and enqueue vector embeddings");
727
+ console.log(" (does NOT populate the fulltext/Meilisearch index \u2014 use POST /api/search/reindex)");
721
728
  console.log(" reindex-help Show reindex command options");
722
729
  console.log(" test-meilisearch Test Meilisearch connection");
723
730
  console.log(" worker Start a queue worker for search indexing");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/search/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getRedisUrlOrThrow } from '@open-mercato/shared/lib/redis/connection'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { createProgressBar } from '@open-mercato/shared/lib/cli/progress'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { reindexEntity, DEFAULT_REINDEX_PARTITIONS } from '@open-mercato/core/modules/query_index/lib/reindexer'\nimport { writeCoverageCounts } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport type { SearchService } from '../../service'\nimport type { SearchIndexer } from '../../indexer/search-indexer'\nimport { VECTOR_INDEXING_QUEUE_NAME, type VectorIndexJobPayload } from '../../queue/vector-indexing'\nimport { FULLTEXT_INDEXING_QUEUE_NAME, type FulltextIndexJobPayload } from '../../queue/fulltext-indexing'\nimport type { QueuedJob, JobContext } from '@open-mercato/queue'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\ntype CliProgressBar = {\n update(completed: number): void\n complete(): void\n}\n\ntype ParsedArgs = Record<string, string | boolean>\n\ntype PartitionProgressInfo = { processed: number; total: number }\n\nfunction isIndexerVerbose(): boolean {\n const parsed = parseBooleanToken(process.env.OM_INDEXER_VERBOSE ?? '')\n return parsed === true\n}\n\nfunction createGroupedProgress(label: string, partitionTargets: number[]) {\n const totals = new Map<number, number>()\n const processed = new Map<number, number>()\n let bar: CliProgressBar | null = null\n\n const getTotals = () => {\n let total = 0\n let done = 0\n for (const value of totals.values()) total += value\n for (const value of processed.values()) done += value\n return { total, done }\n }\n\n const tryInitBar = () => {\n if (bar) return\n if (totals.size < partitionTargets.length) return\n const { total } = getTotals()\n if (total <= 0) return\n bar = createProgressBar(label, total)\n }\n\n return {\n onProgress(partition: number, info: PartitionProgressInfo) {\n processed.set(partition, info.processed)\n if (!totals.has(partition)) totals.set(partition, info.total)\n tryInitBar()\n if (!bar) return\n const { done } = getTotals()\n bar.update(done)\n },\n complete() {\n if (bar) bar.complete()\n },\n getTotals,\n }\n}\n\nfunction parseArgs(rest: string[]): ParsedArgs {\n const args: ParsedArgs = {}\n for (let i = 0; i < rest.length; i += 1) {\n const part = rest[i]\n if (!part?.startsWith('--')) continue\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (!rawKey) continue\n if (rawValue !== undefined) {\n args[rawKey] = rawValue\n } else if (i + 1 < rest.length && !rest[i + 1]!.startsWith('--')) {\n args[rawKey] = rest[i + 1]!\n i += 1\n } else {\n args[rawKey] = true\n }\n }\n return args\n}\n\nfunction stringOpt(args: ParsedArgs, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw !== 'string') continue\n const trimmed = raw.trim()\n if (trimmed.length > 0) return trimmed\n }\n return undefined\n}\n\nfunction numberOpt(args: ParsedArgs, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw === 'number') return raw\n if (typeof raw === 'string') {\n const parsed = Number(raw)\n if (Number.isFinite(parsed)) return parsed\n }\n }\n return undefined\n}\n\nfunction flagOpt(args: ParsedArgs, ...keys: string[]): boolean | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (raw === undefined) continue\n if (raw === true) return true\n if (raw === false) return false\n if (typeof raw === 'string') {\n const trimmed = raw.trim()\n if (!trimmed) return true\n const parsed = parseBooleanToken(trimmed)\n return parsed === null ? true : parsed\n }\n }\n return undefined\n}\n\nfunction toPositiveInt(value: number | undefined): number | undefined {\n if (value === undefined) return undefined\n const n = Math.floor(value)\n if (!Number.isFinite(n) || n <= 0) return undefined\n return n\n}\n\nfunction toNonNegativeInt(value: number | undefined, fallback = 0): number {\n if (value === undefined) return fallback\n const n = Math.floor(value)\n if (!Number.isFinite(n) || n < 0) return fallback\n return n\n}\n\n/**\n * Test search functionality with a query\n */\nasync function searchCommand(rest: string[]): Promise<void> {\n const args = parseArgs(rest)\n const query = stringOpt(args, 'query', 'q')\n const tenantId = stringOpt(args, 'tenant', 'tenantId')\n const organizationId = stringOpt(args, 'org', 'organizationId')\n const entityTypes = stringOpt(args, 'entity', 'entities')\n const strategies = stringOpt(args, 'strategy', 'strategies')\n const limit = numberOpt(args, 'limit') ?? 20\n\n if (!query) {\n console.error('Usage: yarn mercato search query --query \"search terms\" --tenant <id> [options]')\n console.error(' --query, -q Search query (required)')\n console.error(' --tenant Tenant ID (required)')\n console.error(' --org Organization ID (optional)')\n console.error(' --entity Entity types to search (comma-separated)')\n console.error(' --strategy Strategies to use (comma-separated: meilisearch,vector,tokens)')\n console.error(' --limit Max results (default: 20)')\n return\n }\n\n if (!tenantId) {\n console.error('Error: --tenant is required')\n return\n }\n\n const container = await createRequestContainer()\n\n try {\n const searchService = container.resolve('searchService') as SearchService | undefined\n\n if (!searchService) {\n console.error('Error: SearchService not available. Make sure the search module is registered.')\n return\n }\n\n console.log(`\\nSearching for: \"${query}\"`)\n console.log(`Tenant: ${tenantId}`)\n if (organizationId) console.log(`Organization: ${organizationId}`)\n console.log('---')\n\n const results = await searchService.search(query, {\n tenantId,\n organizationId,\n entityTypes: entityTypes?.split(',').map(s => s.trim()),\n strategies: strategies?.split(',').map(s => s.trim()) as any,\n limit,\n })\n\n if (results.length === 0) {\n console.log('No results found.')\n return\n }\n\n console.log(`\\nFound ${results.length} result(s):\\n`)\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n console.log(`${i + 1}. [${result.source}] ${result.entityId}`)\n console.log(` Record ID: ${result.recordId}`)\n console.log(` Score: ${result.score.toFixed(4)}`)\n if (result.presenter) {\n console.log(` Title: ${result.presenter.title}`)\n if (result.presenter.subtitle) console.log(` Subtitle: ${result.presenter.subtitle}`)\n }\n if (result.url) console.log(` URL: ${result.url}`)\n console.log('')\n }\n } finally {\n try {\n const em = container.resolve('em') as any\n await em?.getConnection?.()?.close?.()\n } catch {}\n }\n}\n\n/**\n * Show status of search strategies\n */\nasync function statusCommand(): Promise<void> {\n const container = await createRequestContainer()\n\n try {\n const searchService = container.resolve('searchService') as SearchService | undefined\n const strategies = container.resolve('searchStrategies') as any[] | undefined\n\n console.log('\\n=== Search Module Status ===\\n')\n\n if (!searchService) {\n console.log('SearchService: NOT REGISTERED')\n return\n }\n\n console.log('SearchService: ACTIVE')\n console.log('')\n\n if (!strategies || strategies.length === 0) {\n console.log('Strategies: NONE CONFIGURED')\n return\n }\n\n console.log('Strategies:')\n console.log('-----------')\n\n for (const strategy of strategies) {\n const available = await strategy.isAvailable?.() ?? true\n const status = available ? 'AVAILABLE' : 'UNAVAILABLE'\n const icon = available ? '\u2713' : '\u2717'\n console.log(` ${icon} ${strategy.name ?? strategy.id} (${strategy.id})`)\n console.log(` Status: ${status}`)\n console.log(` Priority: ${strategy.priority ?? 'N/A'}`)\n console.log('')\n }\n\n // Check environment variables\n console.log('Environment:')\n console.log('------------')\n console.log(` MEILISEARCH_HOST: ${process.env.MEILISEARCH_HOST ?? '(not set)'}`)\n console.log(` MEILISEARCH_API_KEY: ${process.env.MEILISEARCH_API_KEY ? '(set)' : '(not set)'}`)\n console.log(` OPENAI_API_KEY: ${process.env.OPENAI_API_KEY ? '(set)' : '(not set)'}`)\n console.log(` OM_SEARCH_ENABLED: ${process.env.OM_SEARCH_ENABLED ?? 'true (default)'}`)\n console.log('')\n } finally {\n try {\n const em = container.resolve('em') as any\n await em?.getConnection?.()?.close?.()\n } catch {}\n }\n}\n\n/**\n * Index a specific record for testing\n */\nasync function indexCommand(rest: string[]): Promise<void> {\n const args = parseArgs(rest)\n const entityId = stringOpt(args, 'entity', 'entityId')\n const recordId = stringOpt(args, 'record', 'recordId')\n const tenantId = stringOpt(args, 'tenant', 'tenantId')\n const organizationId = stringOpt(args, 'org', 'organizationId')\n\n if (!entityId || !recordId || !tenantId) {\n console.error('Usage: yarn mercato search index --entity <entityId> --record <recordId> --tenant <tenantId>')\n console.error(' --entity Entity ID (e.g., customers:customer_person_profile)')\n console.error(' --record Record ID')\n console.error(' --tenant Tenant ID')\n console.error(' --org Organization ID (optional)')\n return\n }\n\n const container = await createRequestContainer()\n\n try {\n const searchIndexer = container.resolve('searchIndexer') as SearchIndexer | undefined\n\n if (!searchIndexer) {\n console.error('Error: SearchIndexer not available.')\n return\n }\n\n // Load record from query engine\n const queryEngine = container.resolve('queryEngine') as any\n\n console.log(`\\nLoading record: ${entityId} / ${recordId}`)\n\n const result = await queryEngine.query(entityId, {\n tenantId,\n organizationId,\n filters: { id: recordId },\n includeCustomFields: true,\n page: { page: 1, pageSize: 1 },\n })\n\n const record = result.items[0]\n\n if (!record) {\n console.error('Error: Record not found')\n return\n }\n\n console.log('Record loaded, indexing...')\n\n // Extract custom fields\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n const cfKey = key.slice(3) // Remove 'cf:' or 'cf_' prefix (both are 3 chars)\n customFields[cfKey] = value\n }\n }\n\n await searchIndexer.indexRecord({\n entityId,\n recordId,\n tenantId,\n organizationId,\n record,\n customFields,\n })\n\n console.log('Record indexed successfully!')\n } finally {\n try {\n const em = container.resolve('em') as any\n await em?.getConnection?.()?.close?.()\n } catch {}\n }\n}\n\n/**\n * Test Meilisearch connection directly\n */\nasync function testMeilisearchCommand(): Promise<void> {\n const host = process.env.MEILISEARCH_HOST\n const apiKey = process.env.MEILISEARCH_API_KEY\n\n console.log('\\n=== Meilisearch Connection Test ===\\n')\n\n if (!host) {\n console.log('MEILISEARCH_HOST: NOT SET')\n console.log('\\nMeilisearch is not configured. Set MEILISEARCH_HOST in your .env file.')\n return\n }\n\n console.log(`Host: ${host}`)\n console.log(`API Key: ${apiKey ? '(configured)' : '(not set)'}`)\n console.log('')\n\n try {\n const { Meilisearch } = await import('meilisearch')\n const client = new Meilisearch({ host, apiKey })\n\n console.log('Testing connection...')\n const health = await client.health()\n console.log(`Health: ${health.status}`)\n\n console.log('\\nListing indexes...')\n const indexes = await client.getIndexes()\n\n if (indexes.results.length === 0) {\n console.log('No indexes found.')\n } else {\n console.log(`Found ${indexes.results.length} index(es):`)\n for (const index of indexes.results) {\n const stats = await client.index(index.uid).getStats()\n console.log(` - ${index.uid}: ${stats.numberOfDocuments} documents`)\n }\n }\n\n console.log('\\nMeilisearch connection successful!')\n } catch (error) {\n console.error('Connection failed:', error instanceof Error ? error.message : error)\n }\n}\n\nconst searchCli: ModuleCli = {\n command: 'query',\n async run(rest) {\n await searchCommand(rest)\n },\n}\n\nconst statusCli: ModuleCli = {\n command: 'status',\n async run() {\n await statusCommand()\n },\n}\n\nconst indexCli: ModuleCli = {\n command: 'index',\n async run(rest) {\n await indexCommand(rest)\n },\n}\n\nconst testMeilisearchCli: ModuleCli = {\n command: 'test-meilisearch',\n async run() {\n await testMeilisearchCommand()\n },\n}\n\nasync function resetVectorCoverageAfterPurge(\n em: EntityManager | null,\n entityId: string,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<void> {\n if (!em || !entityId) return\n try {\n const scopes = new Set<string>()\n scopes.add('__null__')\n if (organizationId) scopes.add(organizationId)\n for (const scope of scopes) {\n const orgValue = scope === '__null__' ? null : scope\n await writeCoverageCounts(\n em,\n {\n entityType: entityId,\n tenantId,\n organizationId: orgValue,\n withDeleted: false,\n },\n { vectorCount: 0 },\n )\n }\n } catch (error) {\n console.warn('[search.cli] Failed to reset vector coverage after purge', error instanceof Error ? error.message : error)\n }\n}\n\nasync function reindexCommand(rest: string[]): Promise<void> {\n const args = parseArgs(rest)\n const tenantId = stringOpt(args, 'tenant', 'tenantId')\n const organizationId = stringOpt(args, 'org', 'orgId', 'organizationId')\n const entityId = stringOpt(args, 'entity', 'entityId')\n const force = flagOpt(args, 'force', 'full') === true\n const batchSize = toPositiveInt(numberOpt(args, 'batch', 'chunk', 'size'))\n const partitionsOption = toPositiveInt(numberOpt(args, 'partitions', 'partitionCount', 'parallel'))\n const partitionIndexRaw = numberOpt(args, 'partition', 'partitionIndex')\n const partitionIndexOption = partitionIndexRaw === undefined ? undefined : toNonNegativeInt(partitionIndexRaw, 0)\n const resetCoverageFlag = flagOpt(args, 'resetCoverage') === true\n const skipResetCoverageFlag = flagOpt(args, 'skipResetCoverage', 'noResetCoverage') === true\n const skipPurgeFlag = flagOpt(args, 'skipPurge', 'noPurge') === true\n const purgeFlag = flagOpt(args, 'purge', 'purgeFirst')\n\n const container = await createRequestContainer()\n let baseEm: EntityManager | null = null\n try {\n baseEm = (container.resolve('em') as EntityManager)\n } catch {\n baseEm = null\n }\n\n const disposeContainer = async () => {\n if (typeof (container as any)?.dispose === 'function') {\n await (container as any).dispose()\n }\n }\n\n const recordError = async (error: Error) => {\n await recordIndexerLog(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n level: 'warn',\n message: `Reindex failed${entityId ? ` for ${entityId}` : ''}`,\n entityType: entityId ?? null,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n details: { error: error.message },\n },\n ).catch(() => undefined)\n await recordIndexerError(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n error,\n entityType: entityId ?? null,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload: {\n args,\n force,\n batchSize,\n partitionsOption,\n partitionIndexOption,\n resetCoverageFlag,\n skipResetCoverageFlag,\n skipPurgeFlag,\n purgeFlag,\n },\n },\n )\n }\n\n try {\n const searchIndexer = container.resolve<SearchIndexer>('searchIndexer')\n const enabledEntities = new Set(searchIndexer.listEnabledEntities())\n const baseEventBus = (() => {\n try {\n return container.resolve('eventBus') as {\n emitEvent(event: string, payload: any, options?: any): Promise<void>\n }\n } catch {\n return null\n }\n })()\n if (!baseEventBus) {\n console.warn('[search.cli] eventBus unavailable; vector embeddings may not be refreshed. Run bootstrap or ensure event bus configuration.')\n }\n\n const partitionCount = Math.max(1, partitionsOption ?? DEFAULT_REINDEX_PARTITIONS)\n if (partitionIndexOption !== undefined && partitionIndexOption >= partitionCount) {\n console.error(`partitionIndex (${partitionIndexOption}) must be < partitionCount (${partitionCount})`)\n return\n }\n const partitionTargets =\n partitionIndexOption !== undefined\n ? [partitionIndexOption]\n : Array.from({ length: partitionCount }, (_, idx) => idx)\n\n const shouldResetCoverage = (partition: number): boolean => {\n if (resetCoverageFlag) return true\n if (skipResetCoverageFlag) return false\n if (partitionIndexOption !== undefined) return partitionIndexOption === 0\n return partition === partitionTargets[0]\n }\n\n const runReindex = async (entityType: string, purgeFirst: boolean) => {\n const scopeLabel = tenantId\n ? `tenant=${tenantId}${organizationId ? `, org=${organizationId}` : ''}`\n : 'all tenants'\n console.log(`Reindexing vectors for ${entityType} (${scopeLabel})${purgeFirst ? ' [purge]' : ''}`)\n await recordIndexerLog(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n message: `Reindex started for ${entityType}`,\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n details: {\n purgeFirst,\n partitions: partitionTargets.length,\n partitionCount,\n partitionIndex: partitionIndexOption ?? null,\n batchSize,\n },\n },\n ).catch(() => undefined)\n\n if (purgeFirst && tenantId) {\n try {\n console.log(' -> purging existing vector index rows...')\n await searchIndexer.purgeEntity({ entityId: entityType as EntityId, tenantId, organizationId })\n await resetVectorCoverageAfterPurge(baseEm, entityType, tenantId ?? null, organizationId ?? null)\n if (baseEventBus) {\n const scopes = new Set<string>()\n scopes.add('__null__')\n if (organizationId) scopes.add(organizationId)\n await Promise.all(\n Array.from(scopes).map((scope) => {\n const orgValue = scope === '__null__' ? null : scope\n return baseEventBus!\n .emitEvent(\n 'query_index.coverage.refresh',\n {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: orgValue,\n delayMs: 0,\n },\n )\n .catch(() => undefined)\n }),\n )\n }\n } catch (err) {\n console.warn(' -> purge failed, continuing with reindex', err instanceof Error ? err.message : err)\n }\n } else if (purgeFirst && !tenantId) {\n console.warn(' -> skipping purge: tenant scope not provided')\n }\n\n const verbose = isIndexerVerbose()\n const progressState = verbose ? new Map<number, { last: number }>() : null\n const groupedProgress =\n !verbose && partitionTargets.length > 1\n ? createGroupedProgress(`Reindexing ${entityType}`, partitionTargets)\n : null\n const renderProgress = (part: number, info: PartitionProgressInfo) => {\n if (!progressState) return\n const state = progressState.get(part) ?? { last: 0 }\n const now = Date.now()\n if (now - state.last < 1000 && info.processed < info.total) return\n state.last = now\n progressState.set(part, state)\n const percent = info.total > 0 ? ((info.processed / info.total) * 100).toFixed(2) : '0.00'\n console.log(\n ` [${entityType}] partition ${part + 1}/${partitionCount}: ${info.processed.toLocaleString()} / ${info.total.toLocaleString()} (${percent}%)`,\n )\n }\n\n const processed = await Promise.all(\n partitionTargets.map(async (part, idx) => {\n const label = partitionTargets.length > 1 ? ` [partition ${part + 1}/${partitionCount}]` : ''\n if (partitionTargets.length === 1) {\n console.log(` -> processing${label}`)\n } else if (verbose && idx === 0) {\n console.log(` -> processing partitions in parallel (count=${partitionTargets.length})`)\n }\n\n const partitionContainer = await createRequestContainer()\n const partitionEm = partitionContainer.resolve<EntityManager>('em')\n try {\n let progressBar: CliProgressBar | null = null\n const useBar = partitionTargets.length === 1\n const stats = await reindexEntity(partitionEm, {\n entityType,\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n force,\n batchSize,\n eventBus: baseEventBus ?? undefined,\n emitVectorizeEvents: true,\n partitionCount,\n partitionIndex: part,\n resetCoverage: shouldResetCoverage(part),\n onProgress(info) {\n if (useBar) {\n if (info.total > 0 && !progressBar) {\n progressBar = createProgressBar(`Reindexing ${entityType}${label}`, info.total)\n }\n progressBar?.update(info.processed)\n } else if (groupedProgress) {\n groupedProgress.onProgress(part, info)\n } else {\n renderProgress(part, info)\n }\n },\n })\n if (progressBar) (progressBar as CliProgressBar).complete()\n if (!useBar && groupedProgress) {\n groupedProgress.onProgress(part, { processed: stats.processed, total: stats.total })\n } else if (!useBar) {\n renderProgress(part, { processed: stats.processed, total: stats.total })\n } else {\n console.log(\n ` processed ${stats.processed} row(s)${stats.total ? ` (base ${stats.total})` : ''}`,\n )\n }\n return stats.processed\n } finally {\n if (typeof (partitionContainer as any)?.dispose === 'function') {\n await (partitionContainer as any).dispose()\n }\n }\n }),\n )\n\n groupedProgress?.complete()\n const totalProcessed = processed.reduce((acc, value) => acc + value, 0)\n console.log(`Finished ${entityType}: processed ${totalProcessed} row(s) across ${partitionTargets.length} partition(s)`)\n await recordIndexerLog(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n message: `Reindex completed for ${entityType}`,\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n details: {\n processed: totalProcessed,\n partitions: partitionTargets.length,\n partitionCount,\n partitionIndex: partitionIndexOption ?? null,\n batchSize,\n },\n },\n ).catch(() => undefined)\n return totalProcessed\n }\n\n const defaultPurge = purgeFlag === true && !skipPurgeFlag\n\n if (entityId) {\n if (!enabledEntities.has(entityId)) {\n console.error(`Entity ${entityId} is not enabled for vector search.`)\n return\n }\n const purgeFirst = defaultPurge\n await runReindex(entityId, purgeFirst)\n console.log('Vector reindex completed.')\n return\n }\n\n const entityIds = searchIndexer.listEnabledEntities()\n if (!entityIds.length) {\n console.log('No entities enabled for vector search.')\n return\n }\n console.log(`Reindexing ${entityIds.length} vector-enabled entities...`)\n let processedOverall = 0\n for (let idx = 0; idx < entityIds.length; idx += 1) {\n const id = entityIds[idx]!\n console.log(`[${idx + 1}/${entityIds.length}] Preparing ${id}...`)\n processedOverall += await runReindex(id, defaultPurge)\n }\n console.log(`Vector reindex completed. Total processed rows: ${processedOverall.toLocaleString()}`)\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n console.error('[search.cli] Reindex failed:', err.stack ?? err.message)\n await recordError(err)\n throw err\n } finally {\n await disposeContainer()\n }\n}\n\nconst reindexCli: ModuleCli = {\n command: 'reindex',\n async run(rest) {\n await reindexCommand(rest)\n },\n}\n\nconst reindexHelpCli: ModuleCli = {\n command: 'reindex-help',\n async run() {\n console.log('Usage: yarn mercato search reindex [options]')\n console.log(' --tenant <id> Optional tenant scope (required for purge & coverage).')\n console.log(' --org <id> Optional organization scope (requires tenant).')\n console.log(' --entity <module:entity> Reindex a single entity (defaults to all enabled entities).')\n console.log(' --partitions <n> Number of partitions to process in parallel (default from query index).')\n console.log(' --partition <idx> Restrict to a specific partition index.')\n console.log(' --batch <n> Override batch size per chunk.')\n console.log(' --force Force reindex even if another job is running.')\n console.log(' --purgeFirst Purge vector rows before reindexing (defaults to skip).')\n console.log(' --skipPurge Explicitly skip purging vector rows.')\n console.log(' --skipResetCoverage Keep existing coverage snapshots.')\n },\n}\n\n/**\n * Start a queue worker for processing search indexing jobs.\n */\nasync function workerCommand(rest: string[]): Promise<void> {\n const queueName = rest[0]\n const args = parseArgs(rest)\n const concurrency = toPositiveInt(numberOpt(args, 'concurrency')) ?? 1\n\n const validQueues = [VECTOR_INDEXING_QUEUE_NAME, FULLTEXT_INDEXING_QUEUE_NAME]\n\n if (!queueName || !validQueues.includes(queueName)) {\n console.error('\\nUsage: yarn mercato search worker <queue-name> [options]\\n')\n console.error('Available queues:')\n console.error(` ${VECTOR_INDEXING_QUEUE_NAME} Process vector embedding indexing jobs`)\n console.error(` ${FULLTEXT_INDEXING_QUEUE_NAME} Process fulltext indexing jobs`)\n console.error('\\nOptions:')\n console.error(' --concurrency <n> Number of concurrent jobs to process (default: 1)')\n console.error('\\nExamples:')\n console.error(` yarn mercato search worker ${VECTOR_INDEXING_QUEUE_NAME} --concurrency=10`)\n console.error(` yarn mercato search worker ${FULLTEXT_INDEXING_QUEUE_NAME} --concurrency=5`)\n return\n }\n\n // Check if Redis is configured for async queue\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n if (queueStrategy !== 'async') {\n console.error('\\nError: Queue workers require QUEUE_STRATEGY=async')\n console.error('Set QUEUE_STRATEGY=async and configure REDIS_URL in your environment.\\n')\n return\n }\n\n const redisUrl = getRedisUrlOrThrow('QUEUE')\n\n // Dynamically import runWorker to avoid loading BullMQ unless needed\n const { runWorker } = await import('@open-mercato/queue/worker')\n\n console.log(`\\nStarting ${queueName} worker...`)\n console.log(` Concurrency: ${concurrency}`)\n console.log(` Redis: ${redisUrl.replace(/\\/\\/[^:]+:[^@]+@/, '//<credentials>@')}`)\n console.log('')\n\n if (queueName === VECTOR_INDEXING_QUEUE_NAME) {\n const { handleVectorIndexJob } = await import('./workers/vector-index.worker')\n const container = await createRequestContainer()\n\n await runWorker<VectorIndexJobPayload>({\n queueName: VECTOR_INDEXING_QUEUE_NAME,\n handler: async (job: QueuedJob<VectorIndexJobPayload>, ctx: JobContext) => {\n await handleVectorIndexJob(job, ctx, { resolve: container.resolve.bind(container) })\n },\n connection: { url: redisUrl },\n concurrency,\n })\n } else if (queueName === FULLTEXT_INDEXING_QUEUE_NAME) {\n const { handleFulltextIndexJob } = await import('./workers/fulltext-index.worker')\n const container = await createRequestContainer()\n\n await runWorker<FulltextIndexJobPayload>({\n queueName: FULLTEXT_INDEXING_QUEUE_NAME,\n handler: async (job: QueuedJob<FulltextIndexJobPayload>, ctx: JobContext) => {\n await handleFulltextIndexJob(job, ctx, { resolve: container.resolve.bind(container) })\n },\n connection: { url: redisUrl },\n concurrency,\n })\n }\n}\n\nconst workerCli: ModuleCli = {\n command: 'worker',\n async run(rest) {\n await workerCommand(rest)\n },\n}\n\nconst helpCli: ModuleCli = {\n command: 'help',\n async run() {\n console.log('\\nUsage: yarn mercato search <command> [options]\\n')\n console.log('Commands:')\n console.log(' status Show search module status and available strategies')\n console.log(' query Execute a search query')\n console.log(' index Index a specific record')\n console.log(' reindex Reindex vector embeddings for entities')\n console.log(' reindex-help Show reindex command options')\n console.log(' test-meilisearch Test Meilisearch connection')\n console.log(' worker Start a queue worker for search indexing')\n console.log(' help Show this help message')\n console.log('\\nExamples:')\n console.log(' yarn mercato search status')\n console.log(' yarn mercato search query --query \"john doe\" --tenant tenant-123')\n console.log(' yarn mercato search index --entity customers:customer_person_profile --record abc123 --tenant tenant-123')\n console.log(' yarn mercato search reindex --tenant tenant-123 --entity customers:customer_person_profile')\n console.log(' yarn mercato search test-meilisearch')\n console.log(` yarn mercato search worker ${VECTOR_INDEXING_QUEUE_NAME} --concurrency=10`)\n console.log(` yarn mercato search worker ${FULLTEXT_INDEXING_QUEUE_NAME} --concurrency=5`)\n },\n}\n\nexport default [searchCli, statusCli, indexCli, reindexCli, reindexHelpCli, testMeilisearchCli, workerCli, helpCli]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getRedisUrlOrThrow } from '@open-mercato/shared/lib/redis/connection'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { createProgressBar } from '@open-mercato/shared/lib/cli/progress'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { reindexEntity, DEFAULT_REINDEX_PARTITIONS } from '@open-mercato/core/modules/query_index/lib/reindexer'\nimport { writeCoverageCounts } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport type { SearchService } from '../../service'\nimport type { SearchIndexer } from '../../indexer/search-indexer'\nimport { VECTOR_INDEXING_QUEUE_NAME, type VectorIndexJobPayload } from '../../queue/vector-indexing'\nimport { FULLTEXT_INDEXING_QUEUE_NAME, type FulltextIndexJobPayload } from '../../queue/fulltext-indexing'\nimport type { QueuedJob, JobContext } from '@open-mercato/queue'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\ntype CliProgressBar = {\n update(completed: number): void\n complete(): void\n}\n\ntype ParsedArgs = Record<string, string | boolean>\n\ntype PartitionProgressInfo = { processed: number; total: number }\n\nfunction isIndexerVerbose(): boolean {\n const parsed = parseBooleanToken(process.env.OM_INDEXER_VERBOSE ?? '')\n return parsed === true\n}\n\nfunction createGroupedProgress(label: string, partitionTargets: number[]) {\n const totals = new Map<number, number>()\n const processed = new Map<number, number>()\n let bar: CliProgressBar | null = null\n\n const getTotals = () => {\n let total = 0\n let done = 0\n for (const value of totals.values()) total += value\n for (const value of processed.values()) done += value\n return { total, done }\n }\n\n const tryInitBar = () => {\n if (bar) return\n if (totals.size < partitionTargets.length) return\n const { total } = getTotals()\n if (total <= 0) return\n bar = createProgressBar(label, total)\n }\n\n return {\n onProgress(partition: number, info: PartitionProgressInfo) {\n processed.set(partition, info.processed)\n if (!totals.has(partition)) totals.set(partition, info.total)\n tryInitBar()\n if (!bar) return\n const { done } = getTotals()\n bar.update(done)\n },\n complete() {\n if (bar) bar.complete()\n },\n getTotals,\n }\n}\n\nfunction parseArgs(rest: string[]): ParsedArgs {\n const args: ParsedArgs = {}\n for (let i = 0; i < rest.length; i += 1) {\n const part = rest[i]\n if (!part?.startsWith('--')) continue\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (!rawKey) continue\n if (rawValue !== undefined) {\n args[rawKey] = rawValue\n } else if (i + 1 < rest.length && !rest[i + 1]!.startsWith('--')) {\n args[rawKey] = rest[i + 1]!\n i += 1\n } else {\n args[rawKey] = true\n }\n }\n return args\n}\n\nfunction stringOpt(args: ParsedArgs, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw !== 'string') continue\n const trimmed = raw.trim()\n if (trimmed.length > 0) return trimmed\n }\n return undefined\n}\n\nfunction numberOpt(args: ParsedArgs, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (typeof raw === 'number') return raw\n if (typeof raw === 'string') {\n const parsed = Number(raw)\n if (Number.isFinite(parsed)) return parsed\n }\n }\n return undefined\n}\n\nfunction flagOpt(args: ParsedArgs, ...keys: string[]): boolean | undefined {\n for (const key of keys) {\n const raw = args[key]\n if (raw === undefined) continue\n if (raw === true) return true\n if (raw === false) return false\n if (typeof raw === 'string') {\n const trimmed = raw.trim()\n if (!trimmed) return true\n const parsed = parseBooleanToken(trimmed)\n return parsed === null ? true : parsed\n }\n }\n return undefined\n}\n\nfunction toPositiveInt(value: number | undefined): number | undefined {\n if (value === undefined) return undefined\n const n = Math.floor(value)\n if (!Number.isFinite(n) || n <= 0) return undefined\n return n\n}\n\nfunction toNonNegativeInt(value: number | undefined, fallback = 0): number {\n if (value === undefined) return fallback\n const n = Math.floor(value)\n if (!Number.isFinite(n) || n < 0) return fallback\n return n\n}\n\n/**\n * Test search functionality with a query\n */\nasync function searchCommand(rest: string[]): Promise<void> {\n const args = parseArgs(rest)\n const query = stringOpt(args, 'query', 'q')\n const tenantId = stringOpt(args, 'tenant', 'tenantId')\n const organizationId = stringOpt(args, 'org', 'organizationId')\n const entityTypes = stringOpt(args, 'entity', 'entities')\n const strategies = stringOpt(args, 'strategy', 'strategies')\n const limit = numberOpt(args, 'limit') ?? 20\n\n if (!query) {\n console.error('Usage: yarn mercato search query --query \"search terms\" --tenant <id> [options]')\n console.error(' --query, -q Search query (required)')\n console.error(' --tenant Tenant ID (required)')\n console.error(' --org Organization ID (optional)')\n console.error(' --entity Entity types to search (comma-separated)')\n console.error(' --strategy Strategies to use (comma-separated: meilisearch,vector,tokens)')\n console.error(' --limit Max results (default: 20)')\n return\n }\n\n if (!tenantId) {\n console.error('Error: --tenant is required')\n return\n }\n\n const container = await createRequestContainer()\n\n try {\n const searchService = container.resolve('searchService') as SearchService | undefined\n\n if (!searchService) {\n console.error('Error: SearchService not available. Make sure the search module is registered.')\n return\n }\n\n console.log(`\\nSearching for: \"${query}\"`)\n console.log(`Tenant: ${tenantId}`)\n if (organizationId) console.log(`Organization: ${organizationId}`)\n console.log('---')\n\n const results = await searchService.search(query, {\n tenantId,\n organizationId,\n entityTypes: entityTypes?.split(',').map(s => s.trim()),\n strategies: strategies?.split(',').map(s => s.trim()) as any,\n limit,\n })\n\n if (results.length === 0) {\n console.log('No results found.')\n return\n }\n\n console.log(`\\nFound ${results.length} result(s):\\n`)\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n console.log(`${i + 1}. [${result.source}] ${result.entityId}`)\n console.log(` Record ID: ${result.recordId}`)\n console.log(` Score: ${result.score.toFixed(4)}`)\n if (result.presenter) {\n console.log(` Title: ${result.presenter.title}`)\n if (result.presenter.subtitle) console.log(` Subtitle: ${result.presenter.subtitle}`)\n }\n if (result.url) console.log(` URL: ${result.url}`)\n console.log('')\n }\n } finally {\n try {\n const em = container.resolve('em') as any\n await em?.getConnection?.()?.close?.()\n } catch {}\n }\n}\n\n/**\n * Show status of search strategies\n */\nasync function statusCommand(): Promise<void> {\n const container = await createRequestContainer()\n\n try {\n const searchService = container.resolve('searchService') as SearchService | undefined\n const strategies = container.resolve('searchStrategies') as any[] | undefined\n\n console.log('\\n=== Search Module Status ===\\n')\n\n if (!searchService) {\n console.log('SearchService: NOT REGISTERED')\n return\n }\n\n console.log('SearchService: ACTIVE')\n console.log('')\n\n if (!strategies || strategies.length === 0) {\n console.log('Strategies: NONE CONFIGURED')\n return\n }\n\n console.log('Strategies:')\n console.log('-----------')\n\n for (const strategy of strategies) {\n const available = await strategy.isAvailable?.() ?? true\n const status = available ? 'AVAILABLE' : 'UNAVAILABLE'\n const icon = available ? '\u2713' : '\u2717'\n console.log(` ${icon} ${strategy.name ?? strategy.id} (${strategy.id})`)\n console.log(` Status: ${status}`)\n console.log(` Priority: ${strategy.priority ?? 'N/A'}`)\n console.log('')\n }\n\n // Check environment variables\n console.log('Environment:')\n console.log('------------')\n console.log(` MEILISEARCH_HOST: ${process.env.MEILISEARCH_HOST ?? '(not set)'}`)\n console.log(` MEILISEARCH_API_KEY: ${process.env.MEILISEARCH_API_KEY ? '(set)' : '(not set)'}`)\n console.log(` OPENAI_API_KEY: ${process.env.OPENAI_API_KEY ? '(set)' : '(not set)'}`)\n console.log(` OM_SEARCH_ENABLED: ${process.env.OM_SEARCH_ENABLED ?? 'true (default)'}`)\n console.log('')\n } finally {\n try {\n const em = container.resolve('em') as any\n await em?.getConnection?.()?.close?.()\n } catch {}\n }\n}\n\n/**\n * Index a specific record for testing\n */\nasync function indexCommand(rest: string[]): Promise<void> {\n const args = parseArgs(rest)\n const entityId = stringOpt(args, 'entity', 'entityId')\n const recordId = stringOpt(args, 'record', 'recordId')\n const tenantId = stringOpt(args, 'tenant', 'tenantId')\n const organizationId = stringOpt(args, 'org', 'organizationId')\n\n if (!entityId || !recordId || !tenantId) {\n console.error('Usage: yarn mercato search index --entity <entityId> --record <recordId> --tenant <tenantId>')\n console.error(' --entity Entity ID (e.g., customers:customer_person_profile)')\n console.error(' --record Record ID')\n console.error(' --tenant Tenant ID')\n console.error(' --org Organization ID (optional)')\n return\n }\n\n const container = await createRequestContainer()\n\n try {\n const searchIndexer = container.resolve('searchIndexer') as SearchIndexer | undefined\n\n if (!searchIndexer) {\n console.error('Error: SearchIndexer not available.')\n return\n }\n\n // Load record from query engine\n const queryEngine = container.resolve('queryEngine') as any\n\n console.log(`\\nLoading record: ${entityId} / ${recordId}`)\n\n const result = await queryEngine.query(entityId, {\n tenantId,\n organizationId,\n filters: { id: recordId },\n includeCustomFields: true,\n page: { page: 1, pageSize: 1 },\n })\n\n const record = result.items[0]\n\n if (!record) {\n console.error('Error: Record not found')\n return\n }\n\n console.log('Record loaded, indexing...')\n\n // Extract custom fields\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n const cfKey = key.slice(3) // Remove 'cf:' or 'cf_' prefix (both are 3 chars)\n customFields[cfKey] = value\n }\n }\n\n await searchIndexer.indexRecord({\n entityId,\n recordId,\n tenantId,\n organizationId,\n record,\n customFields,\n })\n\n console.log('Record indexed successfully!')\n } finally {\n try {\n const em = container.resolve('em') as any\n await em?.getConnection?.()?.close?.()\n } catch {}\n }\n}\n\n/**\n * Test Meilisearch connection directly\n */\nasync function testMeilisearchCommand(): Promise<void> {\n const host = process.env.MEILISEARCH_HOST\n const apiKey = process.env.MEILISEARCH_API_KEY\n\n console.log('\\n=== Meilisearch Connection Test ===\\n')\n\n if (!host) {\n console.log('MEILISEARCH_HOST: NOT SET')\n console.log('\\nMeilisearch is not configured. Set MEILISEARCH_HOST in your .env file.')\n return\n }\n\n console.log(`Host: ${host}`)\n console.log(`API Key: ${apiKey ? '(configured)' : '(not set)'}`)\n console.log('')\n\n try {\n const { Meilisearch } = await import('meilisearch')\n const client = new Meilisearch({ host, apiKey })\n\n console.log('Testing connection...')\n const health = await client.health()\n console.log(`Health: ${health.status}`)\n\n console.log('\\nListing indexes...')\n const indexes = await client.getIndexes()\n\n if (indexes.results.length === 0) {\n console.log('No indexes found.')\n } else {\n console.log(`Found ${indexes.results.length} index(es):`)\n for (const index of indexes.results) {\n const stats = await client.index(index.uid).getStats()\n console.log(` - ${index.uid}: ${stats.numberOfDocuments} documents`)\n }\n }\n\n console.log('\\nMeilisearch connection successful!')\n } catch (error) {\n console.error('Connection failed:', error instanceof Error ? error.message : error)\n }\n}\n\nconst searchCli: ModuleCli = {\n command: 'query',\n async run(rest) {\n await searchCommand(rest)\n },\n}\n\nconst statusCli: ModuleCli = {\n command: 'status',\n async run() {\n await statusCommand()\n },\n}\n\nconst indexCli: ModuleCli = {\n command: 'index',\n async run(rest) {\n await indexCommand(rest)\n },\n}\n\nconst testMeilisearchCli: ModuleCli = {\n command: 'test-meilisearch',\n async run() {\n await testMeilisearchCommand()\n },\n}\n\nasync function resetVectorCoverageAfterPurge(\n em: EntityManager | null,\n entityId: string,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<void> {\n if (!em || !entityId) return\n try {\n const scopes = new Set<string>()\n scopes.add('__null__')\n if (organizationId) scopes.add(organizationId)\n for (const scope of scopes) {\n const orgValue = scope === '__null__' ? null : scope\n await writeCoverageCounts(\n em,\n {\n entityType: entityId,\n tenantId,\n organizationId: orgValue,\n withDeleted: false,\n },\n { vectorCount: 0 },\n )\n }\n } catch (error) {\n console.warn('[search.cli] Failed to reset vector coverage after purge', error instanceof Error ? error.message : error)\n }\n}\n\nasync function reindexCommand(rest: string[]): Promise<void> {\n const args = parseArgs(rest)\n const tenantId = stringOpt(args, 'tenant', 'tenantId')\n const organizationId = stringOpt(args, 'org', 'orgId', 'organizationId')\n const entityId = stringOpt(args, 'entity', 'entityId')\n const force = flagOpt(args, 'force', 'full') === true\n const batchSize = toPositiveInt(numberOpt(args, 'batch', 'chunk', 'size'))\n const partitionsOption = toPositiveInt(numberOpt(args, 'partitions', 'partitionCount', 'parallel'))\n const partitionIndexRaw = numberOpt(args, 'partition', 'partitionIndex')\n const partitionIndexOption = partitionIndexRaw === undefined ? undefined : toNonNegativeInt(partitionIndexRaw, 0)\n const resetCoverageFlag = flagOpt(args, 'resetCoverage') === true\n const skipResetCoverageFlag = flagOpt(args, 'skipResetCoverage', 'noResetCoverage') === true\n const skipPurgeFlag = flagOpt(args, 'skipPurge', 'noPurge') === true\n const purgeFlag = flagOpt(args, 'purge', 'purgeFirst')\n\n const container = await createRequestContainer()\n let baseEm: EntityManager | null = null\n try {\n baseEm = (container.resolve('em') as EntityManager)\n } catch {\n baseEm = null\n }\n\n const disposeContainer = async () => {\n if (typeof (container as any)?.dispose === 'function') {\n await (container as any).dispose()\n }\n }\n\n const recordError = async (error: Error) => {\n await recordIndexerLog(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n level: 'warn',\n message: `Reindex failed${entityId ? ` for ${entityId}` : ''}`,\n entityType: entityId ?? null,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n details: { error: error.message },\n },\n ).catch(() => undefined)\n await recordIndexerError(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n error,\n entityType: entityId ?? null,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n payload: {\n args,\n force,\n batchSize,\n partitionsOption,\n partitionIndexOption,\n resetCoverageFlag,\n skipResetCoverageFlag,\n skipPurgeFlag,\n purgeFlag,\n },\n },\n )\n }\n\n try {\n const searchIndexer = container.resolve<SearchIndexer>('searchIndexer')\n const enabledEntities = new Set(searchIndexer.listEnabledEntities())\n const baseEventBus = (() => {\n try {\n return container.resolve('eventBus') as {\n emitEvent(event: string, payload: any, options?: any): Promise<void>\n }\n } catch {\n return null\n }\n })()\n if (!baseEventBus) {\n console.warn('[search.cli] eventBus unavailable; vector embeddings may not be refreshed. Run bootstrap or ensure event bus configuration.')\n }\n\n const partitionCount = Math.max(1, partitionsOption ?? DEFAULT_REINDEX_PARTITIONS)\n if (partitionIndexOption !== undefined && partitionIndexOption >= partitionCount) {\n console.error(`partitionIndex (${partitionIndexOption}) must be < partitionCount (${partitionCount})`)\n return\n }\n const partitionTargets =\n partitionIndexOption !== undefined\n ? [partitionIndexOption]\n : Array.from({ length: partitionCount }, (_, idx) => idx)\n\n const shouldResetCoverage = (partition: number): boolean => {\n if (resetCoverageFlag) return true\n if (skipResetCoverageFlag) return false\n if (partitionIndexOption !== undefined) return partitionIndexOption === 0\n return partition === partitionTargets[0]\n }\n\n const runReindex = async (entityType: string, purgeFirst: boolean) => {\n const scopeLabel = tenantId\n ? `tenant=${tenantId}${organizationId ? `, org=${organizationId}` : ''}`\n : 'all tenants'\n console.log(`Reindexing vectors for ${entityType} (${scopeLabel})${purgeFirst ? ' [purge]' : ''}`)\n await recordIndexerLog(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n message: `Reindex started for ${entityType}`,\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n details: {\n purgeFirst,\n partitions: partitionTargets.length,\n partitionCount,\n partitionIndex: partitionIndexOption ?? null,\n batchSize,\n },\n },\n ).catch(() => undefined)\n\n if (purgeFirst && tenantId) {\n try {\n console.log(' -> purging existing vector index rows...')\n await searchIndexer.purgeEntity({ entityId: entityType as EntityId, tenantId, organizationId })\n await resetVectorCoverageAfterPurge(baseEm, entityType, tenantId ?? null, organizationId ?? null)\n if (baseEventBus) {\n const scopes = new Set<string>()\n scopes.add('__null__')\n if (organizationId) scopes.add(organizationId)\n await Promise.all(\n Array.from(scopes).map((scope) => {\n const orgValue = scope === '__null__' ? null : scope\n return baseEventBus!\n .emitEvent(\n 'query_index.coverage.refresh',\n {\n entityType,\n tenantId: tenantId ?? null,\n organizationId: orgValue,\n delayMs: 0,\n },\n )\n .catch(() => undefined)\n }),\n )\n }\n } catch (err) {\n console.warn(' -> purge failed, continuing with reindex', err instanceof Error ? err.message : err)\n }\n } else if (purgeFirst && !tenantId) {\n console.warn(' -> skipping purge: tenant scope not provided')\n }\n\n const verbose = isIndexerVerbose()\n const progressState = verbose ? new Map<number, { last: number }>() : null\n const groupedProgress =\n !verbose && partitionTargets.length > 1\n ? createGroupedProgress(`Reindexing ${entityType}`, partitionTargets)\n : null\n const renderProgress = (part: number, info: PartitionProgressInfo) => {\n if (!progressState) return\n const state = progressState.get(part) ?? { last: 0 }\n const now = Date.now()\n if (now - state.last < 1000 && info.processed < info.total) return\n state.last = now\n progressState.set(part, state)\n const percent = info.total > 0 ? ((info.processed / info.total) * 100).toFixed(2) : '0.00'\n console.log(\n ` [${entityType}] partition ${part + 1}/${partitionCount}: ${info.processed.toLocaleString()} / ${info.total.toLocaleString()} (${percent}%)`,\n )\n }\n\n const processed = await Promise.all(\n partitionTargets.map(async (part, idx) => {\n const label = partitionTargets.length > 1 ? ` [partition ${part + 1}/${partitionCount}]` : ''\n if (partitionTargets.length === 1) {\n console.log(` -> processing${label}`)\n } else if (verbose && idx === 0) {\n console.log(` -> processing partitions in parallel (count=${partitionTargets.length})`)\n }\n\n const partitionContainer = await createRequestContainer()\n const partitionEm = partitionContainer.resolve<EntityManager>('em')\n try {\n let progressBar: CliProgressBar | null = null\n const useBar = partitionTargets.length === 1\n const stats = await reindexEntity(partitionEm, {\n entityType,\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n force,\n batchSize,\n eventBus: baseEventBus ?? undefined,\n emitVectorizeEvents: true,\n partitionCount,\n partitionIndex: part,\n resetCoverage: shouldResetCoverage(part),\n onProgress(info) {\n if (useBar) {\n if (info.total > 0 && !progressBar) {\n progressBar = createProgressBar(`Reindexing ${entityType}${label}`, info.total)\n }\n progressBar?.update(info.processed)\n } else if (groupedProgress) {\n groupedProgress.onProgress(part, info)\n } else {\n renderProgress(part, info)\n }\n },\n })\n if (progressBar) (progressBar as CliProgressBar).complete()\n if (!useBar && groupedProgress) {\n groupedProgress.onProgress(part, { processed: stats.processed, total: stats.total })\n } else if (!useBar) {\n renderProgress(part, { processed: stats.processed, total: stats.total })\n } else {\n console.log(\n ` processed ${stats.processed} row(s)${stats.total ? ` (base ${stats.total})` : ''}`,\n )\n }\n return stats.processed\n } finally {\n if (typeof (partitionContainer as any)?.dispose === 'function') {\n await (partitionContainer as any).dispose()\n }\n }\n }),\n )\n\n groupedProgress?.complete()\n const totalProcessed = processed.reduce((acc, value) => acc + value, 0)\n console.log(`Finished ${entityType}: processed ${totalProcessed} row(s) across ${partitionTargets.length} partition(s)`)\n await recordIndexerLog(\n { em: baseEm ?? undefined },\n {\n source: 'vector',\n handler: 'cli:search.reindex',\n message: `Reindex completed for ${entityType}`,\n entityType,\n tenantId: tenantId ?? null,\n organizationId: organizationId ?? null,\n details: {\n processed: totalProcessed,\n partitions: partitionTargets.length,\n partitionCount,\n partitionIndex: partitionIndexOption ?? null,\n batchSize,\n },\n },\n ).catch(() => undefined)\n return totalProcessed\n }\n\n const defaultPurge = purgeFlag === true && !skipPurgeFlag\n\n if (entityId) {\n if (!enabledEntities.has(entityId)) {\n console.error(`Entity ${entityId} is not enabled for vector search.`)\n return\n }\n const purgeFirst = defaultPurge\n await runReindex(entityId, purgeFirst)\n console.log('Vector reindex completed.')\n return\n }\n\n const entityIds = searchIndexer.listEnabledEntities()\n if (!entityIds.length) {\n console.log('No entities enabled for vector search.')\n return\n }\n console.log(`Reindexing ${entityIds.length} vector-enabled entities...`)\n let processedOverall = 0\n for (let idx = 0; idx < entityIds.length; idx += 1) {\n const id = entityIds[idx]!\n console.log(`[${idx + 1}/${entityIds.length}] Preparing ${id}...`)\n processedOverall += await runReindex(id, defaultPurge)\n }\n console.log(`Vector reindex completed. Total processed rows: ${processedOverall.toLocaleString()}`)\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n console.error('[search.cli] Reindex failed:', err.stack ?? err.message)\n await recordError(err)\n throw err\n } finally {\n await disposeContainer()\n }\n}\n\nconst reindexCli: ModuleCli = {\n command: 'reindex',\n async run(rest) {\n await reindexCommand(rest)\n },\n}\n\nconst reindexHelpCli: ModuleCli = {\n command: 'reindex-help',\n async run() {\n console.log('Usage: yarn mercato search reindex [options]')\n console.log('')\n console.log(' Rebuilds the query_index projection (entity_indexes) + the Postgres search_tokens')\n console.log(' index, and enqueues vector embedding jobs. It does NOT populate the fulltext')\n console.log(' (Meilisearch) index \u2014 to rebuild fulltext use POST /api/search/reindex (feature')\n console.log(' search.reindex) with a running `yarn mercato search worker fulltext-indexing`.')\n console.log('')\n console.log(' --tenant <id> Optional tenant scope (required for purge & coverage).')\n console.log(' --org <id> Optional organization scope (requires tenant).')\n console.log(' --entity <module:entity> Reindex a single entity (defaults to all enabled entities).')\n console.log(' --partitions <n> Number of partitions to process in parallel (default from query index).')\n console.log(' --partition <idx> Restrict to a specific partition index.')\n console.log(' --batch <n> Override batch size per chunk.')\n console.log(' --force Force reindex even if another job is running.')\n console.log(' --purgeFirst Purge vector rows before reindexing (defaults to skip).')\n console.log(' --skipPurge Explicitly skip purging vector rows.')\n console.log(' --skipResetCoverage Keep existing coverage snapshots.')\n },\n}\n\n/**\n * Start a queue worker for processing search indexing jobs.\n */\nasync function workerCommand(rest: string[]): Promise<void> {\n const queueName = rest[0]\n const args = parseArgs(rest)\n const concurrency = toPositiveInt(numberOpt(args, 'concurrency')) ?? 1\n\n const validQueues = [VECTOR_INDEXING_QUEUE_NAME, FULLTEXT_INDEXING_QUEUE_NAME]\n\n if (!queueName || !validQueues.includes(queueName)) {\n console.error('\\nUsage: yarn mercato search worker <queue-name> [options]\\n')\n console.error('Available queues:')\n console.error(` ${VECTOR_INDEXING_QUEUE_NAME} Process vector embedding indexing jobs`)\n console.error(` ${FULLTEXT_INDEXING_QUEUE_NAME} Process fulltext indexing jobs`)\n console.error('\\nOptions:')\n console.error(' --concurrency <n> Number of concurrent jobs to process (default: 1)')\n console.error('\\nExamples:')\n console.error(` yarn mercato search worker ${VECTOR_INDEXING_QUEUE_NAME} --concurrency=10`)\n console.error(` yarn mercato search worker ${FULLTEXT_INDEXING_QUEUE_NAME} --concurrency=5`)\n return\n }\n\n // Check if Redis is configured for async queue\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n if (queueStrategy !== 'async') {\n console.error('\\nError: Queue workers require QUEUE_STRATEGY=async')\n console.error('Set QUEUE_STRATEGY=async and configure REDIS_URL in your environment.\\n')\n return\n }\n\n const redisUrl = getRedisUrlOrThrow('QUEUE')\n\n // Dynamically import runWorker to avoid loading BullMQ unless needed\n const { runWorker } = await import('@open-mercato/queue/worker')\n\n console.log(`\\nStarting ${queueName} worker...`)\n console.log(` Concurrency: ${concurrency}`)\n console.log(` Redis: ${redisUrl.replace(/\\/\\/[^:]+:[^@]+@/, '//<credentials>@')}`)\n console.log('')\n\n if (queueName === VECTOR_INDEXING_QUEUE_NAME) {\n const { handleVectorIndexJob } = await import('./workers/vector-index.worker')\n const container = await createRequestContainer()\n\n await runWorker<VectorIndexJobPayload>({\n queueName: VECTOR_INDEXING_QUEUE_NAME,\n handler: async (job: QueuedJob<VectorIndexJobPayload>, ctx: JobContext) => {\n await handleVectorIndexJob(job, ctx, { resolve: container.resolve.bind(container) })\n },\n connection: { url: redisUrl },\n concurrency,\n })\n } else if (queueName === FULLTEXT_INDEXING_QUEUE_NAME) {\n const { handleFulltextIndexJob } = await import('./workers/fulltext-index.worker')\n const container = await createRequestContainer()\n\n await runWorker<FulltextIndexJobPayload>({\n queueName: FULLTEXT_INDEXING_QUEUE_NAME,\n handler: async (job: QueuedJob<FulltextIndexJobPayload>, ctx: JobContext) => {\n await handleFulltextIndexJob(job, ctx, { resolve: container.resolve.bind(container) })\n },\n connection: { url: redisUrl },\n concurrency,\n })\n }\n}\n\nconst workerCli: ModuleCli = {\n command: 'worker',\n async run(rest) {\n await workerCommand(rest)\n },\n}\n\nconst helpCli: ModuleCli = {\n command: 'help',\n async run() {\n console.log('\\nUsage: yarn mercato search <command> [options]\\n')\n console.log('Commands:')\n console.log(' status Show search module status and available strategies')\n console.log(' query Execute a search query')\n console.log(' index Index a specific record')\n console.log(' reindex Rebuild query_index projections + search_tokens and enqueue vector embeddings')\n console.log(' (does NOT populate the fulltext/Meilisearch index \u2014 use POST /api/search/reindex)')\n console.log(' reindex-help Show reindex command options')\n console.log(' test-meilisearch Test Meilisearch connection')\n console.log(' worker Start a queue worker for search indexing')\n console.log(' help Show this help message')\n console.log('\\nExamples:')\n console.log(' yarn mercato search status')\n console.log(' yarn mercato search query --query \"john doe\" --tenant tenant-123')\n console.log(' yarn mercato search index --entity customers:customer_person_profile --record abc123 --tenant tenant-123')\n console.log(' yarn mercato search reindex --tenant tenant-123 --entity customers:customer_person_profile')\n console.log(' yarn mercato search test-meilisearch')\n console.log(` yarn mercato search worker ${VECTOR_INDEXING_QUEUE_NAME} --concurrency=10`)\n console.log(` yarn mercato search worker ${FULLTEXT_INDEXING_QUEUE_NAME} --concurrency=5`)\n },\n}\n\nexport default [searchCli, statusCli, indexCli, reindexCli, reindexHelpCli, testMeilisearchCli, workerCli, helpCli]\n"],
5
+ "mappings": "AACA,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAElC,SAAS,eAAe,kCAAkC;AAC1D,SAAS,2BAA2B;AAGpC,SAAS,kCAA8D;AACvE,SAAS,oCAAkE;AAG3E,SAAS,yBAAyB;AAWlC,SAAS,mBAA4B;AACnC,QAAM,SAAS,kBAAkB,QAAQ,IAAI,sBAAsB,EAAE;AACrE,SAAO,WAAW;AACpB;AAEA,SAAS,sBAAsB,OAAe,kBAA4B;AACxE,QAAM,SAAS,oBAAI,IAAoB;AACvC,QAAM,YAAY,oBAAI,IAAoB;AAC1C,MAAI,MAA6B;AAEjC,QAAM,YAAY,MAAM;AACtB,QAAI,QAAQ;AACZ,QAAI,OAAO;AACX,eAAW,SAAS,OAAO,OAAO,EAAG,UAAS;AAC9C,eAAW,SAAS,UAAU,OAAO,EAAG,SAAQ;AAChD,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AAEA,QAAM,aAAa,MAAM;AACvB,QAAI,IAAK;AACT,QAAI,OAAO,OAAO,iBAAiB,OAAQ;AAC3C,UAAM,EAAE,MAAM,IAAI,UAAU;AAC5B,QAAI,SAAS,EAAG;AAChB,UAAM,kBAAkB,OAAO,KAAK;AAAA,EACtC;AAEA,SAAO;AAAA,IACL,WAAW,WAAmB,MAA6B;AACzD,gBAAU,IAAI,WAAW,KAAK,SAAS;AACvC,UAAI,CAAC,OAAO,IAAI,SAAS,EAAG,QAAO,IAAI,WAAW,KAAK,KAAK;AAC5D,iBAAW;AACX,UAAI,CAAC,IAAK;AACV,YAAM,EAAE,KAAK,IAAI,UAAU;AAC3B,UAAI,OAAO,IAAI;AAAA,IACjB;AAAA,IACA,WAAW;AACT,UAAI,IAAK,KAAI,SAAS;AAAA,IACxB;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,UAAU,MAA4B;AAC7C,QAAM,OAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,OAAO,KAAK,CAAC;AACnB,QAAI,CAAC,MAAM,WAAW,IAAI,EAAG;AAC7B,UAAM,CAAC,QAAQ,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAE,MAAM,GAAG;AAClD,QAAI,CAAC,OAAQ;AACb,QAAI,aAAa,QAAW;AAC1B,WAAK,MAAM,IAAI;AAAA,IACjB,WAAW,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,CAAC,EAAG,WAAW,IAAI,GAAG;AAChE,WAAK,MAAM,IAAI,KAAK,IAAI,CAAC;AACzB,WAAK;AAAA,IACP,OAAO;AACL,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,UAAU,SAAqB,MAAoC;AAC1E,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,GAAG;AACpB,QAAI,OAAO,QAAQ,SAAU;AAC7B,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,EACjC;AACA,SAAO;AACT;AAEA,SAAS,UAAU,SAAqB,MAAoC;AAC1E,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,GAAG;AACpB,QAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,SAAS,OAAO,GAAG;AACzB,UAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,IACtC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,SAAqB,MAAqC;AACzE,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,GAAG;AACpB,QAAI,QAAQ,OAAW;AACvB,QAAI,QAAQ,KAAM,QAAO;AACzB,QAAI,QAAQ,MAAO,QAAO;AAC1B,QAAI,OAAO,QAAQ,UAAU;AAC3B,YAAM,UAAU,IAAI,KAAK;AACzB,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,SAAS,kBAAkB,OAAO;AACxC,aAAO,WAAW,OAAO,OAAO;AAAA,IAClC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,OAA+C;AACpE,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,QAAO;AAC1C,SAAO;AACT;AAEA,SAAS,iBAAiB,OAA2B,WAAW,GAAW;AACzE,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,EAAG,QAAO;AACzC,SAAO;AACT;AAKA,eAAe,cAAc,MAA+B;AAC1D,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,QAAQ,UAAU,MAAM,SAAS,GAAG;AAC1C,QAAM,WAAW,UAAU,MAAM,UAAU,UAAU;AACrD,QAAM,iBAAiB,UAAU,MAAM,OAAO,gBAAgB;AAC9D,QAAM,cAAc,UAAU,MAAM,UAAU,UAAU;AACxD,QAAM,aAAa,UAAU,MAAM,YAAY,YAAY;AAC3D,QAAM,QAAQ,UAAU,MAAM,OAAO,KAAK;AAE1C,MAAI,CAAC,OAAO;AACV,YAAQ,MAAM,iFAAiF;AAC/F,YAAQ,MAAM,6CAA6C;AAC3D,YAAQ,MAAM,0CAA0C;AACxD,YAAQ,MAAM,gDAAgD;AAC9D,YAAQ,MAAM,8DAA8D;AAC5E,YAAQ,MAAM,oFAAoF;AAClG,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AAEA,MAAI,CAAC,UAAU;AACb,YAAQ,MAAM,6BAA6B;AAC3C;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,gBAAgB,UAAU,QAAQ,eAAe;AAEvD,QAAI,CAAC,eAAe;AAClB,cAAQ,MAAM,gFAAgF;AAC9F;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,kBAAqB,KAAK,GAAG;AACzC,YAAQ,IAAI,WAAW,QAAQ,EAAE;AACjC,QAAI,eAAgB,SAAQ,IAAI,iBAAiB,cAAc,EAAE;AACjE,YAAQ,IAAI,KAAK;AAEjB,UAAM,UAAU,MAAM,cAAc,OAAO,OAAO;AAAA,MAChD;AAAA,MACA;AAAA,MACA,aAAa,aAAa,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC;AAAA,MACtD,YAAY,YAAY,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC;AAAA,MACpD;AAAA,IACF,CAAC;AAED,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,mBAAmB;AAC/B;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,QAAW,QAAQ,MAAM;AAAA,CAAe;AAEpD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,SAAS,QAAQ,CAAC;AACxB,cAAQ,IAAI,GAAG,IAAI,CAAC,MAAM,OAAO,MAAM,KAAK,OAAO,QAAQ,EAAE;AAC7D,cAAQ,IAAI,iBAAiB,OAAO,QAAQ,EAAE;AAC9C,cAAQ,IAAI,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,EAAE;AAClD,UAAI,OAAO,WAAW;AACpB,gBAAQ,IAAI,aAAa,OAAO,UAAU,KAAK,EAAE;AACjD,YAAI,OAAO,UAAU,SAAU,SAAQ,IAAI,gBAAgB,OAAO,UAAU,QAAQ,EAAE;AAAA,MACxF;AACA,UAAI,OAAO,IAAK,SAAQ,IAAI,WAAW,OAAO,GAAG,EAAE;AACnD,cAAQ,IAAI,EAAE;AAAA,IAChB;AAAA,EACF,UAAE;AACA,QAAI;AACF,YAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,YAAM,IAAI,gBAAgB,GAAG,QAAQ;AAAA,IACvC,QAAQ;AAAA,IAAC;AAAA,EACX;AACF;AAKA,eAAe,gBAA+B;AAC5C,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,gBAAgB,UAAU,QAAQ,eAAe;AACvD,UAAM,aAAa,UAAU,QAAQ,kBAAkB;AAEvD,YAAQ,IAAI,kCAAkC;AAE9C,QAAI,CAAC,eAAe;AAClB,cAAQ,IAAI,+BAA+B;AAC3C;AAAA,IACF;AAEA,YAAQ,IAAI,uBAAuB;AACnC,YAAQ,IAAI,EAAE;AAEd,QAAI,CAAC,cAAc,WAAW,WAAW,GAAG;AAC1C,cAAQ,IAAI,6BAA6B;AACzC;AAAA,IACF;AAEA,YAAQ,IAAI,aAAa;AACzB,YAAQ,IAAI,aAAa;AAEzB,eAAW,YAAY,YAAY;AACjC,YAAM,YAAY,MAAM,SAAS,cAAc,KAAK;AACpD,YAAM,SAAS,YAAY,cAAc;AACzC,YAAM,OAAO,YAAY,WAAM;AAC/B,cAAQ,IAAI,KAAK,IAAI,IAAI,SAAS,QAAQ,SAAS,EAAE,KAAK,SAAS,EAAE,GAAG;AACxE,cAAQ,IAAI,eAAe,MAAM,EAAE;AACnC,cAAQ,IAAI,iBAAiB,SAAS,YAAY,KAAK,EAAE;AACzD,cAAQ,IAAI,EAAE;AAAA,IAChB;AAGA,YAAQ,IAAI,cAAc;AAC1B,YAAQ,IAAI,cAAc;AAC1B,YAAQ,IAAI,uBAAuB,QAAQ,IAAI,oBAAoB,WAAW,EAAE;AAChF,YAAQ,IAAI,0BAA0B,QAAQ,IAAI,sBAAsB,UAAU,WAAW,EAAE;AAC/F,YAAQ,IAAI,qBAAqB,QAAQ,IAAI,iBAAiB,UAAU,WAAW,EAAE;AACrF,YAAQ,IAAI,wBAAwB,QAAQ,IAAI,qBAAqB,gBAAgB,EAAE;AACvF,YAAQ,IAAI,EAAE;AAAA,EAChB,UAAE;AACA,QAAI;AACF,YAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,YAAM,IAAI,gBAAgB,GAAG,QAAQ;AAAA,IACvC,QAAQ;AAAA,IAAC;AAAA,EACX;AACF;AAKA,eAAe,aAAa,MAA+B;AACzD,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,WAAW,UAAU,MAAM,UAAU,UAAU;AACrD,QAAM,WAAW,UAAU,MAAM,UAAU,UAAU;AACrD,QAAM,WAAW,UAAU,MAAM,UAAU,UAAU;AACrD,QAAM,iBAAiB,UAAU,MAAM,OAAO,gBAAgB;AAE9D,MAAI,CAAC,YAAY,CAAC,YAAY,CAAC,UAAU;AACvC,YAAQ,MAAM,8FAA8F;AAC5G,YAAQ,MAAM,yEAAyE;AACvF,YAAQ,MAAM,+BAA+B;AAC7C,YAAQ,MAAM,+BAA+B;AAC7C,YAAQ,MAAM,gDAAgD;AAC9D;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAE/C,MAAI;AACF,UAAM,gBAAgB,UAAU,QAAQ,eAAe;AAEvD,QAAI,CAAC,eAAe;AAClB,cAAQ,MAAM,qCAAqC;AACnD;AAAA,IACF;AAGA,UAAM,cAAc,UAAU,QAAQ,aAAa;AAEnD,YAAQ,IAAI;AAAA,kBAAqB,QAAQ,MAAM,QAAQ,EAAE;AAEzD,UAAM,SAAS,MAAM,YAAY,MAAM,UAAU;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,SAAS,EAAE,IAAI,SAAS;AAAA,MACxB,qBAAqB;AAAA,MACrB,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;AAAA,IAC/B,CAAC;AAED,UAAM,SAAS,OAAO,MAAM,CAAC;AAE7B,QAAI,CAAC,QAAQ;AACX,cAAQ,MAAM,yBAAyB;AACvC;AAAA,IACF;AAEA,YAAQ,IAAI,4BAA4B;AAGxC,UAAM,eAAwC,CAAC;AAC/C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,IAAI,WAAW,KAAK,KAAK,IAAI,WAAW,KAAK,GAAG;AAClD,cAAM,QAAQ,IAAI,MAAM,CAAC;AACzB,qBAAa,KAAK,IAAI;AAAA,MACxB;AAAA,IACF;AAEA,UAAM,cAAc,YAAY;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,YAAQ,IAAI,8BAA8B;AAAA,EAC5C,UAAE;AACA,QAAI;AACF,YAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,YAAM,IAAI,gBAAgB,GAAG,QAAQ;AAAA,IACvC,QAAQ;AAAA,IAAC;AAAA,EACX;AACF;AAKA,eAAe,yBAAwC;AACrD,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,SAAS,QAAQ,IAAI;AAE3B,UAAQ,IAAI,yCAAyC;AAErD,MAAI,CAAC,MAAM;AACT,YAAQ,IAAI,2BAA2B;AACvC,YAAQ,IAAI,0EAA0E;AACtF;AAAA,EACF;AAEA,UAAQ,IAAI,SAAS,IAAI,EAAE;AAC3B,UAAQ,IAAI,YAAY,SAAS,iBAAiB,WAAW,EAAE;AAC/D,UAAQ,IAAI,EAAE;AAEd,MAAI;AACF,UAAM,EAAE,YAAY,IAAI,MAAM,OAAO,aAAa;AAClD,UAAM,SAAS,IAAI,YAAY,EAAE,MAAM,OAAO,CAAC;AAE/C,YAAQ,IAAI,uBAAuB;AACnC,UAAM,SAAS,MAAM,OAAO,OAAO;AACnC,YAAQ,IAAI,WAAW,OAAO,MAAM,EAAE;AAEtC,YAAQ,IAAI,sBAAsB;AAClC,UAAM,UAAU,MAAM,OAAO,WAAW;AAExC,QAAI,QAAQ,QAAQ,WAAW,GAAG;AAChC,cAAQ,IAAI,mBAAmB;AAAA,IACjC,OAAO;AACL,cAAQ,IAAI,SAAS,QAAQ,QAAQ,MAAM,aAAa;AACxD,iBAAW,SAAS,QAAQ,SAAS;AACnC,cAAM,QAAQ,MAAM,OAAO,MAAM,MAAM,GAAG,EAAE,SAAS;AACrD,gBAAQ,IAAI,OAAO,MAAM,GAAG,KAAK,MAAM,iBAAiB,YAAY;AAAA,MACtE;AAAA,IACF;AAEA,YAAQ,IAAI,sCAAsC;AAAA,EACpD,SAAS,OAAO;AACd,YAAQ,MAAM,sBAAsB,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,EACpF;AACF;AAEA,MAAM,YAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,cAAc,IAAI;AAAA,EAC1B;AACF;AAEA,MAAM,YAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,MAAM,MAAM;AACV,UAAM,cAAc;AAAA,EACtB;AACF;AAEA,MAAM,WAAsB;AAAA,EAC1B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,aAAa,IAAI;AAAA,EACzB;AACF;AAEA,MAAM,qBAAgC;AAAA,EACpC,SAAS;AAAA,EACT,MAAM,MAAM;AACV,UAAM,uBAAuB;AAAA,EAC/B;AACF;AAEA,eAAe,8BACb,IACA,UACA,UACA,gBACe;AACf,MAAI,CAAC,MAAM,CAAC,SAAU;AACtB,MAAI;AACF,UAAM,SAAS,oBAAI,IAAY;AAC/B,WAAO,IAAI,UAAU;AACrB,QAAI,eAAgB,QAAO,IAAI,cAAc;AAC7C,eAAW,SAAS,QAAQ;AAC1B,YAAM,WAAW,UAAU,aAAa,OAAO;AAC/C,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,YAAY;AAAA,UACZ;AAAA,UACA,gBAAgB;AAAA,UAChB,aAAa;AAAA,QACf;AAAA,QACA,EAAE,aAAa,EAAE;AAAA,MACnB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,KAAK,4DAA4D,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,EACzH;AACF;AAEA,eAAe,eAAe,MAA+B;AAC3D,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,WAAW,UAAU,MAAM,UAAU,UAAU;AACrD,QAAM,iBAAiB,UAAU,MAAM,OAAO,SAAS,gBAAgB;AACvE,QAAM,WAAW,UAAU,MAAM,UAAU,UAAU;AACrD,QAAM,QAAQ,QAAQ,MAAM,SAAS,MAAM,MAAM;AACjD,QAAM,YAAY,cAAc,UAAU,MAAM,SAAS,SAAS,MAAM,CAAC;AACzE,QAAM,mBAAmB,cAAc,UAAU,MAAM,cAAc,kBAAkB,UAAU,CAAC;AAClG,QAAM,oBAAoB,UAAU,MAAM,aAAa,gBAAgB;AACvE,QAAM,uBAAuB,sBAAsB,SAAY,SAAY,iBAAiB,mBAAmB,CAAC;AAChH,QAAM,oBAAoB,QAAQ,MAAM,eAAe,MAAM;AAC7D,QAAM,wBAAwB,QAAQ,MAAM,qBAAqB,iBAAiB,MAAM;AACxF,QAAM,gBAAgB,QAAQ,MAAM,aAAa,SAAS,MAAM;AAChE,QAAM,YAAY,QAAQ,MAAM,SAAS,YAAY;AAErD,QAAM,YAAY,MAAM,uBAAuB;AAC/C,MAAI,SAA+B;AACnC,MAAI;AACF,aAAU,UAAU,QAAQ,IAAI;AAAA,EAClC,QAAQ;AACN,aAAS;AAAA,EACX;AAEA,QAAM,mBAAmB,YAAY;AACnC,QAAI,OAAQ,WAAmB,YAAY,YAAY;AACrD,YAAO,UAAkB,QAAQ;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,cAAc,OAAO,UAAiB;AAC1C,UAAM;AAAA,MACJ,EAAE,IAAI,UAAU,OAAU;AAAA,MAC1B;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,iBAAiB,WAAW,QAAQ,QAAQ,KAAK,EAAE;AAAA,QAC5D,YAAY,YAAY;AAAA,QACxB,UAAU,YAAY;AAAA,QACtB,gBAAgB,kBAAkB;AAAA,QAClC,SAAS,EAAE,OAAO,MAAM,QAAQ;AAAA,MAClC;AAAA,IACF,EAAE,MAAM,MAAM,MAAS;AACvB,UAAM;AAAA,MACJ,EAAE,IAAI,UAAU,OAAU;AAAA,MAC1B;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT;AAAA,QACA,YAAY,YAAY;AAAA,QACxB,UAAU,YAAY;AAAA,QACtB,gBAAgB,kBAAkB;AAAA,QAClC,SAAS;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,gBAAgB,UAAU,QAAuB,eAAe;AACtE,UAAM,kBAAkB,IAAI,IAAI,cAAc,oBAAoB,CAAC;AACnE,UAAM,gBAAgB,MAAM;AAC1B,UAAI;AACF,eAAO,UAAU,QAAQ,UAAU;AAAA,MAGrC,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,GAAG;AACH,QAAI,CAAC,cAAc;AACjB,cAAQ,KAAK,6HAA6H;AAAA,IAC5I;AAEA,UAAM,iBAAiB,KAAK,IAAI,GAAG,oBAAoB,0BAA0B;AACjF,QAAI,yBAAyB,UAAa,wBAAwB,gBAAgB;AAChF,cAAQ,MAAM,mBAAmB,oBAAoB,+BAA+B,cAAc,GAAG;AACrG;AAAA,IACF;AACA,UAAM,mBACJ,yBAAyB,SACrB,CAAC,oBAAoB,IACrB,MAAM,KAAK,EAAE,QAAQ,eAAe,GAAG,CAAC,GAAG,QAAQ,GAAG;AAE5D,UAAM,sBAAsB,CAAC,cAA+B;AAC1D,UAAI,kBAAmB,QAAO;AAC9B,UAAI,sBAAuB,QAAO;AAClC,UAAI,yBAAyB,OAAW,QAAO,yBAAyB;AACxE,aAAO,cAAc,iBAAiB,CAAC;AAAA,IACzC;AAEA,UAAM,aAAa,OAAO,YAAoB,eAAwB;AACpE,YAAM,aAAa,WACf,UAAU,QAAQ,GAAG,iBAAiB,SAAS,cAAc,KAAK,EAAE,KACpE;AACJ,cAAQ,IAAI,0BAA0B,UAAU,KAAK,UAAU,IAAI,aAAa,aAAa,EAAE,EAAE;AACjG,YAAM;AAAA,QACJ,EAAE,IAAI,UAAU,OAAU;AAAA,QAC1B;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,SAAS,uBAAuB,UAAU;AAAA,UAC1C;AAAA,UACA,UAAU,YAAY;AAAA,UACtB,gBAAgB,kBAAkB;AAAA,UAClC,SAAS;AAAA,YACP;AAAA,YACA,YAAY,iBAAiB;AAAA,YAC7B;AAAA,YACA,gBAAgB,wBAAwB;AAAA,YACxC;AAAA,UACF;AAAA,QACF;AAAA,MACF,EAAE,MAAM,MAAM,MAAS;AAEvB,UAAI,cAAc,UAAU;AAC1B,YAAI;AACF,kBAAQ,IAAI,4CAA4C;AACxD,gBAAM,cAAc,YAAY,EAAE,UAAU,YAAwB,UAAU,eAAe,CAAC;AAC9F,gBAAM,8BAA8B,QAAQ,YAAY,YAAY,MAAM,kBAAkB,IAAI;AAChG,cAAI,cAAc;AAChB,kBAAM,SAAS,oBAAI,IAAY;AAC/B,mBAAO,IAAI,UAAU;AACrB,gBAAI,eAAgB,QAAO,IAAI,cAAc;AAC7C,kBAAM,QAAQ;AAAA,cACZ,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,UAAU;AAChC,sBAAM,WAAW,UAAU,aAAa,OAAO;AAC/C,uBAAO,aACJ;AAAA,kBACC;AAAA,kBACA;AAAA,oBACE;AAAA,oBACA,UAAU,YAAY;AAAA,oBACtB,gBAAgB;AAAA,oBAChB,SAAS;AAAA,kBACX;AAAA,gBACF,EACC,MAAM,MAAM,MAAS;AAAA,cAC1B,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,kBAAQ,KAAK,8CAA8C,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,QACrG;AAAA,MACF,WAAW,cAAc,CAAC,UAAU;AAClC,gBAAQ,KAAK,gDAAgD;AAAA,MAC/D;AAEA,YAAM,UAAU,iBAAiB;AACjC,YAAM,gBAAgB,UAAU,oBAAI,IAA8B,IAAI;AACtE,YAAM,kBACJ,CAAC,WAAW,iBAAiB,SAAS,IAClC,sBAAsB,cAAc,UAAU,IAAI,gBAAgB,IAClE;AACN,YAAM,iBAAiB,CAAC,MAAc,SAAgC;AACpE,YAAI,CAAC,cAAe;AACpB,cAAM,QAAQ,cAAc,IAAI,IAAI,KAAK,EAAE,MAAM,EAAE;AACnD,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,MAAM,OAAO,OAAQ,KAAK,YAAY,KAAK,MAAO;AAC5D,cAAM,OAAO;AACb,sBAAc,IAAI,MAAM,KAAK;AAC7B,cAAM,UAAU,KAAK,QAAQ,KAAM,KAAK,YAAY,KAAK,QAAS,KAAK,QAAQ,CAAC,IAAI;AACpF,gBAAQ;AAAA,UACN,SAAS,UAAU,eAAe,OAAO,CAAC,IAAI,cAAc,KAAK,KAAK,UAAU,eAAe,CAAC,MAAM,KAAK,MAAM,eAAe,CAAC,KAAK,OAAO;AAAA,QAC/I;AAAA,MACF;AAEA,YAAM,YAAY,MAAM,QAAQ;AAAA,QAC9B,iBAAiB,IAAI,OAAO,MAAM,QAAQ;AACxC,gBAAM,QAAQ,iBAAiB,SAAS,IAAI,eAAe,OAAO,CAAC,IAAI,cAAc,MAAM;AAC3F,cAAI,iBAAiB,WAAW,GAAG;AACjC,oBAAQ,IAAI,kBAAkB,KAAK,EAAE;AAAA,UACvC,WAAW,WAAW,QAAQ,GAAG;AAC/B,oBAAQ,IAAI,iDAAiD,iBAAiB,MAAM,GAAG;AAAA,UACzF;AAEA,gBAAM,qBAAqB,MAAM,uBAAuB;AACxD,gBAAM,cAAc,mBAAmB,QAAuB,IAAI;AAClE,cAAI;AACF,gBAAI,cAAqC;AACzC,kBAAM,SAAS,iBAAiB,WAAW;AAC3C,kBAAM,QAAQ,MAAM,cAAc,aAAa;AAAA,cAC7C;AAAA,cACA,UAAU,YAAY;AAAA,cACtB,gBAAgB,kBAAkB;AAAA,cAClC;AAAA,cACA;AAAA,cACA,UAAU,gBAAgB;AAAA,cAC1B,qBAAqB;AAAA,cACrB;AAAA,cACA,gBAAgB;AAAA,cAChB,eAAe,oBAAoB,IAAI;AAAA,cACvC,WAAW,MAAM;AACf,oBAAI,QAAQ;AACV,sBAAI,KAAK,QAAQ,KAAK,CAAC,aAAa;AAClC,kCAAc,kBAAkB,cAAc,UAAU,GAAG,KAAK,IAAI,KAAK,KAAK;AAAA,kBAChF;AACA,+BAAa,OAAO,KAAK,SAAS;AAAA,gBACpC,WAAW,iBAAiB;AAC1B,kCAAgB,WAAW,MAAM,IAAI;AAAA,gBACvC,OAAO;AACL,iCAAe,MAAM,IAAI;AAAA,gBAC3B;AAAA,cACF;AAAA,YACF,CAAC;AACD,gBAAI,YAAa,CAAC,YAA+B,SAAS;AAC1D,gBAAI,CAAC,UAAU,iBAAiB;AAC9B,8BAAgB,WAAW,MAAM,EAAE,WAAW,MAAM,WAAW,OAAO,MAAM,MAAM,CAAC;AAAA,YACrF,WAAW,CAAC,QAAQ;AAClB,6BAAe,MAAM,EAAE,WAAW,MAAM,WAAW,OAAO,MAAM,MAAM,CAAC;AAAA,YACzE,OAAO;AACL,sBAAQ;AAAA,gBACN,kBAAkB,MAAM,SAAS,UAAU,MAAM,QAAQ,UAAU,MAAM,KAAK,MAAM,EAAE;AAAA,cACxF;AAAA,YACF;AACA,mBAAO,MAAM;AAAA,UACf,UAAE;AACA,gBAAI,OAAQ,oBAA4B,YAAY,YAAY;AAC9D,oBAAO,mBAA2B,QAAQ;AAAA,YAC5C;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAEA,uBAAiB,SAAS;AAC1B,YAAM,iBAAiB,UAAU,OAAO,CAAC,KAAK,UAAU,MAAM,OAAO,CAAC;AACtE,cAAQ,IAAI,YAAY,UAAU,eAAe,cAAc,kBAAkB,iBAAiB,MAAM,eAAe;AACvH,YAAM;AAAA,QACJ,EAAE,IAAI,UAAU,OAAU;AAAA,QAC1B;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,SAAS,yBAAyB,UAAU;AAAA,UAC5C;AAAA,UACA,UAAU,YAAY;AAAA,UACtB,gBAAgB,kBAAkB;AAAA,UAClC,SAAS;AAAA,YACP,WAAW;AAAA,YACX,YAAY,iBAAiB;AAAA,YAC7B;AAAA,YACA,gBAAgB,wBAAwB;AAAA,YACxC;AAAA,UACF;AAAA,QACF;AAAA,MACF,EAAE,MAAM,MAAM,MAAS;AACvB,aAAO;AAAA,IACT;AAEA,UAAM,eAAe,cAAc,QAAQ,CAAC;AAE5C,QAAI,UAAU;AACZ,UAAI,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AAClC,gBAAQ,MAAM,UAAU,QAAQ,oCAAoC;AACpE;AAAA,MACF;AACA,YAAM,aAAa;AACnB,YAAM,WAAW,UAAU,UAAU;AACrC,cAAQ,IAAI,2BAA2B;AACvC;AAAA,IACF;AAEA,UAAM,YAAY,cAAc,oBAAoB;AACpD,QAAI,CAAC,UAAU,QAAQ;AACrB,cAAQ,IAAI,wCAAwC;AACpD;AAAA,IACF;AACA,YAAQ,IAAI,cAAc,UAAU,MAAM,6BAA6B;AACvE,QAAI,mBAAmB;AACvB,aAAS,MAAM,GAAG,MAAM,UAAU,QAAQ,OAAO,GAAG;AAClD,YAAM,KAAK,UAAU,GAAG;AACxB,cAAQ,IAAI,IAAI,MAAM,CAAC,IAAI,UAAU,MAAM,eAAe,EAAE,KAAK;AACjE,0BAAoB,MAAM,WAAW,IAAI,YAAY;AAAA,IACvD;AACA,YAAQ,IAAI,mDAAmD,iBAAiB,eAAe,CAAC,EAAE;AAAA,EACpG,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,YAAQ,MAAM,gCAAgC,IAAI,SAAS,IAAI,OAAO;AACtE,UAAM,YAAY,GAAG;AACrB,UAAM;AAAA,EACR,UAAE;AACA,UAAM,iBAAiB;AAAA,EACzB;AACF;AAEA,MAAM,aAAwB;AAAA,EAC5B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,eAAe,IAAI;AAAA,EAC3B;AACF;AAEA,MAAM,iBAA4B;AAAA,EAChC,SAAS;AAAA,EACT,MAAM,MAAM;AACV,YAAQ,IAAI,8CAA8C;AAC1D,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,qFAAqF;AACjG,YAAQ,IAAI,gFAAgF;AAC5F,YAAQ,IAAI,wFAAmF;AAC/F,YAAQ,IAAI,kFAAkF;AAC9F,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,kFAAkF;AAC9F,YAAQ,IAAI,0EAA0E;AACtF,YAAQ,IAAI,wFAAwF;AACpG,YAAQ,IAAI,mGAAmG;AAC/G,YAAQ,IAAI,mEAAmE;AAC/E,YAAQ,IAAI,0DAA0D;AACtE,YAAQ,IAAI,yEAAyE;AACrF,YAAQ,IAAI,mFAAmF;AAC/F,YAAQ,IAAI,gEAAgE;AAC5E,YAAQ,IAAI,6DAA6D;AAAA,EAC3E;AACF;AAKA,eAAe,cAAc,MAA+B;AAC1D,QAAM,YAAY,KAAK,CAAC;AACxB,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,cAAc,cAAc,UAAU,MAAM,aAAa,CAAC,KAAK;AAErE,QAAM,cAAc,CAAC,4BAA4B,4BAA4B;AAE7E,MAAI,CAAC,aAAa,CAAC,YAAY,SAAS,SAAS,GAAG;AAClD,YAAQ,MAAM,8DAA8D;AAC5E,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,MAAM,KAAK,0BAA0B,gDAAgD;AAC7F,YAAQ,MAAM,KAAK,4BAA4B,mCAAmC;AAClF,YAAQ,MAAM,YAAY;AAC1B,YAAQ,MAAM,yEAAyE;AACvF,YAAQ,MAAM,aAAa;AAC3B,YAAQ,MAAM,gCAAgC,0BAA0B,mBAAmB;AAC3F,YAAQ,MAAM,gCAAgC,4BAA4B,kBAAkB;AAC5F;AAAA,EACF;AAGA,QAAM,gBAAgB,QAAQ,IAAI,kBAAkB;AACpD,MAAI,kBAAkB,SAAS;AAC7B,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,yEAAyE;AACvF;AAAA,EACF;AAEA,QAAM,WAAW,mBAAmB,OAAO;AAG3C,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,4BAA4B;AAE/D,UAAQ,IAAI;AAAA,WAAc,SAAS,YAAY;AAC/C,UAAQ,IAAI,kBAAkB,WAAW,EAAE;AAC3C,UAAQ,IAAI,YAAY,SAAS,QAAQ,oBAAoB,kBAAkB,CAAC,EAAE;AAClF,UAAQ,IAAI,EAAE;AAEd,MAAI,cAAc,4BAA4B;AAC5C,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,+BAA+B;AAC7E,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,UAAiC;AAAA,MACrC,WAAW;AAAA,MACX,SAAS,OAAO,KAAuC,QAAoB;AACzE,cAAM,qBAAqB,KAAK,KAAK,EAAE,SAAS,UAAU,QAAQ,KAAK,SAAS,EAAE,CAAC;AAAA,MACrF;AAAA,MACA,YAAY,EAAE,KAAK,SAAS;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH,WAAW,cAAc,8BAA8B;AACrD,UAAM,EAAE,uBAAuB,IAAI,MAAM,OAAO,iCAAiC;AACjF,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,UAAmC;AAAA,MACvC,WAAW;AAAA,MACX,SAAS,OAAO,KAAyC,QAAoB;AAC3E,cAAM,uBAAuB,KAAK,KAAK,EAAE,SAAS,UAAU,QAAQ,KAAK,SAAS,EAAE,CAAC;AAAA,MACvF;AAAA,MACA,YAAY,EAAE,KAAK,SAAS;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,MAAM,YAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,cAAc,IAAI;AAAA,EAC1B;AACF;AAEA,MAAM,UAAqB;AAAA,EACzB,SAAS;AAAA,EACT,MAAM,MAAM;AACV,YAAQ,IAAI,oDAAoD;AAChE,YAAQ,IAAI,WAAW;AACvB,YAAQ,IAAI,0EAA0E;AACtF,YAAQ,IAAI,8CAA8C;AAC1D,YAAQ,IAAI,+CAA+C;AAC3D,YAAQ,IAAI,qGAAqG;AACjH,YAAQ,IAAI,8GAAyG;AACrH,YAAQ,IAAI,oDAAoD;AAChE,YAAQ,IAAI,mDAAmD;AAC/D,YAAQ,IAAI,gEAAgE;AAC5E,YAAQ,IAAI,8CAA8C;AAC1D,YAAQ,IAAI,aAAa;AACzB,YAAQ,IAAI,8BAA8B;AAC1C,YAAQ,IAAI,oEAAoE;AAChF,YAAQ,IAAI,4GAA4G;AACxH,YAAQ,IAAI,8FAA8F;AAC1G,YAAQ,IAAI,wCAAwC;AACpD,YAAQ,IAAI,gCAAgC,0BAA0B,mBAAmB;AACzF,YAAQ,IAAI,gCAAgC,4BAA4B,kBAAkB;AAAA,EAC5F;AACF;AAEA,IAAO,cAAQ,CAAC,WAAW,WAAW,UAAU,YAAY,gBAAgB,oBAAoB,WAAW,OAAO;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/search",
3
- "version": "0.6.7-develop.6628.1.f9e4ecf70f",
3
+ "version": "0.6.7-develop.6631.1.9d64b49ddc",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -127,9 +127,9 @@
127
127
  "zod": "^4.4.3"
128
128
  },
129
129
  "peerDependencies": {
130
- "@open-mercato/core": "0.6.7-develop.6628.1.f9e4ecf70f",
131
- "@open-mercato/queue": "0.6.7-develop.6628.1.f9e4ecf70f",
132
- "@open-mercato/shared": "0.6.7-develop.6628.1.f9e4ecf70f"
130
+ "@open-mercato/core": "0.6.7-develop.6631.1.9d64b49ddc",
131
+ "@open-mercato/queue": "0.6.7-develop.6631.1.9d64b49ddc",
132
+ "@open-mercato/shared": "0.6.7-develop.6631.1.9d64b49ddc"
133
133
  },
134
134
  "devDependencies": {
135
135
  "@types/jest": "^30.0.0",
@@ -456,6 +456,11 @@ yarn mercato search worker fulltext-indexing --concurrency=5
456
456
  | `OPENAI_API_KEY` | OpenAI API key for embeddings | - |
457
457
  | `OM_SEARCH_ENABLED` | Enable/disable search module | `true` |
458
458
  | `OM_SEARCH_DEBUG` | Enable debug logging for search module | `false` |
459
+ | `OM_SEARCH_MIN_LEN` | Minimum token length for the Postgres `search_tokens` index; also the floor of prefix expansion (token strategy only) | `3` |
460
+ | `OM_SEARCH_ENABLE_PARTIAL` | Prefix/partial expansion for `search_tokens` (indexing "john" stores hashes for `joh`,`john`). Token/Postgres only — Meilisearch unaffected. Increases `search_tokens` size ~5–6× | `true` |
461
+ | `OM_SEARCH_HASH_ALGO` | Hash algorithm for `search_tokens` tokens (`sha256`/`sha1`/`md5`); token strategy only | `sha256` |
462
+ | `OM_SEARCH_STORE_RAW_TOKENS` | Store plaintext token alongside the hash in `search_tokens` — **security-sensitive** (retains plaintext of otherwise-hashed values); token strategy only | `false` |
463
+ | `OM_SEARCH_FIELD_BLOCKLIST` | Comma-separated extra field names excluded from tokenization (merged with built-in `password,token,secret,hash`); token strategy only | - |
459
464
  | `SEARCH_EXCLUDE_ENCRYPTED_FIELDS` | Exclude encrypted fields from Meilisearch indexing | `false` |
460
465
  | `QUEUE_STRATEGY` | Queue strategy (`local` or `async`) | `local` |
461
466
  | `REDIS_URL` | Redis connection URL for async queues | - |
@@ -754,6 +754,12 @@ const reindexHelpCli: ModuleCli = {
754
754
  command: 'reindex-help',
755
755
  async run() {
756
756
  console.log('Usage: yarn mercato search reindex [options]')
757
+ console.log('')
758
+ console.log(' Rebuilds the query_index projection (entity_indexes) + the Postgres search_tokens')
759
+ console.log(' index, and enqueues vector embedding jobs. It does NOT populate the fulltext')
760
+ console.log(' (Meilisearch) index — to rebuild fulltext use POST /api/search/reindex (feature')
761
+ console.log(' search.reindex) with a running `yarn mercato search worker fulltext-indexing`.')
762
+ console.log('')
757
763
  console.log(' --tenant <id> Optional tenant scope (required for purge & coverage).')
758
764
  console.log(' --org <id> Optional organization scope (requires tenant).')
759
765
  console.log(' --entity <module:entity> Reindex a single entity (defaults to all enabled entities).')
@@ -850,7 +856,8 @@ const helpCli: ModuleCli = {
850
856
  console.log(' status Show search module status and available strategies')
851
857
  console.log(' query Execute a search query')
852
858
  console.log(' index Index a specific record')
853
- console.log(' reindex Reindex vector embeddings for entities')
859
+ console.log(' reindex Rebuild query_index projections + search_tokens and enqueue vector embeddings')
860
+ console.log(' (does NOT populate the fulltext/Meilisearch index — use POST /api/search/reindex)')
854
861
  console.log(' reindex-help Show reindex command options')
855
862
  console.log(' test-meilisearch Test Meilisearch connection')
856
863
  console.log(' worker Start a queue worker for search indexing')