@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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Uniform
3
+ Copyright (c) 2026 Uniform Systems, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @uniformdev/search
2
+
3
+ A lightweight client for [Uniform Search](https://uniform.dev) with optional React bindings for building faceted search experiences (filters, sorting, pagination, and URL-synced state).
4
+
5
+ The core client is framework-agnostic. The React bindings are exposed from a separate `@uniformdev/search/react` entry point and `react` is an optional peer dependency, so you only pull in React if you use them.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @uniformdev/search
11
+ # or
12
+ pnpm add @uniformdev/search
13
+ # or
14
+ yarn add @uniformdev/search
15
+ ```
16
+
17
+ To use the React bindings, make sure React 18+ is installed in your app:
18
+
19
+ ```bash
20
+ npm install react
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ### 1. Create a search client
26
+
27
+ The client posts to `${apiUrl}/api/search` and (optionally) attaches an `x-api-key` header. Configure it with your Uniform Search service URL, API key, and project ID:
28
+
29
+ ```ts
30
+ import { createSearchClient } from '@uniformdev/search';
31
+
32
+ const client = createSearchClient({
33
+ apiUrl: process.env.NEXT_PUBLIC_UNIFORM_SEARCH_API_URL!, // https://<your-project>.search.uniform.app
34
+ apiKey: process.env.NEXT_PUBLIC_UNIFORM_SEARCH_API_KEY,
35
+ projectId: process.env.NEXT_PUBLIC_UNIFORM_PROJECT_ID,
36
+ });
37
+
38
+ const result = await client.performSearch({
39
+ search: 'hello world',
40
+ page: 0,
41
+ perPage: 10,
42
+ });
43
+
44
+ console.log(result.data.items, result.data.total, result.facets);
45
+ ```
46
+
47
+ `performSearch` never throws — on a network or non-2xx error it logs and returns an empty result shape so your UI can render safely.
48
+
49
+ ### 2. (React) Wire up the provider
50
+
51
+ ```tsx
52
+ import { SearchProvider, useSearch } from '@uniformdev/search/react';
53
+ import { createSearchClient } from '@uniformdev/search';
54
+
55
+ const client = createSearchClient({
56
+ apiUrl: process.env.NEXT_PUBLIC_UNIFORM_SEARCH_API_URL!,
57
+ apiKey: process.env.NEXT_PUBLIC_UNIFORM_SEARCH_API_KEY,
58
+ projectId: process.env.NEXT_PUBLIC_UNIFORM_PROJECT_ID,
59
+ });
60
+
61
+ export function SearchPage() {
62
+ return (
63
+ <SearchProvider performSearch={client.performSearch}>
64
+ <SearchBox />
65
+ <Results />
66
+ </SearchProvider>
67
+ );
68
+ }
69
+
70
+ function SearchBox() {
71
+ const { searchBoxValue, setSearchQuery } = useSearch();
72
+ return (
73
+ <input
74
+ value={searchBoxValue}
75
+ onChange={(e) => setSearchQuery(e.target.value)}
76
+ placeholder="Search…"
77
+ />
78
+ );
79
+ }
80
+
81
+ function Results() {
82
+ const { results, isLoading } = useSearch();
83
+ if (isLoading) return <p>Loading…</p>;
84
+ return (
85
+ <ul>
86
+ {results.items.map((item) => (
87
+ <li key={item.id}>{String(item.title ?? item.id)}</li>
88
+ ))}
89
+ </ul>
90
+ );
91
+ }
92
+ ```
93
+
94
+ The provider keeps search state (query, page, page size, sort, and selected filters) in sync with the URL query string and debounces text input.
95
+
96
+ ## API
97
+
98
+ ### Core (`@uniformdev/search`)
99
+
100
+ | Export | Description |
101
+ |---|---|
102
+ | `createSearchClient(config)` | Creates a `SearchClient` with a `performSearch(params)` method. |
103
+ | `getSearchParamsFromUrl(url)` | Parses a URL's query string into a params object. |
104
+ | `buildOrderByQuery(orderBy)` | Builds an `orderBy` query string (e.g. `created_at_DESC`). |
105
+ | `flattenBlockParams(value)` | Normalizes Uniform block parameter values into a flat array. |
106
+ | Types | `SearchParams`, `SearchClient`, `CollectionResult`, `SearchHit`, `Pagination`, `Facets`, `FacetBy`, `OrderBy`, `PageSize`, and more. |
107
+
108
+ ### React (`@uniformdev/search/react`)
109
+
110
+ | Export | Description |
111
+ |---|---|
112
+ | `SearchProvider` | Context provider that owns search state and runs queries. |
113
+ | `useSearch()` | Hook returning results, facets, loading state, and state setters. Must be used inside a `SearchProvider`. |
114
+ | `usePagination`, `useSearchPagination`, `DOTS` | Pagination helpers. |
115
+ | `SearchItemUrlResolverProvider`, `useUrlResolver`, `createDefaultUrlResolver` | Resolve result items to URLs. |
116
+
117
+ ## Configuration
118
+
119
+ `createSearchClient` accepts:
120
+
121
+ | Option | Required | Description |
122
+ |---|---|---|
123
+ | `apiUrl` | yes | Base URL of your Uniform Search service. `/api/search` is appended automatically. |
124
+ | `apiKey` | no | Sent as the `x-api-key` header. |
125
+ | `projectId` | no | Default project ID, merged into each request body. |
126
+
127
+ ## License
128
+
129
+ MIT
@@ -8,7 +8,14 @@ var DEFAULT_PAGE_SIZE = 10;
8
8
 
9
9
  // src/utils.ts
10
10
  var buildOrderByQuery = (orderBy) => {
11
- return `${orderBy.field}_${orderBy.direction}`;
11
+ var _a;
12
+ const field = orderBy.field;
13
+ if (field && typeof field === "object" && "field" in field) {
14
+ const sv = field;
15
+ return `${sv.field}_${sv.direction.toUpperCase()}`;
16
+ }
17
+ if (!field || field === "relevance") return "";
18
+ return `${field}_${(_a = orderBy.direction) != null ? _a : "ASC"}`;
12
19
  };
13
20
  var getSearchParamsFromUrl = (urlString) => {
14
21
  if (!urlString) {
@@ -28,13 +35,13 @@ var getSearchParamsFromUrl = (urlString) => {
28
35
 
29
36
  // src/flattenBlockParams.ts
30
37
  function resolveFieldValue(field, locale = "en-us") {
31
- var _a, _b;
38
+ var _a;
32
39
  if (!field || typeof field !== "object") return field;
33
40
  if (field.type === "$block" && Array.isArray(field.value)) {
34
41
  return flattenBlockParams(field.value, locale);
35
42
  }
36
43
  if (field.locales && typeof field.locales === "object") {
37
- return (_b = (_a = field.locales[locale]) != null ? _a : Object.values(field.locales)[0]) != null ? _b : field.value;
44
+ return (_a = field.locales[locale]) != null ? _a : field.value;
38
45
  }
39
46
  return field.value;
40
47
  }
@@ -63,4 +70,4 @@ export {
63
70
  getSearchParamsFromUrl,
64
71
  flattenBlockParams
65
72
  };
66
- //# sourceMappingURL=chunk-ZABEFR2D.mjs.map
73
+ //# sourceMappingURL=chunk-RMUYFKZO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils.ts","../src/flattenBlockParams.ts"],"sourcesContent":["export const UNIFORM_SEARCH_QUERY_KEY = 'search';\nexport const UNIFORM_SEARCH_PAGE_KEY = 'page';\nexport const UNIFORM_SEARCH_PAGE_SIZE_KEY = 'pageSize';\nexport const UNIFORM_SEARCH_ORDER_BY_KEY = 'orderBy';\nexport const FIRST_PAGE = 0;\nexport const DEFAULT_PAGE_SIZE = 10;\n","import type { OrderBy } from './types';\n\nexport const buildOrderByQuery = (orderBy: OrderBy) => {\n const field = orderBy.field as unknown;\n // sortByConfig produces an object value: { field: string; direction: 'asc' | 'desc' }\n if (field && typeof field === 'object' && 'field' in (field as object)) {\n const sv = field as { field: string; direction: string };\n return `${sv.field}_${sv.direction.toUpperCase()}`;\n }\n // Plain string \"relevance\" or missing direction → no sort\n if (!field || field === 'relevance') return '';\n return `${field}_${orderBy.direction ?? 'ASC'}`;\n};\n\nexport const getSearchParamsFromUrl = (urlString: string): Record<string, string | string[]> => {\n if (!urlString) {\n return {};\n }\n\n const url = new URL(urlString);\n const params: Record<string, string | string[]> = {};\n\n url.searchParams.forEach((value, key) => {\n if (params[key]) {\n params[key] = Array.isArray(params[key])\n ? [...(params[key] as string[]), value]\n : [params[key] as string, value];\n } else {\n params[key] = value;\n }\n });\n\n return params;\n};\n","type RawField = {\n type?: string;\n value?: unknown;\n locales?: Record<string, unknown>;\n};\n\ntype RawBlockItem = {\n type?: string;\n fields?: Record<string, RawField>;\n};\n\nfunction resolveFieldValue(field: RawField, locale = 'en-us'): unknown {\n if (!field || typeof field !== 'object') return field;\n\n if (field.type === '$block' && Array.isArray(field.value)) {\n return flattenBlockParams(field.value as RawBlockItem[], locale);\n }\n\n if (field.locales && typeof field.locales === 'object') {\n return field.locales[locale] ?? field.value;\n }\n\n return field.value;\n}\n\nexport function flattenBlockParams<T = Record<string, unknown>>(\n items: unknown,\n locale = 'en-us'\n): T[] {\n if (!Array.isArray(items)) return [];\n return items.map(item => {\n const result: Record<string, unknown> = {};\n const blockItem = item as RawBlockItem;\n if (blockItem.fields) {\n for (const [key, field] of Object.entries(blockItem.fields)) {\n result[key] = resolveFieldValue(field, locale);\n }\n }\n return result as T;\n });\n}\n"],"mappings":";AAAO,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AAChC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AACpC,IAAM,aAAa;AACnB,IAAM,oBAAoB;;;ACH1B,IAAM,oBAAoB,CAAC,YAAqB;AAFvD;AAGE,QAAM,QAAQ,QAAQ;AAEtB,MAAI,SAAS,OAAO,UAAU,YAAY,WAAY,OAAkB;AACtE,UAAM,KAAK;AACX,WAAO,GAAG,GAAG,KAAK,IAAI,GAAG,UAAU,YAAY,CAAC;AAAA,EAClD;AAEA,MAAI,CAAC,SAAS,UAAU,YAAa,QAAO;AAC5C,SAAO,GAAG,KAAK,KAAI,aAAQ,cAAR,YAAqB,KAAK;AAC/C;AAEO,IAAM,yBAAyB,CAAC,cAAyD;AAC9F,MAAI,CAAC,WAAW;AACd,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,MAAM,IAAI,IAAI,SAAS;AAC7B,QAAM,SAA4C,CAAC;AAEnD,MAAI,aAAa,QAAQ,CAAC,OAAO,QAAQ;AACvC,QAAI,OAAO,GAAG,GAAG;AACf,aAAO,GAAG,IAAI,MAAM,QAAQ,OAAO,GAAG,CAAC,IACnC,CAAC,GAAI,OAAO,GAAG,GAAgB,KAAK,IACpC,CAAC,OAAO,GAAG,GAAa,KAAK;AAAA,IACnC,OAAO;AACL,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;ACtBA,SAAS,kBAAkB,OAAiB,SAAS,SAAkB;AAXvE;AAYE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,MAAI,MAAM,SAAS,YAAY,MAAM,QAAQ,MAAM,KAAK,GAAG;AACzD,WAAO,mBAAmB,MAAM,OAAyB,MAAM;AAAA,EACjE;AAEA,MAAI,MAAM,WAAW,OAAO,MAAM,YAAY,UAAU;AACtD,YAAO,WAAM,QAAQ,MAAM,MAApB,YAAyB,MAAM;AAAA,EACxC;AAEA,SAAO,MAAM;AACf;AAEO,SAAS,mBACd,OACA,SAAS,SACJ;AACL,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,SAAO,MAAM,IAAI,UAAQ;AACvB,UAAM,SAAkC,CAAC;AACzC,UAAM,YAAY;AAClB,QAAI,UAAU,QAAQ;AACpB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,MAAM,GAAG;AAC3D,eAAO,GAAG,IAAI,kBAAkB,OAAO,MAAM;AAAA,MAC/C;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;","names":[]}
@@ -0,0 +1,99 @@
1
+ type SearchCollection = 'entries' | 'compositions' | 'assets';
2
+ type SearchResponseHighlightObject = {
3
+ snippet?: string;
4
+ snippets?: string[];
5
+ value?: unknown;
6
+ values?: unknown[];
7
+ matched_tokens?: unknown;
8
+ };
9
+ type SearchResponseHighlight = {
10
+ [field: string]: SearchResponseHighlight | SearchResponseHighlightObject;
11
+ };
12
+ type SearchHit = {
13
+ id: string;
14
+ _collection: SearchCollection;
15
+ /** Per-field highlights (nested by field name). Present only when there are matches. */
16
+ _highlight?: SearchResponseHighlight;
17
+ /** Per-field highlights (array form, each entry carries a `field`). */
18
+ _highlights?: unknown[];
19
+ [key: string]: unknown;
20
+ };
21
+ type Pagination<Item> = {
22
+ items: Item[];
23
+ page: number;
24
+ perPage: number;
25
+ total: number;
26
+ totalPages: number;
27
+ };
28
+ type CollectionResult = {
29
+ data: Pagination<SearchHit>;
30
+ facets: Facets;
31
+ };
32
+ type FilterByItem = {
33
+ title: string;
34
+ value: string;
35
+ };
36
+ type FacetBy = {
37
+ type: 'select' | 'multiSelect' | 'range';
38
+ title: string;
39
+ fieldKey: string;
40
+ };
41
+ type OrderBy = {
42
+ title: string;
43
+ field: string;
44
+ direction: 'ASC' | 'DESC';
45
+ };
46
+ type PageSize = {
47
+ size: number;
48
+ /** When set, this size is the initial page size (first `default` wins). */
49
+ default?: boolean;
50
+ };
51
+ type Facets = {
52
+ [key: string]: {
53
+ [key: string]: number;
54
+ };
55
+ };
56
+ type FilterQuery = {
57
+ [K: string]: string[];
58
+ };
59
+ type SearchItemConfig = {
60
+ source: string;
61
+ type: string;
62
+ nodeId?: string;
63
+ urlTemplate?: string;
64
+ tokenMapping?: Record<string, string>;
65
+ };
66
+
67
+ interface SearchParams {
68
+ projectId?: string;
69
+ page?: number;
70
+ perPage?: number;
71
+ filters?: Record<string, unknown>;
72
+ baseFilterBy?: string;
73
+ facetBy?: string;
74
+ queryBy?: string;
75
+ search?: string;
76
+ orderBy?: string;
77
+ locale?: string;
78
+ maxFacetValues?: number;
79
+ /** Max typos tolerated per word: 0, 1, or 2. (Typesense `num_typos`.) */
80
+ maxTypos?: number;
81
+ /** Minimum word length before a single typo is allowed. (Typesense `min_len_1typo`.) */
82
+ minLengthFor1Typo?: number;
83
+ /** Minimum word length before two typos are allowed. (Typesense `min_len_2typo`.) */
84
+ minLengthFor2Typos?: number;
85
+ /** If a query returns fewer than this many results, retry with more typos. (Typesense `typo_tokens_threshold`.) */
86
+ typoFallbackThreshold?: number;
87
+ /** Rank exact matches above typo-corrected ones. (Typesense `prioritize_exact_match`.) */
88
+ prioritizeExactMatch?: boolean;
89
+ }
90
+ interface SearchClient {
91
+ performSearch(params: SearchParams): Promise<CollectionResult>;
92
+ }
93
+ declare function createSearchClient(config: {
94
+ apiUrl: string;
95
+ apiKey?: string;
96
+ projectId?: string;
97
+ }): SearchClient;
98
+
99
+ export { type CollectionResult as C, type FacetBy as F, type OrderBy as O, type PageSize as P, type SearchHit as S, type Facets as a, type FilterByItem as b, type FilterQuery as c, type Pagination as d, type SearchClient as e, type SearchCollection as f, type SearchItemConfig as g, type SearchParams as h, type SearchResponseHighlight as i, type SearchResponseHighlightObject as j, createSearchClient as k };
@@ -0,0 +1,99 @@
1
+ type SearchCollection = 'entries' | 'compositions' | 'assets';
2
+ type SearchResponseHighlightObject = {
3
+ snippet?: string;
4
+ snippets?: string[];
5
+ value?: unknown;
6
+ values?: unknown[];
7
+ matched_tokens?: unknown;
8
+ };
9
+ type SearchResponseHighlight = {
10
+ [field: string]: SearchResponseHighlight | SearchResponseHighlightObject;
11
+ };
12
+ type SearchHit = {
13
+ id: string;
14
+ _collection: SearchCollection;
15
+ /** Per-field highlights (nested by field name). Present only when there are matches. */
16
+ _highlight?: SearchResponseHighlight;
17
+ /** Per-field highlights (array form, each entry carries a `field`). */
18
+ _highlights?: unknown[];
19
+ [key: string]: unknown;
20
+ };
21
+ type Pagination<Item> = {
22
+ items: Item[];
23
+ page: number;
24
+ perPage: number;
25
+ total: number;
26
+ totalPages: number;
27
+ };
28
+ type CollectionResult = {
29
+ data: Pagination<SearchHit>;
30
+ facets: Facets;
31
+ };
32
+ type FilterByItem = {
33
+ title: string;
34
+ value: string;
35
+ };
36
+ type FacetBy = {
37
+ type: 'select' | 'multiSelect' | 'range';
38
+ title: string;
39
+ fieldKey: string;
40
+ };
41
+ type OrderBy = {
42
+ title: string;
43
+ field: string;
44
+ direction: 'ASC' | 'DESC';
45
+ };
46
+ type PageSize = {
47
+ size: number;
48
+ /** When set, this size is the initial page size (first `default` wins). */
49
+ default?: boolean;
50
+ };
51
+ type Facets = {
52
+ [key: string]: {
53
+ [key: string]: number;
54
+ };
55
+ };
56
+ type FilterQuery = {
57
+ [K: string]: string[];
58
+ };
59
+ type SearchItemConfig = {
60
+ source: string;
61
+ type: string;
62
+ nodeId?: string;
63
+ urlTemplate?: string;
64
+ tokenMapping?: Record<string, string>;
65
+ };
66
+
67
+ interface SearchParams {
68
+ projectId?: string;
69
+ page?: number;
70
+ perPage?: number;
71
+ filters?: Record<string, unknown>;
72
+ baseFilterBy?: string;
73
+ facetBy?: string;
74
+ queryBy?: string;
75
+ search?: string;
76
+ orderBy?: string;
77
+ locale?: string;
78
+ maxFacetValues?: number;
79
+ /** Max typos tolerated per word: 0, 1, or 2. (Typesense `num_typos`.) */
80
+ maxTypos?: number;
81
+ /** Minimum word length before a single typo is allowed. (Typesense `min_len_1typo`.) */
82
+ minLengthFor1Typo?: number;
83
+ /** Minimum word length before two typos are allowed. (Typesense `min_len_2typo`.) */
84
+ minLengthFor2Typos?: number;
85
+ /** If a query returns fewer than this many results, retry with more typos. (Typesense `typo_tokens_threshold`.) */
86
+ typoFallbackThreshold?: number;
87
+ /** Rank exact matches above typo-corrected ones. (Typesense `prioritize_exact_match`.) */
88
+ prioritizeExactMatch?: boolean;
89
+ }
90
+ interface SearchClient {
91
+ performSearch(params: SearchParams): Promise<CollectionResult>;
92
+ }
93
+ declare function createSearchClient(config: {
94
+ apiUrl: string;
95
+ apiKey?: string;
96
+ projectId?: string;
97
+ }): SearchClient;
98
+
99
+ export { type CollectionResult as C, type FacetBy as F, type OrderBy as O, type PageSize as P, type SearchHit as S, type Facets as a, type FilterByItem as b, type FilterQuery as c, type Pagination as d, type SearchClient as e, type SearchCollection as f, type SearchItemConfig as g, type SearchParams as h, type SearchResponseHighlight as i, type SearchResponseHighlightObject as j, createSearchClient as k };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OrderBy } from './client-DrL-8TfY.mjs';
2
- export { C as CollectionResult, F as Facets, a as FilterBy, b as FilterByItem, c as FilterQuery, M as MultiSearchResult, P as PageSize, d as Pagination, S as SearchClient, e as SearchCollection, f as SearchHit, g as SearchParams, h as createSearchClient } from './client-DrL-8TfY.mjs';
1
+ import { O as OrderBy, S as SearchHit } from './client-DOew5kqb.mjs';
2
+ export { C as CollectionResult, F as FacetBy, a as Facets, b as FilterByItem, c as FilterQuery, P as PageSize, d as Pagination, e as SearchClient, f as SearchCollection, g as SearchItemConfig, h as SearchParams, i as SearchResponseHighlight, j as SearchResponseHighlightObject, k as createSearchClient } from './client-DOew5kqb.mjs';
3
3
 
4
4
  declare const UNIFORM_SEARCH_QUERY_KEY = "search";
5
5
  declare const UNIFORM_SEARCH_PAGE_KEY = "page";
@@ -11,6 +11,21 @@ declare const DEFAULT_PAGE_SIZE = 10;
11
11
  declare const buildOrderByQuery: (orderBy: OrderBy) => string;
12
12
  declare const getSearchParamsFromUrl: (urlString: string) => Record<string, string | string[]>;
13
13
 
14
+ type HighlightMatch = {
15
+ html?: string;
16
+ values?: unknown[];
17
+ };
18
+ /**
19
+ * Resolve the highlight (if any) for a field on a search hit.
20
+ *
21
+ * @param hit A hit returned by `performSearch`.
22
+ * @param fieldPath The field name (dot-path for nested fields, e.g. `author.name`).
23
+ * @param fieldValue The hit's raw value for the field (used to align array highlights).
24
+ * @returns `{ html }` for a highlighted snippet, `{ values }` for per-item array
25
+ * highlights, or `undefined` when the field wasn't matched.
26
+ */
27
+ declare const getHighlightMatch: (hit: SearchHit, fieldPath: string, fieldValue?: unknown) => HighlightMatch | undefined;
28
+
14
29
  declare function flattenBlockParams<T = Record<string, unknown>>(items: unknown, locale?: string): T[];
15
30
 
16
- export { DEFAULT_PAGE_SIZE, FIRST_PAGE, OrderBy, UNIFORM_SEARCH_ORDER_BY_KEY, UNIFORM_SEARCH_PAGE_KEY, UNIFORM_SEARCH_PAGE_SIZE_KEY, UNIFORM_SEARCH_QUERY_KEY, buildOrderByQuery, flattenBlockParams, getSearchParamsFromUrl };
31
+ export { DEFAULT_PAGE_SIZE, FIRST_PAGE, type HighlightMatch, OrderBy, SearchHit, UNIFORM_SEARCH_ORDER_BY_KEY, UNIFORM_SEARCH_PAGE_KEY, UNIFORM_SEARCH_PAGE_SIZE_KEY, UNIFORM_SEARCH_QUERY_KEY, buildOrderByQuery, flattenBlockParams, getHighlightMatch, getSearchParamsFromUrl };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { O as OrderBy } from './client-DrL-8TfY.js';
2
- export { C as CollectionResult, F as Facets, a as FilterBy, b as FilterByItem, c as FilterQuery, M as MultiSearchResult, P as PageSize, d as Pagination, S as SearchClient, e as SearchCollection, f as SearchHit, g as SearchParams, h as createSearchClient } from './client-DrL-8TfY.js';
1
+ import { O as OrderBy, S as SearchHit } from './client-DOew5kqb.js';
2
+ export { C as CollectionResult, F as FacetBy, a as Facets, b as FilterByItem, c as FilterQuery, P as PageSize, d as Pagination, e as SearchClient, f as SearchCollection, g as SearchItemConfig, h as SearchParams, i as SearchResponseHighlight, j as SearchResponseHighlightObject, k as createSearchClient } from './client-DOew5kqb.js';
3
3
 
4
4
  declare const UNIFORM_SEARCH_QUERY_KEY = "search";
5
5
  declare const UNIFORM_SEARCH_PAGE_KEY = "page";
@@ -11,6 +11,21 @@ declare const DEFAULT_PAGE_SIZE = 10;
11
11
  declare const buildOrderByQuery: (orderBy: OrderBy) => string;
12
12
  declare const getSearchParamsFromUrl: (urlString: string) => Record<string, string | string[]>;
13
13
 
14
+ type HighlightMatch = {
15
+ html?: string;
16
+ values?: unknown[];
17
+ };
18
+ /**
19
+ * Resolve the highlight (if any) for a field on a search hit.
20
+ *
21
+ * @param hit A hit returned by `performSearch`.
22
+ * @param fieldPath The field name (dot-path for nested fields, e.g. `author.name`).
23
+ * @param fieldValue The hit's raw value for the field (used to align array highlights).
24
+ * @returns `{ html }` for a highlighted snippet, `{ values }` for per-item array
25
+ * highlights, or `undefined` when the field wasn't matched.
26
+ */
27
+ declare const getHighlightMatch: (hit: SearchHit, fieldPath: string, fieldValue?: unknown) => HighlightMatch | undefined;
28
+
14
29
  declare function flattenBlockParams<T = Record<string, unknown>>(items: unknown, locale?: string): T[];
15
30
 
16
- export { DEFAULT_PAGE_SIZE, FIRST_PAGE, OrderBy, UNIFORM_SEARCH_ORDER_BY_KEY, UNIFORM_SEARCH_PAGE_KEY, UNIFORM_SEARCH_PAGE_SIZE_KEY, UNIFORM_SEARCH_QUERY_KEY, buildOrderByQuery, flattenBlockParams, getSearchParamsFromUrl };
31
+ export { DEFAULT_PAGE_SIZE, FIRST_PAGE, type HighlightMatch, OrderBy, SearchHit, UNIFORM_SEARCH_ORDER_BY_KEY, UNIFORM_SEARCH_PAGE_KEY, UNIFORM_SEARCH_PAGE_SIZE_KEY, UNIFORM_SEARCH_QUERY_KEY, buildOrderByQuery, flattenBlockParams, getHighlightMatch, getSearchParamsFromUrl };
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ __export(src_exports, {
29
29
  buildOrderByQuery: () => buildOrderByQuery,
30
30
  createSearchClient: () => createSearchClient,
31
31
  flattenBlockParams: () => flattenBlockParams,
32
+ getHighlightMatch: () => getHighlightMatch,
32
33
  getSearchParamsFromUrl: () => getSearchParamsFromUrl
33
34
  });
34
35
  module.exports = __toCommonJS(src_exports);
@@ -42,7 +43,12 @@ var FIRST_PAGE = 0;
42
43
  var DEFAULT_PAGE_SIZE = 10;
43
44
 
44
45
  // src/client.ts
46
+ var emptyResult = () => ({
47
+ data: { items: [], total: 0, page: 0, perPage: 10, totalPages: 0 },
48
+ facets: {}
49
+ });
45
50
  function createSearchClient(config) {
51
+ const baseUrl = config.apiUrl.replace(/\/+$/, "");
46
52
  return {
47
53
  async performSearch(params) {
48
54
  const headers = {
@@ -56,19 +62,19 @@ function createSearchClient(config) {
56
62
  projectId: params.projectId || config.projectId
57
63
  };
58
64
  try {
59
- const response = await fetch(`${config.apiUrl}/api/search`, {
65
+ const response = await fetch(`${baseUrl}/api/search`, {
60
66
  method: "POST",
61
67
  headers,
62
68
  body: JSON.stringify(body)
63
69
  });
64
70
  if (!response.ok) {
65
- console.error(`[alex] Search API error: ${response.status}`);
66
- return { results: {} };
71
+ console.error(`Search API error: ${response.status}`);
72
+ return emptyResult();
67
73
  }
68
74
  return response.json();
69
75
  } catch (error) {
70
- console.error("[alex] Search fetch error:", error);
71
- return { results: {} };
76
+ console.error("Search fetch error:", error);
77
+ return emptyResult();
72
78
  }
73
79
  }
74
80
  };
@@ -76,7 +82,14 @@ function createSearchClient(config) {
76
82
 
77
83
  // src/utils.ts
78
84
  var buildOrderByQuery = (orderBy) => {
79
- return `${orderBy.field}_${orderBy.direction}`;
85
+ var _a;
86
+ const field = orderBy.field;
87
+ if (field && typeof field === "object" && "field" in field) {
88
+ const sv = field;
89
+ return `${sv.field}_${sv.direction.toUpperCase()}`;
90
+ }
91
+ if (!field || field === "relevance") return "";
92
+ return `${field}_${(_a = orderBy.direction) != null ? _a : "ASC"}`;
80
93
  };
81
94
  var getSearchParamsFromUrl = (urlString) => {
82
95
  if (!urlString) {
@@ -94,15 +107,59 @@ var getSearchParamsFromUrl = (urlString) => {
94
107
  return params;
95
108
  };
96
109
 
110
+ // src/highlights.ts
111
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
112
+ var isHighlightLeaf = (value) => isPlainObject(value) && ("snippet" in value || "snippets" in value || "value" in value || "values" in value);
113
+ var unknownToString = (value) => {
114
+ if (value === null || value === void 0) return "";
115
+ if (typeof value === "string") return value;
116
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
117
+ return String(value);
118
+ }
119
+ try {
120
+ return JSON.stringify(value);
121
+ } catch (e) {
122
+ return "";
123
+ }
124
+ };
125
+ var getHighlightMatch = (hit, fieldPath, fieldValue) => {
126
+ const fieldHighlight = Array.isArray(hit._highlights) ? hit._highlights.find((highlight) => isPlainObject(highlight) && highlight.field === fieldPath) : void 0;
127
+ if (isPlainObject(fieldHighlight)) {
128
+ const snippets = fieldHighlight.snippets;
129
+ if (Array.isArray(snippets)) return { html: snippets.map(unknownToString).join(", ") };
130
+ const snippet = unknownToString(fieldHighlight.snippet);
131
+ if (snippet) return { html: snippet };
132
+ const value = unknownToString(fieldHighlight.value);
133
+ if (value) return { html: value };
134
+ }
135
+ const highlightValue = fieldPath.split(".").reduce((current, pathPart) => {
136
+ if (!isPlainObject(current)) return void 0;
137
+ return current[pathPart];
138
+ }, hit._highlight);
139
+ if (isHighlightLeaf(highlightValue)) {
140
+ if (Array.isArray(highlightValue.snippets)) {
141
+ return { html: highlightValue.snippets.map(unknownToString).join(", ") };
142
+ }
143
+ const snippet = unknownToString(highlightValue.snippet);
144
+ if (snippet) return { html: snippet };
145
+ if (Array.isArray(highlightValue.values)) return { values: highlightValue.values };
146
+ if ("value" in highlightValue) return { html: unknownToString(highlightValue.value) };
147
+ }
148
+ if (Array.isArray(fieldValue) && Array.isArray(highlightValue)) {
149
+ return { values: highlightValue };
150
+ }
151
+ return void 0;
152
+ };
153
+
97
154
  // src/flattenBlockParams.ts
98
155
  function resolveFieldValue(field, locale = "en-us") {
99
- var _a, _b;
156
+ var _a;
100
157
  if (!field || typeof field !== "object") return field;
101
158
  if (field.type === "$block" && Array.isArray(field.value)) {
102
159
  return flattenBlockParams(field.value, locale);
103
160
  }
104
161
  if (field.locales && typeof field.locales === "object") {
105
- return (_b = (_a = field.locales[locale]) != null ? _a : Object.values(field.locales)[0]) != null ? _b : field.value;
162
+ return (_a = field.locales[locale]) != null ? _a : field.value;
106
163
  }
107
164
  return field.value;
108
165
  }
@@ -130,6 +187,7 @@ function flattenBlockParams(items, locale = "en-us") {
130
187
  buildOrderByQuery,
131
188
  createSearchClient,
132
189
  flattenBlockParams,
190
+ getHighlightMatch,
133
191
  getSearchParamsFromUrl
134
192
  });
135
193
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/client.ts","../src/utils.ts","../src/flattenBlockParams.ts"],"sourcesContent":["export * from './types';\nexport * from './constants';\nexport * from './client';\nexport * from './utils';\nexport * from './flattenBlockParams';\n","export const UNIFORM_SEARCH_QUERY_KEY = 'search';\nexport const UNIFORM_SEARCH_PAGE_KEY = 'page';\nexport const UNIFORM_SEARCH_PAGE_SIZE_KEY = 'pageSize';\nexport const UNIFORM_SEARCH_ORDER_BY_KEY = 'orderBy';\nexport const FIRST_PAGE = 0;\nexport const DEFAULT_PAGE_SIZE = 10;\n","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","import type { OrderBy } from './types';\n\nexport const buildOrderByQuery = (orderBy: OrderBy) => {\n return `${orderBy.field}_${orderBy.direction}`;\n};\n\nexport const getSearchParamsFromUrl = (urlString: string): Record<string, string | string[]> => {\n if (!urlString) {\n return {};\n }\n\n const url = new URL(urlString);\n const params: Record<string, string | string[]> = {};\n\n url.searchParams.forEach((value, key) => {\n if (params[key]) {\n params[key] = Array.isArray(params[key])\n ? [...(params[key] as string[]), value]\n : [params[key] as string, value];\n } else {\n params[key] = value;\n }\n });\n\n return params;\n};\n","type RawField = {\n type?: string;\n value?: unknown;\n locales?: Record<string, unknown>;\n};\n\ntype RawBlockItem = {\n type?: string;\n fields?: Record<string, RawField>;\n};\n\nfunction resolveFieldValue(field: RawField, locale = 'en-us'): unknown {\n if (!field || typeof field !== 'object') return field;\n\n if (field.type === '$block' && Array.isArray(field.value)) {\n return flattenBlockParams(field.value as RawBlockItem[], locale);\n }\n\n if (field.locales && typeof field.locales === 'object') {\n return field.locales[locale] ?? Object.values(field.locales)[0] ?? field.value;\n }\n\n return field.value;\n}\n\nexport function flattenBlockParams<T = Record<string, unknown>>(\n items: unknown,\n locale = 'en-us'\n): T[] {\n if (!Array.isArray(items)) return [];\n return items.map(item => {\n const result: Record<string, unknown> = {};\n const blockItem = item as RawBlockItem;\n if (blockItem.fields) {\n for (const [key, field] of Object.entries(blockItem.fields)) {\n result[key] = resolveFieldValue(field, locale);\n }\n }\n return result as T;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AAChC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AACpC,IAAM,aAAa;AACnB,IAAM,oBAAoB;;;ACY1B,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;;;ACrDO,IAAM,oBAAoB,CAAC,YAAqB;AACrD,SAAO,GAAG,QAAQ,KAAK,IAAI,QAAQ,SAAS;AAC9C;AAEO,IAAM,yBAAyB,CAAC,cAAyD;AAC9F,MAAI,CAAC,WAAW;AACd,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,MAAM,IAAI,IAAI,SAAS;AAC7B,QAAM,SAA4C,CAAC;AAEnD,MAAI,aAAa,QAAQ,CAAC,OAAO,QAAQ;AACvC,QAAI,OAAO,GAAG,GAAG;AACf,aAAO,GAAG,IAAI,MAAM,QAAQ,OAAO,GAAG,CAAC,IACnC,CAAC,GAAI,OAAO,GAAG,GAAgB,KAAK,IACpC,CAAC,OAAO,GAAG,GAAa,KAAK;AAAA,IACnC,OAAO;AACL,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;ACdA,SAAS,kBAAkB,OAAiB,SAAS,SAAkB;AAXvE;AAYE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,MAAI,MAAM,SAAS,YAAY,MAAM,QAAQ,MAAM,KAAK,GAAG;AACzD,WAAO,mBAAmB,MAAM,OAAyB,MAAM;AAAA,EACjE;AAEA,MAAI,MAAM,WAAW,OAAO,MAAM,YAAY,UAAU;AACtD,YAAO,iBAAM,QAAQ,MAAM,MAApB,YAAyB,OAAO,OAAO,MAAM,OAAO,EAAE,CAAC,MAAvD,YAA4D,MAAM;AAAA,EAC3E;AAEA,SAAO,MAAM;AACf;AAEO,SAAS,mBACd,OACA,SAAS,SACJ;AACL,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,SAAO,MAAM,IAAI,UAAQ;AACvB,UAAM,SAAkC,CAAC;AACzC,UAAM,YAAY;AAClB,QAAI,UAAU,QAAQ;AACpB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,MAAM,GAAG;AAC3D,eAAO,GAAG,IAAI,kBAAkB,OAAO,MAAM;AAAA,MAC/C;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/client.ts","../src/utils.ts","../src/highlights.ts","../src/flattenBlockParams.ts"],"sourcesContent":["export * from './types';\nexport * from './constants';\nexport * from './client';\nexport * from './utils';\nexport * from './highlights';\nexport * from './flattenBlockParams';\n","export const UNIFORM_SEARCH_QUERY_KEY = 'search';\nexport const UNIFORM_SEARCH_PAGE_KEY = 'page';\nexport const UNIFORM_SEARCH_PAGE_SIZE_KEY = 'pageSize';\nexport const UNIFORM_SEARCH_ORDER_BY_KEY = 'orderBy';\nexport const FIRST_PAGE = 0;\nexport const DEFAULT_PAGE_SIZE = 10;\n","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 { OrderBy } from './types';\n\nexport const buildOrderByQuery = (orderBy: OrderBy) => {\n const field = orderBy.field as unknown;\n // sortByConfig produces an object value: { field: string; direction: 'asc' | 'desc' }\n if (field && typeof field === 'object' && 'field' in (field as object)) {\n const sv = field as { field: string; direction: string };\n return `${sv.field}_${sv.direction.toUpperCase()}`;\n }\n // Plain string \"relevance\" or missing direction → no sort\n if (!field || field === 'relevance') return '';\n return `${field}_${orderBy.direction ?? 'ASC'}`;\n};\n\nexport const getSearchParamsFromUrl = (urlString: string): Record<string, string | string[]> => {\n if (!urlString) {\n return {};\n }\n\n const url = new URL(urlString);\n const params: Record<string, string | string[]> = {};\n\n url.searchParams.forEach((value, key) => {\n if (params[key]) {\n params[key] = Array.isArray(params[key])\n ? [...(params[key] as string[]), value]\n : [params[key] as string, value];\n } else {\n params[key] = value;\n }\n });\n\n return params;\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","type RawField = {\n type?: string;\n value?: unknown;\n locales?: Record<string, unknown>;\n};\n\ntype RawBlockItem = {\n type?: string;\n fields?: Record<string, RawField>;\n};\n\nfunction resolveFieldValue(field: RawField, locale = 'en-us'): unknown {\n if (!field || typeof field !== 'object') return field;\n\n if (field.type === '$block' && Array.isArray(field.value)) {\n return flattenBlockParams(field.value as RawBlockItem[], locale);\n }\n\n if (field.locales && typeof field.locales === 'object') {\n return field.locales[locale] ?? field.value;\n }\n\n return field.value;\n}\n\nexport function flattenBlockParams<T = Record<string, unknown>>(\n items: unknown,\n locale = 'en-us'\n): T[] {\n if (!Array.isArray(items)) return [];\n return items.map(item => {\n const result: Record<string, unknown> = {};\n const blockItem = item as RawBlockItem;\n if (blockItem.fields) {\n for (const [key, field] of Object.entries(blockItem.fields)) {\n result[key] = resolveFieldValue(field, locale);\n }\n }\n return result as T;\n });\n}\n"],"mappings":"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","names":[]}