@uniformdev/search 0.0.1 → 0.0.3

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/dist/index.mjs CHANGED
@@ -8,10 +8,15 @@ import {
8
8
  buildOrderByQuery,
9
9
  flattenBlockParams,
10
10
  getSearchParamsFromUrl
11
- } from "./chunk-ZABEFR2D.mjs";
11
+ } from "./chunk-RMUYFKZO.mjs";
12
12
 
13
13
  // src/client.ts
14
+ var emptyResult = () => ({
15
+ data: { items: [], total: 0, page: 0, perPage: 10, totalPages: 0 },
16
+ facets: {}
17
+ });
14
18
  function createSearchClient(config) {
19
+ const baseUrl = config.apiUrl.replace(/\/+$/, "");
15
20
  return {
16
21
  async performSearch(params) {
17
22
  const headers = {
@@ -25,23 +30,67 @@ function createSearchClient(config) {
25
30
  projectId: params.projectId || config.projectId
26
31
  };
27
32
  try {
28
- const response = await fetch(`${config.apiUrl}/api/search`, {
33
+ const response = await fetch(`${baseUrl}/api/search`, {
29
34
  method: "POST",
30
35
  headers,
31
36
  body: JSON.stringify(body)
32
37
  });
33
38
  if (!response.ok) {
34
- console.error(`[alex] Search API error: ${response.status}`);
35
- return { results: {} };
39
+ console.error(`Search API error: ${response.status}`);
40
+ return emptyResult();
36
41
  }
37
42
  return response.json();
38
43
  } catch (error) {
39
- console.error("[alex] Search fetch error:", error);
40
- return { results: {} };
44
+ console.error("Search fetch error:", error);
45
+ return emptyResult();
41
46
  }
42
47
  }
43
48
  };
44
49
  }
50
+
51
+ // src/highlights.ts
52
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
53
+ var isHighlightLeaf = (value) => isPlainObject(value) && ("snippet" in value || "snippets" in value || "value" in value || "values" in value);
54
+ var unknownToString = (value) => {
55
+ if (value === null || value === void 0) return "";
56
+ if (typeof value === "string") return value;
57
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
58
+ return String(value);
59
+ }
60
+ try {
61
+ return JSON.stringify(value);
62
+ } catch (e) {
63
+ return "";
64
+ }
65
+ };
66
+ var getHighlightMatch = (hit, fieldPath, fieldValue) => {
67
+ const fieldHighlight = Array.isArray(hit._highlights) ? hit._highlights.find((highlight) => isPlainObject(highlight) && highlight.field === fieldPath) : void 0;
68
+ if (isPlainObject(fieldHighlight)) {
69
+ const snippets = fieldHighlight.snippets;
70
+ if (Array.isArray(snippets)) return { html: snippets.map(unknownToString).join(", ") };
71
+ const snippet = unknownToString(fieldHighlight.snippet);
72
+ if (snippet) return { html: snippet };
73
+ const value = unknownToString(fieldHighlight.value);
74
+ if (value) return { html: value };
75
+ }
76
+ const highlightValue = fieldPath.split(".").reduce((current, pathPart) => {
77
+ if (!isPlainObject(current)) return void 0;
78
+ return current[pathPart];
79
+ }, hit._highlight);
80
+ if (isHighlightLeaf(highlightValue)) {
81
+ if (Array.isArray(highlightValue.snippets)) {
82
+ return { html: highlightValue.snippets.map(unknownToString).join(", ") };
83
+ }
84
+ const snippet = unknownToString(highlightValue.snippet);
85
+ if (snippet) return { html: snippet };
86
+ if (Array.isArray(highlightValue.values)) return { values: highlightValue.values };
87
+ if ("value" in highlightValue) return { html: unknownToString(highlightValue.value) };
88
+ }
89
+ if (Array.isArray(fieldValue) && Array.isArray(highlightValue)) {
90
+ return { values: highlightValue };
91
+ }
92
+ return void 0;
93
+ };
45
94
  export {
46
95
  DEFAULT_PAGE_SIZE,
47
96
  FIRST_PAGE,
@@ -52,6 +101,7 @@ export {
52
101
  buildOrderByQuery,
53
102
  createSearchClient,
54
103
  flattenBlockParams,
104
+ getHighlightMatch,
55
105
  getSearchParamsFromUrl
56
106
  };
57
107
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { SearchCollection, MultiSearchResult } from './types';\n\nexport interface SearchParams {\n projectId?: string;\n page?: number;\n perPage?: number;\n filters?: Record<string, unknown>;\n facetBy?: string;\n search?: string;\n orderBy?: string;\n collections?: SearchCollection[];\n}\n\nexport interface SearchClient {\n performSearch(params: SearchParams): Promise<{ results: MultiSearchResult }>;\n}\n\nexport function createSearchClient(config: {\n apiUrl: string;\n apiKey?: string;\n projectId?: string;\n}): SearchClient {\n return {\n async performSearch(params) {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (config.apiKey) {\n headers['x-api-key'] = config.apiKey;\n }\n\n const body = {\n ...params,\n projectId: params.projectId || config.projectId,\n };\n\n try {\n const response = await fetch(`${config.apiUrl}/api/search`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n console.error(`[alex] Search API error: ${response.status}`);\n return { results: {} };\n }\n\n return response.json();\n } catch (error) {\n console.error('[alex] Search fetch error:', error);\n return { results: {} };\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAiBO,SAAS,mBAAmB,QAIlB;AACf,SAAO;AAAA,IACL,MAAM,cAAc,QAAQ;AAC1B,YAAM,UAAkC;AAAA,QACtC,gBAAgB;AAAA,MAClB;AACA,UAAI,OAAO,QAAQ;AACjB,gBAAQ,WAAW,IAAI,OAAO;AAAA,MAChC;AAEA,YAAM,OAAO;AAAA,QACX,GAAG;AAAA,QACH,WAAW,OAAO,aAAa,OAAO;AAAA,MACxC;AAEA,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,GAAG,OAAO,MAAM,eAAe;AAAA,UAC1D,QAAQ;AAAA,UACR;AAAA,UACA,MAAM,KAAK,UAAU,IAAI;AAAA,QAC3B,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,kBAAQ,MAAM,4BAA4B,SAAS,MAAM,EAAE;AAC3D,iBAAO,EAAE,SAAS,CAAC,EAAE;AAAA,QACvB;AAEA,eAAO,SAAS,KAAK;AAAA,MACvB,SAAS,OAAO;AACd,gBAAQ,MAAM,8BAA8B,KAAK;AACjD,eAAO,EAAE,SAAS,CAAC,EAAE;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/highlights.ts"],"sourcesContent":["import type { CollectionResult } from './types';\n\nexport interface SearchParams {\n projectId?: string;\n page?: number;\n perPage?: number;\n filters?: Record<string, unknown>;\n baseFilterBy?: string;\n facetBy?: string;\n queryBy?: string;\n search?: string;\n orderBy?: string;\n locale?: string;\n maxFacetValues?: number;\n\n // ── Typo tolerance (all optional; omitted → the search engine's defaults apply) ──\n /** Max typos tolerated per word: 0, 1, or 2. (Typesense `num_typos`.) */\n maxTypos?: number;\n /** Minimum word length before a single typo is allowed. (Typesense `min_len_1typo`.) */\n minLengthFor1Typo?: number;\n /** Minimum word length before two typos are allowed. (Typesense `min_len_2typo`.) */\n minLengthFor2Typos?: number;\n /** If a query returns fewer than this many results, retry with more typos. (Typesense `typo_tokens_threshold`.) */\n typoFallbackThreshold?: number;\n /** Rank exact matches above typo-corrected ones. (Typesense `prioritize_exact_match`.) */\n prioritizeExactMatch?: boolean;\n}\n\nexport interface SearchClient {\n performSearch(params: SearchParams): Promise<CollectionResult>;\n}\n\nconst emptyResult = (): CollectionResult => ({\n data: { items: [], total: 0, page: 0, perPage: 10, totalPages: 0 },\n facets: {},\n});\n\nexport function createSearchClient(config: { apiUrl: string; apiKey?: string; projectId?: string }): SearchClient {\n const baseUrl = config.apiUrl.replace(/\\/+$/, '');\n return {\n async performSearch(params) {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (config.apiKey) {\n headers['x-api-key'] = config.apiKey;\n }\n\n const body = {\n ...params,\n projectId: params.projectId || config.projectId,\n };\n\n try {\n const response = await fetch(`${baseUrl}/api/search`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n console.error(`Search API error: ${response.status}`);\n return emptyResult();\n }\n\n return response.json();\n } catch (error) {\n console.error('Search fetch error:', error);\n return emptyResult();\n }\n },\n };\n}\n","import type { SearchHit, SearchResponseHighlightObject } from './types';\n\n// Helpers for rendering search highlights returned by `performSearch`. A hit carries\n// highlight data in `_highlight` (nested by field) and/or `_highlights` (array form);\n// `getHighlightMatch` resolves the highlight for a given field into a simple shape the\n// UI can render — `html` (a snippet with the matched terms wrapped in highlight tags,\n// for `dangerouslySetInnerHTML`) or `values` (per-item highlights for array fields).\n\nexport type HighlightMatch = {\n html?: string;\n values?: unknown[];\n};\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isHighlightLeaf = (value: unknown): value is SearchResponseHighlightObject =>\n isPlainObject(value) &&\n ('snippet' in value || 'snippets' in value || 'value' in value || 'values' in value);\n\nconst unknownToString = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n};\n\n/**\n * Resolve the highlight (if any) for a field on a search hit.\n *\n * @param hit A hit returned by `performSearch`.\n * @param fieldPath The field name (dot-path for nested fields, e.g. `author.name`).\n * @param fieldValue The hit's raw value for the field (used to align array highlights).\n * @returns `{ html }` for a highlighted snippet, `{ values }` for per-item array\n * highlights, or `undefined` when the field wasn't matched.\n */\nexport const getHighlightMatch = (\n hit: SearchHit,\n fieldPath: string,\n fieldValue?: unknown\n): HighlightMatch | undefined => {\n const fieldHighlight = Array.isArray(hit._highlights)\n ? hit._highlights.find((highlight) => isPlainObject(highlight) && highlight.field === fieldPath)\n : undefined;\n\n if (isPlainObject(fieldHighlight)) {\n const snippets = fieldHighlight.snippets;\n if (Array.isArray(snippets)) return { html: snippets.map(unknownToString).join(', ') };\n const snippet = unknownToString(fieldHighlight.snippet);\n if (snippet) return { html: snippet };\n const value = unknownToString(fieldHighlight.value);\n if (value) return { html: value };\n }\n\n const highlightValue = fieldPath.split('.').reduce<unknown>((current, pathPart) => {\n if (!isPlainObject(current)) return undefined;\n return current[pathPart];\n }, hit._highlight);\n\n if (isHighlightLeaf(highlightValue)) {\n if (Array.isArray(highlightValue.snippets)) {\n return { html: highlightValue.snippets.map(unknownToString).join(', ') };\n }\n const snippet = unknownToString(highlightValue.snippet);\n if (snippet) return { html: snippet };\n if (Array.isArray(highlightValue.values)) return { values: highlightValue.values };\n if ('value' in highlightValue) return { html: unknownToString(highlightValue.value) };\n }\n\n if (Array.isArray(fieldValue) && Array.isArray(highlightValue)) {\n return { values: highlightValue };\n }\n\n return undefined;\n};\n"],"mappings":"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","names":[]}
package/dist/react.d.mts CHANGED
@@ -1,24 +1,22 @@
1
+ import * as react from 'react';
1
2
  import { FC, ReactNode } from 'react';
2
- import { g as SearchParams, M as MultiSearchResult, d as Pagination, f as SearchHit, F as Facets, P as PageSize, a as FilterBy, e as SearchCollection } from './client-DrL-8TfY.mjs';
3
+ import { d as Pagination, S as SearchHit, a as Facets, P as PageSize, F as FacetBy, h as SearchParams, C as CollectionResult, g as SearchItemConfig } from './client-DOew5kqb.mjs';
3
4
 
4
5
  interface SearchProviderProps {
5
6
  children: ReactNode;
6
- performSearch: (params: SearchParams) => Promise<{
7
- results: MultiSearchResult;
8
- }>;
9
- contentType?: string;
10
- filterBy?: unknown;
7
+ performSearch: (params: SearchParams) => Promise<CollectionResult>;
11
8
  orderBy?: unknown;
12
- baseFilters?: unknown;
9
+ baseFilterString?: string;
13
10
  pageSizes?: unknown;
14
- collections?: string | string[];
11
+ queryBy?: string[];
15
12
  searchDebounceMs?: number;
13
+ locale?: string;
14
+ maxFacetValues?: number;
16
15
  }
17
16
  interface UseSearchReturn {
18
17
  results: Pagination<SearchHit>;
19
18
  facets: Facets | null;
20
19
  isLoading: boolean;
21
- collectionResults: MultiSearchResult;
22
20
  searchBoxValue: string;
23
21
  setSearchQuery: (value: string) => void;
24
22
  page: number;
@@ -26,20 +24,34 @@ interface UseSearchReturn {
26
24
  pageSize: number;
27
25
  setPageSize: (size: number) => void;
28
26
  pageSizes: PageSize[];
27
+ /** Register a component's page-size config (last registration wins). `id` should be
28
+ * stable per instance. Used by pagination components that own their config. */
29
+ registerPageSizes: (id: string, pageSizes: unknown) => void;
30
+ unregisterPageSizes: (id: string) => void;
29
31
  orderByOptions: Array<{
30
32
  title: string;
31
33
  value: string;
32
34
  }>;
33
35
  selectedOrderBy: string;
34
36
  setOrderBy: (query: string) => void;
35
- filterOptions: FilterBy[];
37
+ /** Register a sort component's order-by config (last registration wins). `id` should
38
+ * be stable per component instance. Used by sort components that own their config
39
+ * (App Router slots) instead of it being passed to the provider. */
40
+ registerOrderBy: (id: string, orderBy: unknown) => void;
41
+ unregisterOrderBy: (id: string) => void;
42
+ filterOptions: FacetBy[];
43
+ setFilterOptions: (filters: FacetBy[]) => void;
44
+ /** Additive registration of a single facet option (keyed by fieldKey). Used by facet
45
+ * components that mount independently (e.g. App Router slots) rather than being
46
+ * enumerated by a parent. */
47
+ registerFilterOption: (filter: FacetBy) => void;
48
+ unregisterFilterOption: (fieldKey: string) => void;
36
49
  selectedFilters: Record<string, string[]>;
37
50
  setSelectedFilters: (filters: Record<string, string[]>) => void;
38
51
  clearFilters: () => void;
39
- selectedCollections: SearchCollection[];
40
- setSelectedCollections: (collections: SearchCollection[]) => void;
41
52
  formatResultsSummary: (template: string) => string;
42
53
  }
54
+ declare const SearchContext: react.Context<UseSearchReturn | null>;
43
55
  declare const SearchProvider: FC<SearchProviderProps>;
44
56
 
45
57
  declare function useSearch(): UseSearchReturn;
@@ -65,4 +77,28 @@ interface UseSearchPaginationReturn {
65
77
  }
66
78
  declare function useSearchPagination(siblingCount?: number): UseSearchPaginationReturn;
67
79
 
68
- export { DOTS, SearchProvider, type SearchProviderProps, type UseSearchPaginationReturn, type UseSearchReturn, usePagination, useSearch, useSearchPagination };
80
+ type UrlResolver = (hit: SearchHit) => string | undefined;
81
+ /** Wrap your search tree to override URL building for all hits. */
82
+ declare const SearchItemUrlResolverProvider: FC<{
83
+ resolver: UrlResolver;
84
+ children: ReactNode;
85
+ }>;
86
+ /** Used internally by SearchList to register the Canvas-config-driven default. */
87
+ declare const SearchItemDefaultUrlResolverProvider: FC<{
88
+ resolver: UrlResolver;
89
+ children: ReactNode;
90
+ }>;
91
+ /**
92
+ * Always returns a resolver. User-provided resolver (SearchItemUrlResolverProvider)
93
+ * takes priority over the default registered by SearchList.
94
+ */
95
+ declare function useUrlResolver(): UrlResolver;
96
+ interface UrlResolverOptions {
97
+ /** nodeId → resolved locale-specific path (from project map). */
98
+ pathsByNodeId?: Record<string, string>;
99
+ /** Current locale, used as fallback for legacy :locale token in urlTemplate. */
100
+ locale?: string;
101
+ }
102
+ declare function createDefaultUrlResolver(configs: SearchItemConfig[], options?: UrlResolverOptions): UrlResolver;
103
+
104
+ export { DOTS, SearchContext, SearchItemDefaultUrlResolverProvider, SearchItemUrlResolverProvider, SearchProvider, type SearchProviderProps, type UrlResolver, type UseSearchPaginationReturn, type UseSearchReturn, createDefaultUrlResolver, usePagination, useSearch, useSearchPagination, useUrlResolver };
package/dist/react.d.ts CHANGED
@@ -1,24 +1,22 @@
1
+ import * as react from 'react';
1
2
  import { FC, ReactNode } from 'react';
2
- import { g as SearchParams, M as MultiSearchResult, d as Pagination, f as SearchHit, F as Facets, P as PageSize, a as FilterBy, e as SearchCollection } from './client-DrL-8TfY.js';
3
+ import { d as Pagination, S as SearchHit, a as Facets, P as PageSize, F as FacetBy, h as SearchParams, C as CollectionResult, g as SearchItemConfig } from './client-DOew5kqb.js';
3
4
 
4
5
  interface SearchProviderProps {
5
6
  children: ReactNode;
6
- performSearch: (params: SearchParams) => Promise<{
7
- results: MultiSearchResult;
8
- }>;
9
- contentType?: string;
10
- filterBy?: unknown;
7
+ performSearch: (params: SearchParams) => Promise<CollectionResult>;
11
8
  orderBy?: unknown;
12
- baseFilters?: unknown;
9
+ baseFilterString?: string;
13
10
  pageSizes?: unknown;
14
- collections?: string | string[];
11
+ queryBy?: string[];
15
12
  searchDebounceMs?: number;
13
+ locale?: string;
14
+ maxFacetValues?: number;
16
15
  }
17
16
  interface UseSearchReturn {
18
17
  results: Pagination<SearchHit>;
19
18
  facets: Facets | null;
20
19
  isLoading: boolean;
21
- collectionResults: MultiSearchResult;
22
20
  searchBoxValue: string;
23
21
  setSearchQuery: (value: string) => void;
24
22
  page: number;
@@ -26,20 +24,34 @@ interface UseSearchReturn {
26
24
  pageSize: number;
27
25
  setPageSize: (size: number) => void;
28
26
  pageSizes: PageSize[];
27
+ /** Register a component's page-size config (last registration wins). `id` should be
28
+ * stable per instance. Used by pagination components that own their config. */
29
+ registerPageSizes: (id: string, pageSizes: unknown) => void;
30
+ unregisterPageSizes: (id: string) => void;
29
31
  orderByOptions: Array<{
30
32
  title: string;
31
33
  value: string;
32
34
  }>;
33
35
  selectedOrderBy: string;
34
36
  setOrderBy: (query: string) => void;
35
- filterOptions: FilterBy[];
37
+ /** Register a sort component's order-by config (last registration wins). `id` should
38
+ * be stable per component instance. Used by sort components that own their config
39
+ * (App Router slots) instead of it being passed to the provider. */
40
+ registerOrderBy: (id: string, orderBy: unknown) => void;
41
+ unregisterOrderBy: (id: string) => void;
42
+ filterOptions: FacetBy[];
43
+ setFilterOptions: (filters: FacetBy[]) => void;
44
+ /** Additive registration of a single facet option (keyed by fieldKey). Used by facet
45
+ * components that mount independently (e.g. App Router slots) rather than being
46
+ * enumerated by a parent. */
47
+ registerFilterOption: (filter: FacetBy) => void;
48
+ unregisterFilterOption: (fieldKey: string) => void;
36
49
  selectedFilters: Record<string, string[]>;
37
50
  setSelectedFilters: (filters: Record<string, string[]>) => void;
38
51
  clearFilters: () => void;
39
- selectedCollections: SearchCollection[];
40
- setSelectedCollections: (collections: SearchCollection[]) => void;
41
52
  formatResultsSummary: (template: string) => string;
42
53
  }
54
+ declare const SearchContext: react.Context<UseSearchReturn | null>;
43
55
  declare const SearchProvider: FC<SearchProviderProps>;
44
56
 
45
57
  declare function useSearch(): UseSearchReturn;
@@ -65,4 +77,28 @@ interface UseSearchPaginationReturn {
65
77
  }
66
78
  declare function useSearchPagination(siblingCount?: number): UseSearchPaginationReturn;
67
79
 
68
- export { DOTS, SearchProvider, type SearchProviderProps, type UseSearchPaginationReturn, type UseSearchReturn, usePagination, useSearch, useSearchPagination };
80
+ type UrlResolver = (hit: SearchHit) => string | undefined;
81
+ /** Wrap your search tree to override URL building for all hits. */
82
+ declare const SearchItemUrlResolverProvider: FC<{
83
+ resolver: UrlResolver;
84
+ children: ReactNode;
85
+ }>;
86
+ /** Used internally by SearchList to register the Canvas-config-driven default. */
87
+ declare const SearchItemDefaultUrlResolverProvider: FC<{
88
+ resolver: UrlResolver;
89
+ children: ReactNode;
90
+ }>;
91
+ /**
92
+ * Always returns a resolver. User-provided resolver (SearchItemUrlResolverProvider)
93
+ * takes priority over the default registered by SearchList.
94
+ */
95
+ declare function useUrlResolver(): UrlResolver;
96
+ interface UrlResolverOptions {
97
+ /** nodeId → resolved locale-specific path (from project map). */
98
+ pathsByNodeId?: Record<string, string>;
99
+ /** Current locale, used as fallback for legacy :locale token in urlTemplate. */
100
+ locale?: string;
101
+ }
102
+ declare function createDefaultUrlResolver(configs: SearchItemConfig[], options?: UrlResolverOptions): UrlResolver;
103
+
104
+ export { DOTS, SearchContext, SearchItemDefaultUrlResolverProvider, SearchItemUrlResolverProvider, SearchProvider, type SearchProviderProps, type UrlResolver, type UseSearchPaginationReturn, type UseSearchReturn, createDefaultUrlResolver, usePagination, useSearch, useSearchPagination, useUrlResolver };