@open-mercato/search 0.6.7-develop.6785.1.1dd7cfac55 → 0.6.7-develop.6795.1.8a3f27921c
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -0
- package/dist/lib/presenter-enricher.js +12 -12
- package/dist/lib/presenter-enricher.js.map +2 -2
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js +2 -9
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +2 -2
- package/dist/modules/search/frontend/components/HybridSearchTable.js +2 -11
- package/dist/modules/search/frontend/components/HybridSearchTable.js.map +2 -2
- package/dist/modules/search/frontend/components/TopbarSearchInline.js +2 -9
- package/dist/modules/search/frontend/components/TopbarSearchInline.js.map +2 -2
- package/dist/modules/search/frontend/lib/entityTypeLabel.js +19 -0
- package/dist/modules/search/frontend/lib/entityTypeLabel.js.map +7 -0
- package/dist/modules/search/i18n/de.json +47 -0
- package/dist/modules/search/i18n/en.json +47 -0
- package/dist/modules/search/i18n/es.json +47 -0
- package/dist/modules/search/i18n/pl.json +47 -0
- package/dist/service.js +2 -5
- package/dist/service.js.map +2 -2
- package/package.json +9 -9
- package/src/__tests__/presenter-enricher.test.ts +48 -0
- package/src/__tests__/presenter-locale.test.ts +76 -0
- package/src/lib/presenter-enricher.ts +14 -14
- package/src/modules/search/api/__tests__/global-search.routes.test.ts +167 -0
- package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +2 -15
- package/src/modules/search/frontend/components/HybridSearchTable.tsx +2 -17
- package/src/modules/search/frontend/components/TopbarSearchInline.tsx +2 -15
- package/src/modules/search/frontend/lib/__tests__/entityTypeLabel.test.ts +25 -0
- package/src/modules/search/frontend/lib/entityTypeLabel.ts +22 -0
- package/src/modules/search/i18n/de.json +47 -0
- package/src/modules/search/i18n/en.json +47 -0
- package/src/modules/search/i18n/es.json +47 -0
- package/src/modules/search/i18n/pl.json +47 -0
- package/src/service.ts +2 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/search/frontend/components/TopbarSearchInline.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport NextLink from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport {\n Search,\n Loader2,\n Zap,\n User,\n Users,\n Building,\n StickyNote,\n Briefcase,\n CheckSquare,\n FileText,\n Mail,\n Phone,\n Calendar,\n Clock,\n Star,\n Tag,\n Flag,\n Heart,\n Bookmark,\n Package,\n Truck,\n ShoppingCart,\n CreditCard,\n DollarSign,\n Target,\n Award,\n Trophy,\n Rocket,\n Lightbulb,\n MessageSquare,\n Bell,\n Settings,\n Globe,\n MapPin,\n Link as LinkIcon,\n Folder,\n Database,\n Activity,\n X,\n} from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { SearchResult, SearchResultLink } from '@open-mercato/shared/modules/search'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { ForbiddenError } from '@open-mercato/ui/backend/utils/api'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { resolveSearchMinTokenLength } from '@open-mercato/shared/lib/search/config'\nimport { fetchGlobalSearchResults } from '../utils'\n\nconst MIN_QUERY_LENGTH = resolveSearchMinTokenLength()\n\nfunction normalizeLinks(links?: SearchResultLink[] | null): SearchResultLink[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string')\n}\n\nfunction pickPrimaryLink(result: SearchResult): string | null {\n if (result.url) return result.url\n const links = normalizeLinks(result.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue)\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n bolt: Zap,\n zap: Zap,\n user: User,\n users: Users,\n building: Building,\n 'sticky-note': StickyNote,\n briefcase: Briefcase,\n 'check-square': CheckSquare,\n 'file-text': FileText,\n mail: Mail,\n phone: Phone,\n calendar: Calendar,\n clock: Clock,\n star: Star,\n tag: Tag,\n flag: Flag,\n heart: Heart,\n bookmark: Bookmark,\n package: Package,\n truck: Truck,\n 'shopping-cart': ShoppingCart,\n 'credit-card': CreditCard,\n 'dollar-sign': DollarSign,\n target: Target,\n award: Award,\n trophy: Trophy,\n rocket: Rocket,\n lightbulb: Lightbulb,\n 'message-square': MessageSquare,\n bell: Bell,\n settings: Settings,\n globe: Globe,\n 'map-pin': MapPin,\n link: LinkIcon,\n folder: Folder,\n database: Database,\n activity: Activity,\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n return ICON_MAP[name.toLowerCase()] ?? null\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n return `${humanizeSegment(module)} \u00B7 ${humanizeSegment(entity)}`\n}\n\nexport type TopbarSearchInlineProps = {\n /** Whether embedding provider is configured for vector search */\n embeddingConfigured: boolean\n /** Warning text to show when vector search is enabled but not configured */\n missingConfigMessage: string\n}\n\nexport function TopbarSearchInline({\n embeddingConfigured,\n missingConfigMessage,\n}: TopbarSearchInlineProps) {\n const router = useRouter()\n const t = useT()\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<SearchResult[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [open, setOpen] = React.useState(false)\n const [expanded, setExpanded] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n const [anchor, setAnchor] = React.useState<{ top: number; left: number; width: number } | null>(null)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n const popoverRef = React.useRef<HTMLDivElement | null>(null)\n const containerRef = React.useRef<HTMLElement | null>(null)\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n\n const expandAndFocus = React.useCallback(() => {\n setExpanded(true)\n // Wait one tick so the input is mounted/transitioned before focusing\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n inputRef.current?.select()\n })\n }, [])\n\n const collapseAndReset = React.useCallback(() => {\n setOpen(false)\n setExpanded(false)\n setQuery('')\n inputRef.current?.blur()\n }, [])\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n // Cmd/Ctrl+K expands + focuses the input\n React.useEffect(() => {\n const handler = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n expandAndFocus()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [expandAndFocus])\n\n // Click outside closes the popover AND collapses input (unless user has typed something)\n React.useEffect(() => {\n if (!expanded) return\n const handler = (event: MouseEvent) => {\n const target = event.target as Node | null\n if (!target) return\n if (containerRef.current?.contains(target)) return\n if (popoverRef.current?.contains(target)) return\n collapseAndReset()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n }, [expanded, collapseAndReset])\n\n // Fetch results on query change (debounced)\n React.useEffect(() => {\n abortRef.current?.abort()\n const trimmed = query.trim()\n if (trimmed.length < MIN_QUERY_LENGTH) {\n setResults([])\n setError(null)\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n const handle = setTimeout(async () => {\n try {\n const data = await fetchGlobalSearchResults(query, {\n limit: 10,\n signal: controller.signal,\n })\n setResults(data.results)\n setError(data.error ?? null)\n setSelectedIndex(0)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n const abortError = err as { name?: string }\n if (abortError?.name === 'AbortError') return\n if (err instanceof ForbiddenError) {\n setError(t('search.dialog.errors.noPermission'))\n } else {\n setError(err instanceof Error ? err.message : t('search.dialog.errors.searchFailed'))\n }\n setResults([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 220)\n\n return () => {\n clearTimeout(handle)\n controller.abort()\n }\n }, [query, t])\n\n // Auto-scroll active item into view\n React.useEffect(() => {\n const container = listRef.current\n const active = container?.querySelector<HTMLElement>('[data-active=\"true\"]')\n if (!container || !active) return\n const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect()\n const { top: activeTop, bottom: activeBottom } = active.getBoundingClientRect()\n if (activeTop < containerTop) {\n container.scrollTop -= containerTop - activeTop\n } else if (activeBottom > containerBottom) {\n container.scrollTop += activeBottom - containerBottom\n }\n }, [selectedIndex])\n\n const openResult = React.useCallback(\n (result: SearchResult | undefined) => {\n if (!result) return\n const href = pickPrimaryLink(result)\n if (!href) return\n router.push(href)\n collapseAndReset()\n },\n [router, collapseAndReset],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!open) setOpen(true)\n setSelectedIndex((prev) => (results.length ? (prev + 1) % results.length : 0))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => {\n if (!results.length) return 0\n return prev <= 0 ? results.length - 1 : prev - 1\n })\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n if (open) {\n setOpen(false)\n } else if (query) {\n setQuery('')\n } else {\n collapseAndReset()\n }\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n const target = results[selectedIndex]\n openResult(target)\n return\n }\n },\n [open, query, results, selectedIndex, openResult, collapseAndReset],\n )\n\n const showVectorWarning = !embeddingConfigured && !error\n const trimmed = query.trim()\n const tooShort = trimmed.length > 0 && trimmed.length < MIN_QUERY_LENGTH\n const showPopover =\n open && (loading || results.length > 0 || error !== null || tooShort || showVectorWarning)\n const showClear = query.length > 0\n\n // The results panel is portaled to <body> so it escapes the sticky header's\n // stacking context (z-sticky) \u2014 rendered inline it stays trapped at the\n // header's z-index and page content with its own z-index paints over it\n // (#3097). Anchor it under the input and keep it aligned on scroll/resize.\n React.useEffect(() => {\n if (!showPopover) return\n const update = () => {\n const el = containerRef.current\n if (!el) return\n const rect = el.getBoundingClientRect()\n const width = Math.max(rect.width, 320)\n const left = Math.min(Math.max(rect.left, 8), Math.max(window.innerWidth - width - 8, 8))\n setAnchor({ top: rect.bottom + 4, left, width })\n }\n update()\n window.addEventListener('resize', update)\n window.addEventListener('scroll', update, true)\n return () => {\n window.removeEventListener('resize', update)\n window.removeEventListener('scroll', update, true)\n }\n }, [showPopover])\n\n if (!expanded) {\n return (\n <span\n ref={(el) => {\n containerRef.current = el\n }}\n className=\"inline-flex\"\n >\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"lg\"\n onClick={expandAndFocus}\n aria-label={t('search.dialog.actions.openGlobalSearch', 'Open global search')}\n title={t('search.dialog.actions.openGlobalSearch', 'Open global search')}\n >\n <Search className=\"size-4\" aria-hidden=\"true\" />\n </IconButton>\n </span>\n )\n }\n\n return (\n <div\n ref={(el) => {\n containerRef.current = el\n }}\n data-search-expanded=\"true\"\n className=\"relative min-w-0 sm:w-[260px] md:w-[320px] max-sm:absolute max-sm:inset-x-3 max-sm:top-1/2 max-sm:-translate-y-1/2 max-sm:z-popover\"\n >\n <div\n className={cn(\n 'flex h-9 items-center gap-2 rounded-md border border-input bg-background px-3 text-sm shadow-xs transition-colors hover:bg-muted/40',\n open ? 'border-foreground bg-background shadow-focus' : '',\n )}\n onClick={() => inputRef.current?.focus()}\n >\n <Search className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n if (!open) setOpen(true)\n }}\n onFocus={() => setOpen(true)}\n onKeyDown={handleKeyDown}\n placeholder={t('search.dialog.actions.search', 'Search')}\n aria-label={t('search.dialog.actions.openGlobalSearch', 'Search')}\n aria-autocomplete=\"list\"\n aria-expanded={open}\n aria-controls=\"topbar-search-results\"\n className=\"flex-1 min-w-0 bg-transparent placeholder:text-muted-foreground/70 focus:outline-none\"\n // Inline search; no need for native browser autocomplete or autocapitalize\n autoComplete=\"off\"\n spellCheck={false}\n />\n {loading ? (\n <Loader2 className=\"size-4 shrink-0 animate-spin text-muted-foreground\" aria-hidden=\"true\" />\n ) : showClear ? (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation()\n setQuery('')\n inputRef.current?.focus()\n }}\n className=\"rounded-sm p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n aria-label={t('search.dialog.actions.clear', 'Clear search')}\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n ) : (\n <kbd className=\"hidden md:inline-flex shrink-0 items-center rounded border bg-muted/50 px-1.5 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n \u2318K\n </kbd>\n )}\n </div>\n\n {showPopover && anchor ? createPortal(\n <div\n ref={popoverRef}\n id=\"topbar-search-results\"\n role=\"listbox\"\n style={{ position: 'fixed', top: anchor.top, left: anchor.left, width: anchor.width }}\n className=\"z-popover rounded-md border bg-popover text-popover-foreground shadow-md\"\n >\n {error ? (\n <p className=\"border-b px-3 py-2 text-sm text-destructive\">{error}</p>\n ) : null}\n {showVectorWarning && !error ? (\n <div className=\"border-b bg-status-warning-bg px-3 py-2 text-xs text-status-warning-text\">\n <p>{missingConfigMessage}</p>\n <NextLink\n href=\"/backend/config/search\"\n onClick={() => collapseAndReset()}\n className=\"mt-1 inline-flex items-center font-medium underline underline-offset-2 hover:no-underline\"\n >\n {t('search.dialog.warnings.configureLink', 'Configure search settings')}\n </NextLink>\n </div>\n ) : null}\n {showScopeHint ? (\n <p className=\"border-b px-3 py-1.5 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </p>\n ) : null}\n\n {tooShort && !error ? (\n <div className=\"px-3 py-3 text-sm text-muted-foreground\">\n {t('search.dialog.empty.hint', { count: MIN_QUERY_LENGTH })}\n </div>\n ) : null}\n\n {!tooShort && results.length === 0 && !loading && !error ? (\n <div className=\"px-3 py-3 text-sm text-muted-foreground\">\n {t('search.dialog.empty.none', 'No results')}\n </div>\n ) : null}\n\n {results.length > 0 ? (\n <div ref={listRef} className=\"max-h-[380px] overflow-y-auto p-1\">\n {results.map((result, index) => {\n const presenter = result.presenter\n const isActive = index === selectedIndex\n const hasLink = pickPrimaryLink(result) !== null\n const Icon = presenter?.icon ? resolveIcon(presenter.icon) : null\n return (\n <button\n key={`${result.entityId}:${result.recordId}`}\n type=\"button\"\n data-active={isActive}\n onClick={() => openResult(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n role=\"option\"\n aria-selected={isActive}\n className={cn(\n 'flex w-full items-start gap-3 rounded-sm px-2 py-2 text-left transition-colors',\n isActive ? 'bg-muted' : 'hover:bg-muted/60',\n !hasLink && 'opacity-60',\n )}\n >\n {Icon ? (\n <span className=\"mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md border bg-background\">\n <Icon className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n </span>\n ) : (\n <span className=\"mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md border bg-background\">\n <FileText className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"flex items-center gap-2\">\n <span\n className={cn(\n 'truncate text-sm font-medium',\n !hasLink ? 'text-muted-foreground' : 'text-foreground',\n )}\n >\n {presenter?.title ?? result.recordId}\n </span>\n </span>\n <span className=\"flex items-center gap-2 text-overline text-muted-foreground\">\n <span className=\"truncate\">{formatEntityId(result.entityId)}</span>\n {presenter?.subtitle ? (\n <>\n <span aria-hidden=\"true\">\u00B7</span>\n <span className=\"truncate\">{presenter.subtitle}</span>\n </>\n ) : null}\n </span>\n </span>\n </button>\n )\n })}\n </div>\n ) : null}\n </div>,\n document.body,\n ) : null}\n </div>\n )\n}\n\nexport default TopbarSearchInline\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport NextLink from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport {\n Search,\n Loader2,\n Zap,\n User,\n Users,\n Building,\n StickyNote,\n Briefcase,\n CheckSquare,\n FileText,\n Mail,\n Phone,\n Calendar,\n Clock,\n Star,\n Tag,\n Flag,\n Heart,\n Bookmark,\n Package,\n Truck,\n ShoppingCart,\n CreditCard,\n DollarSign,\n Target,\n Award,\n Trophy,\n Rocket,\n Lightbulb,\n MessageSquare,\n Bell,\n Settings,\n Globe,\n MapPin,\n Link as LinkIcon,\n Folder,\n Database,\n Activity,\n X,\n} from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { SearchResult, SearchResultLink } from '@open-mercato/shared/modules/search'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { resolveEntityTypeLabel } from '../lib/entityTypeLabel'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { ForbiddenError } from '@open-mercato/ui/backend/utils/api'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { resolveSearchMinTokenLength } from '@open-mercato/shared/lib/search/config'\nimport { fetchGlobalSearchResults } from '../utils'\n\nconst MIN_QUERY_LENGTH = resolveSearchMinTokenLength()\n\nfunction normalizeLinks(links?: SearchResultLink[] | null): SearchResultLink[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string')\n}\n\nfunction pickPrimaryLink(result: SearchResult): string | null {\n if (result.url) return result.url\n const links = normalizeLinks(result.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue)\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n bolt: Zap,\n zap: Zap,\n user: User,\n users: Users,\n building: Building,\n 'sticky-note': StickyNote,\n briefcase: Briefcase,\n 'check-square': CheckSquare,\n 'file-text': FileText,\n mail: Mail,\n phone: Phone,\n calendar: Calendar,\n clock: Clock,\n star: Star,\n tag: Tag,\n flag: Flag,\n heart: Heart,\n bookmark: Bookmark,\n package: Package,\n truck: Truck,\n 'shopping-cart': ShoppingCart,\n 'credit-card': CreditCard,\n 'dollar-sign': DollarSign,\n target: Target,\n award: Award,\n trophy: Trophy,\n rocket: Rocket,\n lightbulb: Lightbulb,\n 'message-square': MessageSquare,\n bell: Bell,\n settings: Settings,\n globe: Globe,\n 'map-pin': MapPin,\n link: LinkIcon,\n folder: Folder,\n database: Database,\n activity: Activity,\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n return ICON_MAP[name.toLowerCase()] ?? null\n}\n\nexport type TopbarSearchInlineProps = {\n /** Whether embedding provider is configured for vector search */\n embeddingConfigured: boolean\n /** Warning text to show when vector search is enabled but not configured */\n missingConfigMessage: string\n}\n\nexport function TopbarSearchInline({\n embeddingConfigured,\n missingConfigMessage,\n}: TopbarSearchInlineProps) {\n const router = useRouter()\n const t = useT()\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<SearchResult[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [open, setOpen] = React.useState(false)\n const [expanded, setExpanded] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n const [anchor, setAnchor] = React.useState<{ top: number; left: number; width: number } | null>(null)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n const popoverRef = React.useRef<HTMLDivElement | null>(null)\n const containerRef = React.useRef<HTMLElement | null>(null)\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n\n const expandAndFocus = React.useCallback(() => {\n setExpanded(true)\n // Wait one tick so the input is mounted/transitioned before focusing\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n inputRef.current?.select()\n })\n }, [])\n\n const collapseAndReset = React.useCallback(() => {\n setOpen(false)\n setExpanded(false)\n setQuery('')\n inputRef.current?.blur()\n }, [])\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n // Cmd/Ctrl+K expands + focuses the input\n React.useEffect(() => {\n const handler = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n expandAndFocus()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [expandAndFocus])\n\n // Click outside closes the popover AND collapses input (unless user has typed something)\n React.useEffect(() => {\n if (!expanded) return\n const handler = (event: MouseEvent) => {\n const target = event.target as Node | null\n if (!target) return\n if (containerRef.current?.contains(target)) return\n if (popoverRef.current?.contains(target)) return\n collapseAndReset()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n }, [expanded, collapseAndReset])\n\n // Fetch results on query change (debounced)\n React.useEffect(() => {\n abortRef.current?.abort()\n const trimmed = query.trim()\n if (trimmed.length < MIN_QUERY_LENGTH) {\n setResults([])\n setError(null)\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n const handle = setTimeout(async () => {\n try {\n const data = await fetchGlobalSearchResults(query, {\n limit: 10,\n signal: controller.signal,\n })\n setResults(data.results)\n setError(data.error ?? null)\n setSelectedIndex(0)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n const abortError = err as { name?: string }\n if (abortError?.name === 'AbortError') return\n if (err instanceof ForbiddenError) {\n setError(t('search.dialog.errors.noPermission'))\n } else {\n setError(err instanceof Error ? err.message : t('search.dialog.errors.searchFailed'))\n }\n setResults([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 220)\n\n return () => {\n clearTimeout(handle)\n controller.abort()\n }\n }, [query, t])\n\n // Auto-scroll active item into view\n React.useEffect(() => {\n const container = listRef.current\n const active = container?.querySelector<HTMLElement>('[data-active=\"true\"]')\n if (!container || !active) return\n const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect()\n const { top: activeTop, bottom: activeBottom } = active.getBoundingClientRect()\n if (activeTop < containerTop) {\n container.scrollTop -= containerTop - activeTop\n } else if (activeBottom > containerBottom) {\n container.scrollTop += activeBottom - containerBottom\n }\n }, [selectedIndex])\n\n const openResult = React.useCallback(\n (result: SearchResult | undefined) => {\n if (!result) return\n const href = pickPrimaryLink(result)\n if (!href) return\n router.push(href)\n collapseAndReset()\n },\n [router, collapseAndReset],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!open) setOpen(true)\n setSelectedIndex((prev) => (results.length ? (prev + 1) % results.length : 0))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => {\n if (!results.length) return 0\n return prev <= 0 ? results.length - 1 : prev - 1\n })\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n if (open) {\n setOpen(false)\n } else if (query) {\n setQuery('')\n } else {\n collapseAndReset()\n }\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n const target = results[selectedIndex]\n openResult(target)\n return\n }\n },\n [open, query, results, selectedIndex, openResult, collapseAndReset],\n )\n\n const showVectorWarning = !embeddingConfigured && !error\n const trimmed = query.trim()\n const tooShort = trimmed.length > 0 && trimmed.length < MIN_QUERY_LENGTH\n const showPopover =\n open && (loading || results.length > 0 || error !== null || tooShort || showVectorWarning)\n const showClear = query.length > 0\n\n // The results panel is portaled to <body> so it escapes the sticky header's\n // stacking context (z-sticky) \u2014 rendered inline it stays trapped at the\n // header's z-index and page content with its own z-index paints over it\n // (#3097). Anchor it under the input and keep it aligned on scroll/resize.\n React.useEffect(() => {\n if (!showPopover) return\n const update = () => {\n const el = containerRef.current\n if (!el) return\n const rect = el.getBoundingClientRect()\n const width = Math.max(rect.width, 320)\n const left = Math.min(Math.max(rect.left, 8), Math.max(window.innerWidth - width - 8, 8))\n setAnchor({ top: rect.bottom + 4, left, width })\n }\n update()\n window.addEventListener('resize', update)\n window.addEventListener('scroll', update, true)\n return () => {\n window.removeEventListener('resize', update)\n window.removeEventListener('scroll', update, true)\n }\n }, [showPopover])\n\n if (!expanded) {\n return (\n <span\n ref={(el) => {\n containerRef.current = el\n }}\n className=\"inline-flex\"\n >\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"lg\"\n onClick={expandAndFocus}\n aria-label={t('search.dialog.actions.openGlobalSearch', 'Open global search')}\n title={t('search.dialog.actions.openGlobalSearch', 'Open global search')}\n >\n <Search className=\"size-4\" aria-hidden=\"true\" />\n </IconButton>\n </span>\n )\n }\n\n return (\n <div\n ref={(el) => {\n containerRef.current = el\n }}\n data-search-expanded=\"true\"\n className=\"relative min-w-0 sm:w-[260px] md:w-[320px] max-sm:absolute max-sm:inset-x-3 max-sm:top-1/2 max-sm:-translate-y-1/2 max-sm:z-popover\"\n >\n <div\n className={cn(\n 'flex h-9 items-center gap-2 rounded-md border border-input bg-background px-3 text-sm shadow-xs transition-colors hover:bg-muted/40',\n open ? 'border-foreground bg-background shadow-focus' : '',\n )}\n onClick={() => inputRef.current?.focus()}\n >\n <Search className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n if (!open) setOpen(true)\n }}\n onFocus={() => setOpen(true)}\n onKeyDown={handleKeyDown}\n placeholder={t('search.dialog.actions.search', 'Search')}\n aria-label={t('search.dialog.actions.openGlobalSearch', 'Search')}\n aria-autocomplete=\"list\"\n aria-expanded={open}\n aria-controls=\"topbar-search-results\"\n className=\"flex-1 min-w-0 bg-transparent placeholder:text-muted-foreground/70 focus:outline-none\"\n // Inline search; no need for native browser autocomplete or autocapitalize\n autoComplete=\"off\"\n spellCheck={false}\n />\n {loading ? (\n <Loader2 className=\"size-4 shrink-0 animate-spin text-muted-foreground\" aria-hidden=\"true\" />\n ) : showClear ? (\n <button\n type=\"button\"\n onClick={(event) => {\n event.stopPropagation()\n setQuery('')\n inputRef.current?.focus()\n }}\n className=\"rounded-sm p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n aria-label={t('search.dialog.actions.clear', 'Clear search')}\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n ) : (\n <kbd className=\"hidden md:inline-flex shrink-0 items-center rounded border bg-muted/50 px-1.5 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n \u2318K\n </kbd>\n )}\n </div>\n\n {showPopover && anchor ? createPortal(\n <div\n ref={popoverRef}\n id=\"topbar-search-results\"\n role=\"listbox\"\n style={{ position: 'fixed', top: anchor.top, left: anchor.left, width: anchor.width }}\n className=\"z-popover rounded-md border bg-popover text-popover-foreground shadow-md\"\n >\n {error ? (\n <p className=\"border-b px-3 py-2 text-sm text-destructive\">{error}</p>\n ) : null}\n {showVectorWarning && !error ? (\n <div className=\"border-b bg-status-warning-bg px-3 py-2 text-xs text-status-warning-text\">\n <p>{missingConfigMessage}</p>\n <NextLink\n href=\"/backend/config/search\"\n onClick={() => collapseAndReset()}\n className=\"mt-1 inline-flex items-center font-medium underline underline-offset-2 hover:no-underline\"\n >\n {t('search.dialog.warnings.configureLink', 'Configure search settings')}\n </NextLink>\n </div>\n ) : null}\n {showScopeHint ? (\n <p className=\"border-b px-3 py-1.5 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </p>\n ) : null}\n\n {tooShort && !error ? (\n <div className=\"px-3 py-3 text-sm text-muted-foreground\">\n {t('search.dialog.empty.hint', { count: MIN_QUERY_LENGTH })}\n </div>\n ) : null}\n\n {!tooShort && results.length === 0 && !loading && !error ? (\n <div className=\"px-3 py-3 text-sm text-muted-foreground\">\n {t('search.dialog.empty.none', 'No results')}\n </div>\n ) : null}\n\n {results.length > 0 ? (\n <div ref={listRef} className=\"max-h-[380px] overflow-y-auto p-1\">\n {results.map((result, index) => {\n const presenter = result.presenter\n const isActive = index === selectedIndex\n const hasLink = pickPrimaryLink(result) !== null\n const Icon = presenter?.icon ? resolveIcon(presenter.icon) : null\n return (\n <button\n key={`${result.entityId}:${result.recordId}`}\n type=\"button\"\n data-active={isActive}\n onClick={() => openResult(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n role=\"option\"\n aria-selected={isActive}\n className={cn(\n 'flex w-full items-start gap-3 rounded-sm px-2 py-2 text-left transition-colors',\n isActive ? 'bg-muted' : 'hover:bg-muted/60',\n !hasLink && 'opacity-60',\n )}\n >\n {Icon ? (\n <span className=\"mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md border bg-background\">\n <Icon className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n </span>\n ) : (\n <span className=\"mt-0.5 inline-flex size-7 shrink-0 items-center justify-center rounded-md border bg-background\">\n <FileText className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"flex items-center gap-2\">\n <span\n className={cn(\n 'truncate text-sm font-medium',\n !hasLink ? 'text-muted-foreground' : 'text-foreground',\n )}\n >\n {presenter?.title ?? result.recordId}\n </span>\n </span>\n <span className=\"flex items-center gap-2 text-overline text-muted-foreground\">\n <span className=\"truncate\">{resolveEntityTypeLabel(t, result.entityId)}</span>\n {presenter?.subtitle ? (\n <>\n <span aria-hidden=\"true\">\u00B7</span>\n <span className=\"truncate\">{presenter.subtitle}</span>\n </>\n ) : null}\n </span>\n </span>\n </button>\n )\n })}\n </div>\n ) : null}\n </div>,\n document.body,\n ) : null}\n </div>\n )\n}\n\nexport default TopbarSearchInline\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["trimmed"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function humanizeSegment(segment) {
|
|
2
|
+
return segment.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
3
|
+
}
|
|
4
|
+
function formatEntityId(entityId) {
|
|
5
|
+
if (!entityId.includes(":")) return humanizeSegment(entityId);
|
|
6
|
+
const [module, entity] = entityId.split(":");
|
|
7
|
+
return `${humanizeSegment(module)} \xB7 ${humanizeSegment(entity)}`;
|
|
8
|
+
}
|
|
9
|
+
function resolveEntityTypeLabel(t, entityId) {
|
|
10
|
+
const fallback = formatEntityId(entityId);
|
|
11
|
+
if (!entityId.includes(":")) return fallback;
|
|
12
|
+
const [module, entity] = entityId.split(":");
|
|
13
|
+
return t(`search.entityType.${module}.${entity}`, fallback);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
formatEntityId,
|
|
17
|
+
resolveEntityTypeLabel
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=entityTypeLabel.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/search/frontend/lib/entityTypeLabel.ts"],
|
|
4
|
+
"sourcesContent": ["import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nexport function formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n return `${humanizeSegment(module)} \u00B7 ${humanizeSegment(entity)}`\n}\n\nexport function resolveEntityTypeLabel(t: TranslateFn, entityId: string): string {\n const fallback = formatEntityId(entityId)\n if (!entityId.includes(':')) return fallback\n const [module, entity] = entityId.split(':')\n return t(`search.entityType.${module}.${entity}`, fallback)\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,gBAAgB,SAAyB;AAChD,SAAO,QACJ,MAAM,OAAO,EACb,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AACb;AAEO,SAAS,eAAe,UAA0B;AACvD,MAAI,CAAC,SAAS,SAAS,GAAG,EAAG,QAAO,gBAAgB,QAAQ;AAC5D,QAAM,CAAC,QAAQ,MAAM,IAAI,SAAS,MAAM,GAAG;AAC3C,SAAO,GAAG,gBAAgB,MAAM,CAAC,SAAM,gBAAgB,MAAM,CAAC;AAChE;AAEO,SAAS,uBAAuB,GAAgB,UAA0B;AAC/E,QAAM,WAAW,eAAe,QAAQ;AACxC,MAAI,CAAC,SAAS,SAAS,GAAG,EAAG,QAAO;AACpC,QAAM,CAAC,QAAQ,MAAM,IAAI,SAAS,MAAM,GAAG;AAC3C,SAAO,EAAE,qBAAqB,MAAM,IAAI,MAAM,IAAI,QAAQ;AAC5D;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -50,6 +50,53 @@
|
|
|
50
50
|
"search.dialog.strategies.toggle": "Suchoptionen",
|
|
51
51
|
"search.dialog.title": "Globale Suche",
|
|
52
52
|
"search.dialog.warnings.configureLink": "Sucheinstellungen konfigurieren",
|
|
53
|
+
"search.entityType.catalog.catalog_offer": "Angebot",
|
|
54
|
+
"search.entityType.catalog.catalog_option_schema_template": "Optionsvorlage",
|
|
55
|
+
"search.entityType.catalog.catalog_price_kind": "Preistyp",
|
|
56
|
+
"search.entityType.catalog.catalog_product": "Produkt",
|
|
57
|
+
"search.entityType.catalog.catalog_product_category": "Kategorie",
|
|
58
|
+
"search.entityType.catalog.catalog_product_tag": "Tag",
|
|
59
|
+
"search.entityType.catalog.catalog_product_unit_conversion": "Einheitenumrechnung",
|
|
60
|
+
"search.entityType.catalog.catalog_product_variant": "Variante",
|
|
61
|
+
"search.entityType.customer_accounts.customer_role": "Kundenrolle",
|
|
62
|
+
"search.entityType.customer_accounts.customer_user": "Kundenbenutzer",
|
|
63
|
+
"search.entityType.customers.customer_activity": "Aktivität",
|
|
64
|
+
"search.entityType.customers.customer_comment": "Kommentar",
|
|
65
|
+
"search.entityType.customers.customer_company_profile": "Unternehmen",
|
|
66
|
+
"search.entityType.customers.customer_deal": "Deal",
|
|
67
|
+
"search.entityType.customers.customer_entity": "Kunde",
|
|
68
|
+
"search.entityType.customers.customer_person_profile": "Person",
|
|
69
|
+
"search.entityType.customers.customer_todo_link": "Aufgabe",
|
|
70
|
+
"search.entityType.inbox_ops.inbox_proposal": "Posteingang-Vorschlag",
|
|
71
|
+
"search.entityType.messages.message": "Nachricht",
|
|
72
|
+
"search.entityType.planner.planner_availability_rule_set": "Verfügbarkeitsregeln",
|
|
73
|
+
"search.entityType.resources.resources_resource": "Ressource",
|
|
74
|
+
"search.entityType.resources.resources_resource_type": "Ressourcentyp",
|
|
75
|
+
"search.entityType.sales.sales_channel": "Kanal",
|
|
76
|
+
"search.entityType.sales.sales_credit_memo": "Gutschrift",
|
|
77
|
+
"search.entityType.sales.sales_credit_memo_line": "Gutschriftsposition",
|
|
78
|
+
"search.entityType.sales.sales_delivery_window": "Lieferfenster",
|
|
79
|
+
"search.entityType.sales.sales_document_address": "Dokumentadresse",
|
|
80
|
+
"search.entityType.sales.sales_document_tag": "Dokument-Tag",
|
|
81
|
+
"search.entityType.sales.sales_invoice": "Rechnung",
|
|
82
|
+
"search.entityType.sales.sales_invoice_line": "Rechnungsposition",
|
|
83
|
+
"search.entityType.sales.sales_note": "Notiz",
|
|
84
|
+
"search.entityType.sales.sales_order": "Bestellung",
|
|
85
|
+
"search.entityType.sales.sales_order_adjustment": "Bestellanpassung",
|
|
86
|
+
"search.entityType.sales.sales_order_line": "Bestellposition",
|
|
87
|
+
"search.entityType.sales.sales_payment": "Zahlung",
|
|
88
|
+
"search.entityType.sales.sales_payment_allocation": "Zahlungszuordnung",
|
|
89
|
+
"search.entityType.sales.sales_payment_method": "Zahlungsmethode",
|
|
90
|
+
"search.entityType.sales.sales_quote": "Angebot",
|
|
91
|
+
"search.entityType.sales.sales_quote_adjustment": "Angebotsanpassung",
|
|
92
|
+
"search.entityType.sales.sales_quote_line": "Angebotsposition",
|
|
93
|
+
"search.entityType.sales.sales_shipment": "Sendung",
|
|
94
|
+
"search.entityType.sales.sales_shipment_item": "Sendungsposition",
|
|
95
|
+
"search.entityType.sales.sales_shipping_method": "Versandart",
|
|
96
|
+
"search.entityType.sales.sales_tax_rate": "Steuersatz",
|
|
97
|
+
"search.entityType.staff.staff_team": "Team",
|
|
98
|
+
"search.entityType.staff.staff_team_member": "Teammitglied",
|
|
99
|
+
"search.entityType.staff.staff_team_role": "Teamrolle",
|
|
53
100
|
"search.messages.missingConfig": "Die Suche erfordert die Konfiguration eines Embedding-Anbieters für die semantische Suche.",
|
|
54
101
|
"search.nav.hybridSearch": "Suche",
|
|
55
102
|
"search.scopeHint.currentOrg": "Bereich: aktuelle Organisation",
|
|
@@ -50,6 +50,53 @@
|
|
|
50
50
|
"search.dialog.strategies.toggle": "Search options",
|
|
51
51
|
"search.dialog.title": "Global Search",
|
|
52
52
|
"search.dialog.warnings.configureLink": "Configure search settings",
|
|
53
|
+
"search.entityType.catalog.catalog_offer": "Offer",
|
|
54
|
+
"search.entityType.catalog.catalog_option_schema_template": "Option Template",
|
|
55
|
+
"search.entityType.catalog.catalog_price_kind": "Price Type",
|
|
56
|
+
"search.entityType.catalog.catalog_product": "Product",
|
|
57
|
+
"search.entityType.catalog.catalog_product_category": "Category",
|
|
58
|
+
"search.entityType.catalog.catalog_product_tag": "Tag",
|
|
59
|
+
"search.entityType.catalog.catalog_product_unit_conversion": "Unit Conversion",
|
|
60
|
+
"search.entityType.catalog.catalog_product_variant": "Variant",
|
|
61
|
+
"search.entityType.customer_accounts.customer_role": "Customer Role",
|
|
62
|
+
"search.entityType.customer_accounts.customer_user": "Customer User",
|
|
63
|
+
"search.entityType.customers.customer_activity": "Activity",
|
|
64
|
+
"search.entityType.customers.customer_comment": "Comment",
|
|
65
|
+
"search.entityType.customers.customer_company_profile": "Company",
|
|
66
|
+
"search.entityType.customers.customer_deal": "Deal",
|
|
67
|
+
"search.entityType.customers.customer_entity": "Customer",
|
|
68
|
+
"search.entityType.customers.customer_person_profile": "Person",
|
|
69
|
+
"search.entityType.customers.customer_todo_link": "Task",
|
|
70
|
+
"search.entityType.inbox_ops.inbox_proposal": "Inbox Proposal",
|
|
71
|
+
"search.entityType.messages.message": "Message",
|
|
72
|
+
"search.entityType.planner.planner_availability_rule_set": "Availability Rules",
|
|
73
|
+
"search.entityType.resources.resources_resource": "Resource",
|
|
74
|
+
"search.entityType.resources.resources_resource_type": "Resource Type",
|
|
75
|
+
"search.entityType.sales.sales_channel": "Channel",
|
|
76
|
+
"search.entityType.sales.sales_credit_memo": "Credit Memo",
|
|
77
|
+
"search.entityType.sales.sales_credit_memo_line": "Credit Memo Line",
|
|
78
|
+
"search.entityType.sales.sales_delivery_window": "Delivery Window",
|
|
79
|
+
"search.entityType.sales.sales_document_address": "Document Address",
|
|
80
|
+
"search.entityType.sales.sales_document_tag": "Document Tag",
|
|
81
|
+
"search.entityType.sales.sales_invoice": "Invoice",
|
|
82
|
+
"search.entityType.sales.sales_invoice_line": "Invoice Line",
|
|
83
|
+
"search.entityType.sales.sales_note": "Note",
|
|
84
|
+
"search.entityType.sales.sales_order": "Order",
|
|
85
|
+
"search.entityType.sales.sales_order_adjustment": "Order Adjustment",
|
|
86
|
+
"search.entityType.sales.sales_order_line": "Order Line",
|
|
87
|
+
"search.entityType.sales.sales_payment": "Payment",
|
|
88
|
+
"search.entityType.sales.sales_payment_allocation": "Payment Allocation",
|
|
89
|
+
"search.entityType.sales.sales_payment_method": "Payment Method",
|
|
90
|
+
"search.entityType.sales.sales_quote": "Quote",
|
|
91
|
+
"search.entityType.sales.sales_quote_adjustment": "Quote Adjustment",
|
|
92
|
+
"search.entityType.sales.sales_quote_line": "Quote Line",
|
|
93
|
+
"search.entityType.sales.sales_shipment": "Shipment",
|
|
94
|
+
"search.entityType.sales.sales_shipment_item": "Shipment Item",
|
|
95
|
+
"search.entityType.sales.sales_shipping_method": "Shipping Method",
|
|
96
|
+
"search.entityType.sales.sales_tax_rate": "Tax Rate",
|
|
97
|
+
"search.entityType.staff.staff_team": "Team",
|
|
98
|
+
"search.entityType.staff.staff_team_member": "Team Member",
|
|
99
|
+
"search.entityType.staff.staff_team_role": "Team Role",
|
|
53
100
|
"search.messages.missingConfig": "Search requires configuring an embedding provider for semantic search.",
|
|
54
101
|
"search.nav.hybridSearch": "Search",
|
|
55
102
|
"search.scopeHint.currentOrg": "Scoped to current organization",
|
|
@@ -50,6 +50,53 @@
|
|
|
50
50
|
"search.dialog.strategies.toggle": "Opciones de búsqueda",
|
|
51
51
|
"search.dialog.title": "Búsqueda Global",
|
|
52
52
|
"search.dialog.warnings.configureLink": "Configurar búsqueda",
|
|
53
|
+
"search.entityType.catalog.catalog_offer": "Oferta",
|
|
54
|
+
"search.entityType.catalog.catalog_option_schema_template": "Plantilla de opciones",
|
|
55
|
+
"search.entityType.catalog.catalog_price_kind": "Tipo de precio",
|
|
56
|
+
"search.entityType.catalog.catalog_product": "Producto",
|
|
57
|
+
"search.entityType.catalog.catalog_product_category": "Categoría",
|
|
58
|
+
"search.entityType.catalog.catalog_product_tag": "Etiqueta",
|
|
59
|
+
"search.entityType.catalog.catalog_product_unit_conversion": "Conversión de unidades",
|
|
60
|
+
"search.entityType.catalog.catalog_product_variant": "Variante",
|
|
61
|
+
"search.entityType.customer_accounts.customer_role": "Rol de cliente",
|
|
62
|
+
"search.entityType.customer_accounts.customer_user": "Usuario de cliente",
|
|
63
|
+
"search.entityType.customers.customer_activity": "Actividad",
|
|
64
|
+
"search.entityType.customers.customer_comment": "Comentario",
|
|
65
|
+
"search.entityType.customers.customer_company_profile": "Empresa",
|
|
66
|
+
"search.entityType.customers.customer_deal": "Oportunidad",
|
|
67
|
+
"search.entityType.customers.customer_entity": "Cliente",
|
|
68
|
+
"search.entityType.customers.customer_person_profile": "Persona",
|
|
69
|
+
"search.entityType.customers.customer_todo_link": "Tarea",
|
|
70
|
+
"search.entityType.inbox_ops.inbox_proposal": "Propuesta de bandeja",
|
|
71
|
+
"search.entityType.messages.message": "Mensaje",
|
|
72
|
+
"search.entityType.planner.planner_availability_rule_set": "Reglas de disponibilidad",
|
|
73
|
+
"search.entityType.resources.resources_resource": "Recurso",
|
|
74
|
+
"search.entityType.resources.resources_resource_type": "Tipo de recurso",
|
|
75
|
+
"search.entityType.sales.sales_channel": "Canal",
|
|
76
|
+
"search.entityType.sales.sales_credit_memo": "Nota de crédito",
|
|
77
|
+
"search.entityType.sales.sales_credit_memo_line": "Línea de nota de crédito",
|
|
78
|
+
"search.entityType.sales.sales_delivery_window": "Ventana de entrega",
|
|
79
|
+
"search.entityType.sales.sales_document_address": "Dirección del documento",
|
|
80
|
+
"search.entityType.sales.sales_document_tag": "Etiqueta de documento",
|
|
81
|
+
"search.entityType.sales.sales_invoice": "Factura",
|
|
82
|
+
"search.entityType.sales.sales_invoice_line": "Línea de factura",
|
|
83
|
+
"search.entityType.sales.sales_note": "Nota",
|
|
84
|
+
"search.entityType.sales.sales_order": "Pedido",
|
|
85
|
+
"search.entityType.sales.sales_order_adjustment": "Ajuste de pedido",
|
|
86
|
+
"search.entityType.sales.sales_order_line": "Línea de pedido",
|
|
87
|
+
"search.entityType.sales.sales_payment": "Pago",
|
|
88
|
+
"search.entityType.sales.sales_payment_allocation": "Asignación de pago",
|
|
89
|
+
"search.entityType.sales.sales_payment_method": "Método de pago",
|
|
90
|
+
"search.entityType.sales.sales_quote": "Presupuesto",
|
|
91
|
+
"search.entityType.sales.sales_quote_adjustment": "Ajuste de presupuesto",
|
|
92
|
+
"search.entityType.sales.sales_quote_line": "Línea de presupuesto",
|
|
93
|
+
"search.entityType.sales.sales_shipment": "Envío",
|
|
94
|
+
"search.entityType.sales.sales_shipment_item": "Artículo de envío",
|
|
95
|
+
"search.entityType.sales.sales_shipping_method": "Método de envío",
|
|
96
|
+
"search.entityType.sales.sales_tax_rate": "Tasa de impuesto",
|
|
97
|
+
"search.entityType.staff.staff_team": "Equipo",
|
|
98
|
+
"search.entityType.staff.staff_team_member": "Miembro del equipo",
|
|
99
|
+
"search.entityType.staff.staff_team_role": "Rol del equipo",
|
|
53
100
|
"search.messages.missingConfig": "La búsqueda requiere configurar un proveedor de embeddings para la búsqueda semántica.",
|
|
54
101
|
"search.nav.hybridSearch": "Búsqueda",
|
|
55
102
|
"search.scopeHint.currentOrg": "Alcance: organización actual",
|
|
@@ -50,6 +50,53 @@
|
|
|
50
50
|
"search.dialog.strategies.toggle": "Opcje wyszukiwania",
|
|
51
51
|
"search.dialog.title": "Globalne Wyszukiwanie",
|
|
52
52
|
"search.dialog.warnings.configureLink": "Skonfiguruj wyszukiwanie",
|
|
53
|
+
"search.entityType.catalog.catalog_offer": "Oferta",
|
|
54
|
+
"search.entityType.catalog.catalog_option_schema_template": "Szablon opcji",
|
|
55
|
+
"search.entityType.catalog.catalog_price_kind": "Typ ceny",
|
|
56
|
+
"search.entityType.catalog.catalog_product": "Produkt",
|
|
57
|
+
"search.entityType.catalog.catalog_product_category": "Kategoria",
|
|
58
|
+
"search.entityType.catalog.catalog_product_tag": "Tag",
|
|
59
|
+
"search.entityType.catalog.catalog_product_unit_conversion": "Przelicznik jednostek",
|
|
60
|
+
"search.entityType.catalog.catalog_product_variant": "Wariant",
|
|
61
|
+
"search.entityType.customer_accounts.customer_role": "Rola klienta",
|
|
62
|
+
"search.entityType.customer_accounts.customer_user": "Użytkownik klienta",
|
|
63
|
+
"search.entityType.customers.customer_activity": "Aktywność",
|
|
64
|
+
"search.entityType.customers.customer_comment": "Komentarz",
|
|
65
|
+
"search.entityType.customers.customer_company_profile": "Firma",
|
|
66
|
+
"search.entityType.customers.customer_deal": "Szansa sprzedaży",
|
|
67
|
+
"search.entityType.customers.customer_entity": "Klient",
|
|
68
|
+
"search.entityType.customers.customer_person_profile": "Osoba",
|
|
69
|
+
"search.entityType.customers.customer_todo_link": "Zadanie",
|
|
70
|
+
"search.entityType.inbox_ops.inbox_proposal": "Propozycja skrzynki",
|
|
71
|
+
"search.entityType.messages.message": "Wiadomość",
|
|
72
|
+
"search.entityType.planner.planner_availability_rule_set": "Reguły dostępności",
|
|
73
|
+
"search.entityType.resources.resources_resource": "Zasób",
|
|
74
|
+
"search.entityType.resources.resources_resource_type": "Typ zasobu",
|
|
75
|
+
"search.entityType.sales.sales_channel": "Kanał",
|
|
76
|
+
"search.entityType.sales.sales_credit_memo": "Nota kredytowa",
|
|
77
|
+
"search.entityType.sales.sales_credit_memo_line": "Pozycja noty kredytowej",
|
|
78
|
+
"search.entityType.sales.sales_delivery_window": "Okno dostawy",
|
|
79
|
+
"search.entityType.sales.sales_document_address": "Adres dokumentu",
|
|
80
|
+
"search.entityType.sales.sales_document_tag": "Tag dokumentu",
|
|
81
|
+
"search.entityType.sales.sales_invoice": "Faktura",
|
|
82
|
+
"search.entityType.sales.sales_invoice_line": "Pozycja faktury",
|
|
83
|
+
"search.entityType.sales.sales_note": "Notatka",
|
|
84
|
+
"search.entityType.sales.sales_order": "Zamówienie",
|
|
85
|
+
"search.entityType.sales.sales_order_adjustment": "Korekta zamówienia",
|
|
86
|
+
"search.entityType.sales.sales_order_line": "Pozycja zamówienia",
|
|
87
|
+
"search.entityType.sales.sales_payment": "Płatność",
|
|
88
|
+
"search.entityType.sales.sales_payment_allocation": "Przypisanie płatności",
|
|
89
|
+
"search.entityType.sales.sales_payment_method": "Metoda płatności",
|
|
90
|
+
"search.entityType.sales.sales_quote": "Oferta",
|
|
91
|
+
"search.entityType.sales.sales_quote_adjustment": "Korekta oferty",
|
|
92
|
+
"search.entityType.sales.sales_quote_line": "Pozycja oferty",
|
|
93
|
+
"search.entityType.sales.sales_shipment": "Wysyłka",
|
|
94
|
+
"search.entityType.sales.sales_shipment_item": "Pozycja wysyłki",
|
|
95
|
+
"search.entityType.sales.sales_shipping_method": "Metoda wysyłki",
|
|
96
|
+
"search.entityType.sales.sales_tax_rate": "Stawka podatku",
|
|
97
|
+
"search.entityType.staff.staff_team": "Zespół",
|
|
98
|
+
"search.entityType.staff.staff_team_member": "Członek zespołu",
|
|
99
|
+
"search.entityType.staff.staff_team_role": "Rola w zespole",
|
|
53
100
|
"search.messages.missingConfig": "Wyszukiwanie wymaga skonfigurowania dostawcy embeddingów dla wyszukiwania semantycznego.",
|
|
54
101
|
"search.nav.hybridSearch": "Wyszukiwanie",
|
|
55
102
|
"search.scopeHint.currentOrg": "Zakres: bieżąca organizacja",
|
package/dist/service.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { mergeAndRankResults } from "./lib/merger.js";
|
|
2
2
|
import { searchError } from "./lib/debug.js";
|
|
3
|
-
import { needsSearchResultEnrichment } from "./lib/search-result-enrichment.js";
|
|
4
3
|
const DEFAULT_MERGE_CONFIG = {
|
|
5
4
|
duplicateHandling: "highest_score"
|
|
6
5
|
};
|
|
@@ -90,13 +89,11 @@ class SearchService {
|
|
|
90
89
|
return this.enrichResultsWithPresenter(scoped, options.tenantId, options.organizationId);
|
|
91
90
|
}
|
|
92
91
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
92
|
+
* Recompute configured presenters at request time and fill missing presenter
|
|
93
|
+
* or navigation data for unconfigured results.
|
|
95
94
|
*/
|
|
96
95
|
async enrichResultsWithPresenter(results, tenantId, organizationId) {
|
|
97
96
|
if (!this.presenterEnricher) return results;
|
|
98
|
-
const hasMissing = results.some(needsSearchResultEnrichment);
|
|
99
|
-
if (!hasMissing) return results;
|
|
100
97
|
try {
|
|
101
98
|
return await this.presenterEnricher(results, tenantId, organizationId);
|
|
102
99
|
} catch {
|
package/dist/service.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/service.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n SearchStrategy,\n SearchStrategyId,\n SearchOptions,\n SearchResult,\n SearchServiceOptions,\n ResultMergeConfig,\n IndexableRecord,\n PresenterEnricherFn,\n} from './types'\nimport { mergeAndRankResults } from './lib/merger'\nimport { searchError } from './lib/debug'\nimport { needsSearchResultEnrichment } from './lib/search-result-enrichment'\n\n/**\n * Default merge configuration.\n */\nconst DEFAULT_MERGE_CONFIG: ResultMergeConfig = {\n duplicateHandling: 'highest_score',\n}\n\n/**\n * Cache TTL for strategy availability checks.\n * Short window so connectivity changes (Meilisearch up/down) propagate quickly,\n * long enough to skip per-request RTT to remote backends on hot paths.\n */\nconst STRATEGY_AVAILABILITY_CACHE_TTL_MS = 2_000\n\nfunction normalizeOrganizationFilter(options: SearchOptions): string[] | null {\n const single = typeof options.organizationId === 'string' ? options.organizationId.trim() : ''\n if (single) return [single]\n if (!Array.isArray(options.organizationIds)) return null\n\n const values = Array.from(new Set(\n options.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n return values\n}\n\nfunction filterResultsByOrganizationScope(results: SearchResult[], options: SearchOptions): SearchResult[] {\n const organizationIds = normalizeOrganizationFilter(options)\n if (!organizationIds) return results\n if (organizationIds.length === 0) return []\n\n const allowed = new Set(organizationIds)\n return results.filter((result) => {\n const organizationId = typeof result.organizationId === 'string' ? result.organizationId.trim() : ''\n return organizationId.length > 0 && allowed.has(organizationId)\n })\n}\n\n/**\n * SearchService orchestrates multiple search strategies, executing searches in parallel\n * and merging results using the RRF algorithm.\n *\n * Features:\n * - Parallel strategy execution for optimal performance\n * - Graceful degradation when strategies fail\n * - Result merging with configurable weights\n * - Strategy availability checking\n *\n * @example\n * ```typescript\n * const service = new SearchService({\n * strategies: [tokenStrategy, vectorStrategy, fulltextStrategy],\n * defaultStrategies: ['fulltext', 'vector', 'tokens'],\n * mergeConfig: {\n * duplicateHandling: 'highest_score',\n * strategyWeights: { fulltext: 1.2, vector: 1.0, tokens: 0.8 },\n * },\n * })\n *\n * const results = await service.search('john doe', { tenantId: 'tenant-123' })\n * ```\n */\nexport class SearchService {\n private readonly strategies: Map<SearchStrategyId, SearchStrategy>\n private readonly defaultStrategies: SearchStrategyId[]\n private readonly fallbackStrategy: SearchStrategyId | undefined\n private readonly mergeConfig: ResultMergeConfig\n private readonly presenterEnricher?: PresenterEnricherFn\n private readonly availabilityCache = new Map<SearchStrategyId, { value: boolean; expiresAt: number }>()\n private readonly availabilityInflight = new Map<SearchStrategyId, Promise<boolean>>()\n private readonly availabilityCacheTtlMs: number\n\n constructor(options: SearchServiceOptions = {}) {\n this.strategies = new Map()\n for (const strategy of options.strategies ?? []) {\n this.strategies.set(strategy.id, strategy)\n }\n this.defaultStrategies = options.defaultStrategies ?? ['tokens']\n this.fallbackStrategy = options.fallbackStrategy\n this.mergeConfig = options.mergeConfig ?? DEFAULT_MERGE_CONFIG\n this.presenterEnricher = options.presenterEnricher\n this.availabilityCacheTtlMs = options.availabilityCacheTtlMs ?? STRATEGY_AVAILABILITY_CACHE_TTL_MS\n }\n\n /**\n * Get all registered strategies.\n */\n getStrategies(): SearchStrategy[] {\n return Array.from(this.strategies.values())\n }\n\n /**\n * Execute a search query across configured strategies.\n *\n * @param query - Search query string\n * @param options - Search options with tenant, filters, etc.\n * @returns Merged and ranked search results\n */\n async search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const organizationIds = normalizeOrganizationFilter(options)\n if (organizationIds && organizationIds.length === 0) {\n return []\n }\n\n const strategyIds = options.strategies ?? this.defaultStrategies\n const activeStrategies = await this.getAvailableStrategies(strategyIds)\n\n if (activeStrategies.length === 0) {\n // Try fallback strategy if defined\n if (this.fallbackStrategy) {\n const fallback = await this.getAvailableStrategies([this.fallbackStrategy])\n if (fallback.length > 0) {\n activeStrategies.push(...fallback)\n }\n }\n }\n\n if (activeStrategies.length === 0) {\n return []\n }\n\n // Execute searches in parallel with graceful degradation\n const results = await Promise.allSettled(\n activeStrategies.map((strategy) => this.executeStrategySearch(strategy, query, options)),\n )\n\n // Collect successful results, log failures\n const allResults: SearchResult[] = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'fulfilled') {\n allResults.push(...result.value)\n } else {\n const strategy = activeStrategies[i]\n searchError('SearchService', 'Strategy search failed', {\n strategyId: strategy?.id,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n // Merge and rank results\n const merged = mergeAndRankResults(allResults, this.mergeConfig)\n const scoped = filterResultsByOrganizationScope(merged, options)\n\n // Enrich results missing presenter or navigation metadata\n return this.enrichResultsWithPresenter(scoped, options.tenantId, options.organizationId)\n }\n\n /**\n * Enrich results that are missing presenter data using the configured enricher.\n * This ensures token-only results get proper titles/subtitles for display.\n */\n private async enrichResultsWithPresenter(\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n ): Promise<SearchResult[]> {\n // If no enricher configured, return as-is\n if (!this.presenterEnricher) return results\n\n const hasMissing = results.some(needsSearchResultEnrichment)\n if (!hasMissing) return results\n\n // Use the configured presenter enricher\n try {\n return await this.presenterEnricher(results, tenantId, organizationId)\n } catch {\n // Enrichment failed, return results as-is\n return results\n }\n }\n\n /**\n * Index a record across all available strategies.\n *\n * @param record - Record to index\n */\n async index(record: IndexableRecord): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n if (strategies.length === 0) {\n return\n }\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyIndex(strategy, record)),\n )\n\n this.throwOnStrategyFailures('index', strategies, results, {\n entityId: record.entityId,\n recordId: record.recordId,\n })\n }\n\n /**\n * Delete a record from all strategies.\n *\n * @param entityId - Entity type identifier\n * @param recordId - Record primary key\n * @param tenantId - Tenant for isolation\n */\n async delete(entityId: string, recordId: string, tenantId: string): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyDelete(strategy, entityId, recordId, tenantId)),\n )\n\n this.throwOnStrategyFailures('delete', strategies, results, { entityId, recordId })\n }\n\n /**\n * Bulk index multiple records.\n *\n * @param records - Records to index\n */\n async bulkIndex(records: IndexableRecord[]): Promise<void> {\n if (records.length === 0) return\n\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.bulkIndex) {\n return strategy.bulkIndex(records)\n }\n // Fallback to individual indexing\n return Promise.all(records.map((record) => this.executeStrategyIndex(strategy, record)))\n }),\n )\n\n // Collect failures and throw if any occurred\n const failures: Array<{ strategyId: string; error: string }> = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n const errorMessage = result.reason instanceof Error ? result.reason.message : result.reason\n failures.push({\n strategyId: strategy?.id || 'unknown',\n error: errorMessage,\n })\n searchError('SearchService', 'Strategy bulkIndex failed', {\n strategyId: strategy?.id,\n recordCount: records.length,\n error: errorMessage,\n })\n }\n }\n\n if (failures.length > 0) {\n throw new Error(\n `Bulk indexing failed for ${failures.length} strategy(ies): ${failures\n .map((f) => `${f.strategyId} (${f.error})`)\n .join(', ')}`\n )\n }\n }\n\n /**\n * Purge all records for an entity type.\n *\n * @param entityId - Entity type to purge\n * @param tenantId - Tenant for isolation\n */\n async purge(entityId: string, tenantId: string, organizationId?: string | null): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.purge) {\n return strategy.purge(entityId, tenantId, organizationId)\n }\n return Promise.resolve()\n }),\n )\n\n this.throwOnStrategyFailures('purge', strategies, results, { entityId })\n }\n\n /**\n * Inspect the settled results of a per-strategy write operation, log every\n * rejection, and re-throw an aggregated error when any strategy failed.\n *\n * Write operations (index/delete/purge) must surface failures to the caller\n * so the queue worker re-throws and the job is retried. Swallowing rejections\n * here causes silent, permanent index gaps on transient failures such as\n * Postgres connection-pool exhaustion (issue #3103). Successful strategies\n * still commit their work; only the aggregated failure propagates.\n */\n private throwOnStrategyFailures(\n operation: 'index' | 'delete' | 'purge',\n strategies: SearchStrategy[],\n results: PromiseSettledResult<unknown>[],\n context: { entityId: string; recordId?: string },\n ): void {\n const failures: Array<{ strategyId: string; reason: unknown }> = []\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n failures.push({ strategyId: strategy?.id || 'unknown', reason: result.reason })\n searchError('SearchService', `Strategy ${operation} failed`, {\n strategyId: strategy?.id,\n entityId: context.entityId,\n recordId: context.recordId,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n if (failures.length === 0) return\n\n const summary = `Search ${operation} failed for ${failures.length} strategy(ies): ${failures\n .map((failure) => {\n const message = failure.reason instanceof Error ? failure.reason.message : String(failure.reason)\n return `${failure.strategyId} (${message})`\n })\n .join(', ')}`\n\n throw new AggregateError(\n failures.map((failure) => failure.reason),\n summary,\n )\n }\n\n /**\n * Register a new strategy at runtime.\n *\n * @param strategy - Strategy to register\n */\n registerStrategy(strategy: SearchStrategy): void {\n this.strategies.set(strategy.id, strategy)\n this.availabilityCache.delete(strategy.id)\n this.availabilityInflight.delete(strategy.id)\n }\n\n /**\n * Unregister a strategy.\n *\n * @param strategyId - Strategy ID to remove\n */\n unregisterStrategy(strategyId: SearchStrategyId): void {\n this.strategies.delete(strategyId)\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n }\n\n /**\n * Invalidate the strategy availability cache.\n * Call after manual reconnects or env changes when callers must observe the\n * current backend state immediately rather than waiting for TTL expiry.\n */\n invalidateAvailabilityCache(strategyId?: SearchStrategyId): void {\n if (strategyId) {\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n return\n }\n this.availabilityCache.clear()\n this.availabilityInflight.clear()\n }\n\n /**\n * Get all registered strategy IDs.\n */\n getRegisteredStrategies(): SearchStrategyId[] {\n return Array.from(this.strategies.keys())\n }\n\n /**\n * Get a specific strategy by ID.\n *\n * @param strategyId - Strategy ID to retrieve\n * @returns The strategy if registered, undefined otherwise\n */\n getStrategy(strategyId: SearchStrategyId): SearchStrategy | undefined {\n return this.strategies.get(strategyId)\n }\n\n /**\n * Get the default strategies list.\n */\n getDefaultStrategies(): SearchStrategyId[] {\n return [...this.defaultStrategies]\n }\n\n /**\n * Check if a specific strategy is available.\n *\n * @param strategyId - Strategy ID to check\n */\n async isStrategyAvailable(strategyId: SearchStrategyId): Promise<boolean> {\n const strategy = this.strategies.get(strategyId)\n if (!strategy) return false\n return this.checkStrategyAvailability(strategy)\n }\n\n /**\n * Resolve a strategy's availability via the short-lived TTL cache.\n * Coalesces concurrent callers onto a single in-flight probe to avoid\n * thundering-herd on remote backends.\n */\n private async checkStrategyAvailability(strategy: SearchStrategy): Promise<boolean> {\n const now = Date.now()\n const cached = this.availabilityCache.get(strategy.id)\n if (cached && cached.expiresAt > now) return cached.value\n\n const inflight = this.availabilityInflight.get(strategy.id)\n if (inflight) return inflight\n\n const probe = (async () => {\n try {\n const value = await strategy.isAvailable()\n this.availabilityCache.set(strategy.id, {\n value,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return value\n } catch {\n this.availabilityCache.set(strategy.id, {\n value: false,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return false\n } finally {\n this.availabilityInflight.delete(strategy.id)\n }\n })()\n this.availabilityInflight.set(strategy.id, probe)\n return probe\n }\n\n /**\n * Get available strategies from the requested list.\n * Filters out strategies that are not registered or not available.\n * Probes run in parallel and reuse a short-lived per-strategy availability\n * cache, so hot paths pay the max latency of the slowest probe (or zero\n * when cached) instead of the sum of all probes.\n */\n private async getAvailableStrategies(ids?: SearchStrategyId[]): Promise<SearchStrategy[]> {\n const targetIds = ids ?? Array.from(this.strategies.keys())\n const candidates: SearchStrategy[] = []\n for (const id of targetIds) {\n const strategy = this.strategies.get(id)\n if (strategy) candidates.push(strategy)\n }\n\n const probes = await Promise.allSettled(\n candidates.map((strategy) => this.checkStrategyAvailability(strategy)),\n )\n\n const available: SearchStrategy[] = []\n for (let i = 0; i < probes.length; i++) {\n const probe = probes[i]\n if (probe.status === 'fulfilled' && probe.value) {\n available.push(candidates[i])\n }\n }\n\n return available.sort((a, b) => b.priority - a.priority)\n }\n\n /**\n * Execute search on a single strategy with error handling.\n */\n private async executeStrategySearch(\n strategy: SearchStrategy,\n query: string,\n options: SearchOptions,\n ): Promise<SearchResult[]> {\n await strategy.ensureReady()\n return strategy.search(query, options)\n }\n\n /**\n * Execute index on a single strategy with error handling.\n */\n private async executeStrategyIndex(\n strategy: SearchStrategy,\n record: IndexableRecord,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.index(record)\n }\n\n /**\n * Execute delete on a single strategy with error handling.\n */\n private async executeStrategyDelete(\n strategy: SearchStrategy,\n entityId: string,\n recordId: string,\n tenantId: string,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.delete(entityId, recordId, tenantId)\n }\n}\n"],
|
|
5
|
-
"mappings": "AAUA,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import type {\n SearchStrategy,\n SearchStrategyId,\n SearchOptions,\n SearchResult,\n SearchServiceOptions,\n ResultMergeConfig,\n IndexableRecord,\n PresenterEnricherFn,\n} from './types'\nimport { mergeAndRankResults } from './lib/merger'\nimport { searchError } from './lib/debug'\n\n/**\n * Default merge configuration.\n */\nconst DEFAULT_MERGE_CONFIG: ResultMergeConfig = {\n duplicateHandling: 'highest_score',\n}\n\n/**\n * Cache TTL for strategy availability checks.\n * Short window so connectivity changes (Meilisearch up/down) propagate quickly,\n * long enough to skip per-request RTT to remote backends on hot paths.\n */\nconst STRATEGY_AVAILABILITY_CACHE_TTL_MS = 2_000\n\nfunction normalizeOrganizationFilter(options: SearchOptions): string[] | null {\n const single = typeof options.organizationId === 'string' ? options.organizationId.trim() : ''\n if (single) return [single]\n if (!Array.isArray(options.organizationIds)) return null\n\n const values = Array.from(new Set(\n options.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n return values\n}\n\nfunction filterResultsByOrganizationScope(results: SearchResult[], options: SearchOptions): SearchResult[] {\n const organizationIds = normalizeOrganizationFilter(options)\n if (!organizationIds) return results\n if (organizationIds.length === 0) return []\n\n const allowed = new Set(organizationIds)\n return results.filter((result) => {\n const organizationId = typeof result.organizationId === 'string' ? result.organizationId.trim() : ''\n return organizationId.length > 0 && allowed.has(organizationId)\n })\n}\n\n/**\n * SearchService orchestrates multiple search strategies, executing searches in parallel\n * and merging results using the RRF algorithm.\n *\n * Features:\n * - Parallel strategy execution for optimal performance\n * - Graceful degradation when strategies fail\n * - Result merging with configurable weights\n * - Strategy availability checking\n *\n * @example\n * ```typescript\n * const service = new SearchService({\n * strategies: [tokenStrategy, vectorStrategy, fulltextStrategy],\n * defaultStrategies: ['fulltext', 'vector', 'tokens'],\n * mergeConfig: {\n * duplicateHandling: 'highest_score',\n * strategyWeights: { fulltext: 1.2, vector: 1.0, tokens: 0.8 },\n * },\n * })\n *\n * const results = await service.search('john doe', { tenantId: 'tenant-123' })\n * ```\n */\nexport class SearchService {\n private readonly strategies: Map<SearchStrategyId, SearchStrategy>\n private readonly defaultStrategies: SearchStrategyId[]\n private readonly fallbackStrategy: SearchStrategyId | undefined\n private readonly mergeConfig: ResultMergeConfig\n private readonly presenterEnricher?: PresenterEnricherFn\n private readonly availabilityCache = new Map<SearchStrategyId, { value: boolean; expiresAt: number }>()\n private readonly availabilityInflight = new Map<SearchStrategyId, Promise<boolean>>()\n private readonly availabilityCacheTtlMs: number\n\n constructor(options: SearchServiceOptions = {}) {\n this.strategies = new Map()\n for (const strategy of options.strategies ?? []) {\n this.strategies.set(strategy.id, strategy)\n }\n this.defaultStrategies = options.defaultStrategies ?? ['tokens']\n this.fallbackStrategy = options.fallbackStrategy\n this.mergeConfig = options.mergeConfig ?? DEFAULT_MERGE_CONFIG\n this.presenterEnricher = options.presenterEnricher\n this.availabilityCacheTtlMs = options.availabilityCacheTtlMs ?? STRATEGY_AVAILABILITY_CACHE_TTL_MS\n }\n\n /**\n * Get all registered strategies.\n */\n getStrategies(): SearchStrategy[] {\n return Array.from(this.strategies.values())\n }\n\n /**\n * Execute a search query across configured strategies.\n *\n * @param query - Search query string\n * @param options - Search options with tenant, filters, etc.\n * @returns Merged and ranked search results\n */\n async search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const organizationIds = normalizeOrganizationFilter(options)\n if (organizationIds && organizationIds.length === 0) {\n return []\n }\n\n const strategyIds = options.strategies ?? this.defaultStrategies\n const activeStrategies = await this.getAvailableStrategies(strategyIds)\n\n if (activeStrategies.length === 0) {\n // Try fallback strategy if defined\n if (this.fallbackStrategy) {\n const fallback = await this.getAvailableStrategies([this.fallbackStrategy])\n if (fallback.length > 0) {\n activeStrategies.push(...fallback)\n }\n }\n }\n\n if (activeStrategies.length === 0) {\n return []\n }\n\n // Execute searches in parallel with graceful degradation\n const results = await Promise.allSettled(\n activeStrategies.map((strategy) => this.executeStrategySearch(strategy, query, options)),\n )\n\n // Collect successful results, log failures\n const allResults: SearchResult[] = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'fulfilled') {\n allResults.push(...result.value)\n } else {\n const strategy = activeStrategies[i]\n searchError('SearchService', 'Strategy search failed', {\n strategyId: strategy?.id,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n // Merge and rank results\n const merged = mergeAndRankResults(allResults, this.mergeConfig)\n const scoped = filterResultsByOrganizationScope(merged, options)\n\n // Enrich results missing presenter or navigation metadata\n return this.enrichResultsWithPresenter(scoped, options.tenantId, options.organizationId)\n }\n\n /**\n * Recompute configured presenters at request time and fill missing presenter\n * or navigation data for unconfigured results.\n */\n private async enrichResultsWithPresenter(\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n ): Promise<SearchResult[]> {\n // If no enricher configured, return as-is\n if (!this.presenterEnricher) return results\n\n try {\n return await this.presenterEnricher(results, tenantId, organizationId)\n } catch {\n // Enrichment failed, return results as-is\n return results\n }\n }\n\n /**\n * Index a record across all available strategies.\n *\n * @param record - Record to index\n */\n async index(record: IndexableRecord): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n if (strategies.length === 0) {\n return\n }\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyIndex(strategy, record)),\n )\n\n this.throwOnStrategyFailures('index', strategies, results, {\n entityId: record.entityId,\n recordId: record.recordId,\n })\n }\n\n /**\n * Delete a record from all strategies.\n *\n * @param entityId - Entity type identifier\n * @param recordId - Record primary key\n * @param tenantId - Tenant for isolation\n */\n async delete(entityId: string, recordId: string, tenantId: string): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyDelete(strategy, entityId, recordId, tenantId)),\n )\n\n this.throwOnStrategyFailures('delete', strategies, results, { entityId, recordId })\n }\n\n /**\n * Bulk index multiple records.\n *\n * @param records - Records to index\n */\n async bulkIndex(records: IndexableRecord[]): Promise<void> {\n if (records.length === 0) return\n\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.bulkIndex) {\n return strategy.bulkIndex(records)\n }\n // Fallback to individual indexing\n return Promise.all(records.map((record) => this.executeStrategyIndex(strategy, record)))\n }),\n )\n\n // Collect failures and throw if any occurred\n const failures: Array<{ strategyId: string; error: string }> = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n const errorMessage = result.reason instanceof Error ? result.reason.message : result.reason\n failures.push({\n strategyId: strategy?.id || 'unknown',\n error: errorMessage,\n })\n searchError('SearchService', 'Strategy bulkIndex failed', {\n strategyId: strategy?.id,\n recordCount: records.length,\n error: errorMessage,\n })\n }\n }\n\n if (failures.length > 0) {\n throw new Error(\n `Bulk indexing failed for ${failures.length} strategy(ies): ${failures\n .map((f) => `${f.strategyId} (${f.error})`)\n .join(', ')}`\n )\n }\n }\n\n /**\n * Purge all records for an entity type.\n *\n * @param entityId - Entity type to purge\n * @param tenantId - Tenant for isolation\n */\n async purge(entityId: string, tenantId: string, organizationId?: string | null): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.purge) {\n return strategy.purge(entityId, tenantId, organizationId)\n }\n return Promise.resolve()\n }),\n )\n\n this.throwOnStrategyFailures('purge', strategies, results, { entityId })\n }\n\n /**\n * Inspect the settled results of a per-strategy write operation, log every\n * rejection, and re-throw an aggregated error when any strategy failed.\n *\n * Write operations (index/delete/purge) must surface failures to the caller\n * so the queue worker re-throws and the job is retried. Swallowing rejections\n * here causes silent, permanent index gaps on transient failures such as\n * Postgres connection-pool exhaustion (issue #3103). Successful strategies\n * still commit their work; only the aggregated failure propagates.\n */\n private throwOnStrategyFailures(\n operation: 'index' | 'delete' | 'purge',\n strategies: SearchStrategy[],\n results: PromiseSettledResult<unknown>[],\n context: { entityId: string; recordId?: string },\n ): void {\n const failures: Array<{ strategyId: string; reason: unknown }> = []\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n failures.push({ strategyId: strategy?.id || 'unknown', reason: result.reason })\n searchError('SearchService', `Strategy ${operation} failed`, {\n strategyId: strategy?.id,\n entityId: context.entityId,\n recordId: context.recordId,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n if (failures.length === 0) return\n\n const summary = `Search ${operation} failed for ${failures.length} strategy(ies): ${failures\n .map((failure) => {\n const message = failure.reason instanceof Error ? failure.reason.message : String(failure.reason)\n return `${failure.strategyId} (${message})`\n })\n .join(', ')}`\n\n throw new AggregateError(\n failures.map((failure) => failure.reason),\n summary,\n )\n }\n\n /**\n * Register a new strategy at runtime.\n *\n * @param strategy - Strategy to register\n */\n registerStrategy(strategy: SearchStrategy): void {\n this.strategies.set(strategy.id, strategy)\n this.availabilityCache.delete(strategy.id)\n this.availabilityInflight.delete(strategy.id)\n }\n\n /**\n * Unregister a strategy.\n *\n * @param strategyId - Strategy ID to remove\n */\n unregisterStrategy(strategyId: SearchStrategyId): void {\n this.strategies.delete(strategyId)\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n }\n\n /**\n * Invalidate the strategy availability cache.\n * Call after manual reconnects or env changes when callers must observe the\n * current backend state immediately rather than waiting for TTL expiry.\n */\n invalidateAvailabilityCache(strategyId?: SearchStrategyId): void {\n if (strategyId) {\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n return\n }\n this.availabilityCache.clear()\n this.availabilityInflight.clear()\n }\n\n /**\n * Get all registered strategy IDs.\n */\n getRegisteredStrategies(): SearchStrategyId[] {\n return Array.from(this.strategies.keys())\n }\n\n /**\n * Get a specific strategy by ID.\n *\n * @param strategyId - Strategy ID to retrieve\n * @returns The strategy if registered, undefined otherwise\n */\n getStrategy(strategyId: SearchStrategyId): SearchStrategy | undefined {\n return this.strategies.get(strategyId)\n }\n\n /**\n * Get the default strategies list.\n */\n getDefaultStrategies(): SearchStrategyId[] {\n return [...this.defaultStrategies]\n }\n\n /**\n * Check if a specific strategy is available.\n *\n * @param strategyId - Strategy ID to check\n */\n async isStrategyAvailable(strategyId: SearchStrategyId): Promise<boolean> {\n const strategy = this.strategies.get(strategyId)\n if (!strategy) return false\n return this.checkStrategyAvailability(strategy)\n }\n\n /**\n * Resolve a strategy's availability via the short-lived TTL cache.\n * Coalesces concurrent callers onto a single in-flight probe to avoid\n * thundering-herd on remote backends.\n */\n private async checkStrategyAvailability(strategy: SearchStrategy): Promise<boolean> {\n const now = Date.now()\n const cached = this.availabilityCache.get(strategy.id)\n if (cached && cached.expiresAt > now) return cached.value\n\n const inflight = this.availabilityInflight.get(strategy.id)\n if (inflight) return inflight\n\n const probe = (async () => {\n try {\n const value = await strategy.isAvailable()\n this.availabilityCache.set(strategy.id, {\n value,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return value\n } catch {\n this.availabilityCache.set(strategy.id, {\n value: false,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return false\n } finally {\n this.availabilityInflight.delete(strategy.id)\n }\n })()\n this.availabilityInflight.set(strategy.id, probe)\n return probe\n }\n\n /**\n * Get available strategies from the requested list.\n * Filters out strategies that are not registered or not available.\n * Probes run in parallel and reuse a short-lived per-strategy availability\n * cache, so hot paths pay the max latency of the slowest probe (or zero\n * when cached) instead of the sum of all probes.\n */\n private async getAvailableStrategies(ids?: SearchStrategyId[]): Promise<SearchStrategy[]> {\n const targetIds = ids ?? Array.from(this.strategies.keys())\n const candidates: SearchStrategy[] = []\n for (const id of targetIds) {\n const strategy = this.strategies.get(id)\n if (strategy) candidates.push(strategy)\n }\n\n const probes = await Promise.allSettled(\n candidates.map((strategy) => this.checkStrategyAvailability(strategy)),\n )\n\n const available: SearchStrategy[] = []\n for (let i = 0; i < probes.length; i++) {\n const probe = probes[i]\n if (probe.status === 'fulfilled' && probe.value) {\n available.push(candidates[i])\n }\n }\n\n return available.sort((a, b) => b.priority - a.priority)\n }\n\n /**\n * Execute search on a single strategy with error handling.\n */\n private async executeStrategySearch(\n strategy: SearchStrategy,\n query: string,\n options: SearchOptions,\n ): Promise<SearchResult[]> {\n await strategy.ensureReady()\n return strategy.search(query, options)\n }\n\n /**\n * Execute index on a single strategy with error handling.\n */\n private async executeStrategyIndex(\n strategy: SearchStrategy,\n record: IndexableRecord,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.index(record)\n }\n\n /**\n * Execute delete on a single strategy with error handling.\n */\n private async executeStrategyDelete(\n strategy: SearchStrategy,\n entityId: string,\n recordId: string,\n tenantId: string,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.delete(entityId, recordId, tenantId)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
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.
|
|
3
|
+
"version": "0.6.7-develop.6795.1.8a3f27921c",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -116,25 +116,25 @@
|
|
|
116
116
|
},
|
|
117
117
|
"dependencies": {
|
|
118
118
|
"@ai-sdk/amazon-bedrock": "^5.0.2",
|
|
119
|
-
"@ai-sdk/cohere": "^4.0.
|
|
119
|
+
"@ai-sdk/cohere": "^4.0.14",
|
|
120
120
|
"@ai-sdk/google": "^4.0.2",
|
|
121
|
-
"@ai-sdk/mistral": "^4.0.
|
|
121
|
+
"@ai-sdk/mistral": "^4.0.16",
|
|
122
122
|
"@ai-sdk/openai": "^4.0.2",
|
|
123
123
|
"ai": "^7.0.4",
|
|
124
|
-
"ai-sdk-ollama": "4.
|
|
125
|
-
"meilisearch": "^0.
|
|
124
|
+
"ai-sdk-ollama": "4.1.0",
|
|
125
|
+
"meilisearch": "^0.60.0",
|
|
126
126
|
"pg": "8.22.0",
|
|
127
127
|
"zod": "^4.4.3"
|
|
128
128
|
},
|
|
129
129
|
"peerDependencies": {
|
|
130
|
-
"@open-mercato/core": "0.6.7-develop.
|
|
131
|
-
"@open-mercato/queue": "0.6.7-develop.
|
|
132
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
130
|
+
"@open-mercato/core": "0.6.7-develop.6795.1.8a3f27921c",
|
|
131
|
+
"@open-mercato/queue": "0.6.7-develop.6795.1.8a3f27921c",
|
|
132
|
+
"@open-mercato/shared": "0.6.7-develop.6795.1.8a3f27921c"
|
|
133
133
|
},
|
|
134
134
|
"devDependencies": {
|
|
135
135
|
"@types/jest": "^30.0.0",
|
|
136
136
|
"jest": "^30.4.2",
|
|
137
|
-
"ts-jest": "^29.4.
|
|
137
|
+
"ts-jest": "^29.4.12",
|
|
138
138
|
"typescript": "7.0.2"
|
|
139
139
|
},
|
|
140
140
|
"publishConfig": {
|