Appearance
Typesense Integration ​
File Information ​
- Path:
server/api/typesense/search.post.ts,server/api/typesense/utils.ts,server/utils/searchSchema.ts,shared/searchFields.ts,app/composables/api/useSearchApi.ts,app/composables/core/common/useCommonSearch.ts - Purpose: Server-side Typesense search proxy and integration
Overview ​
The portal never talks to Typesense from the browser. The client builds a multi-search request (query, per-collection filter_by, sort, field weights) and posts it to the Nitro route POST /api/typesense/search. The server checks the caller's token against the backend, validates every parameter, narrows the requested fields to what the live schema indexes, runs one Typesense multi-search with the server-only API key, signs S3 paths in the hits and returns one result per collection.
This page is the map. Exact rules live in Typesense Search API, Typesense utils, Search fields and useCommonSearch.
Architecture ​
System Components ​
| Piece | Where | Role |
|---|---|---|
| Collections | Typesense | digital_assets (assets), dam_collections (collages), digital_assets_categories (folders). Nothing else is searchable (ALLOWED_COLLECTIONS). |
| Search route | server/api/typesense/search.post.ts | Auth check, validation, request assembly, response shaping |
| Client and helpers | server/api/typesense/utils.ts | Lazy Typesense client, performMultiSearch, S3 URL signing |
| Field specs | shared/searchFields.ts, server/utils/searchSchema.ts | query_by / weights / infix per collection, _eval relevance band, schema narrowing |
| Filter builder | useCommonSearch | Turns filter chips into filter_by and adds the instance visibility clause |
| Page logic | useSearch, useSearchBar, useFolderDetails, useCollageDetails | Build and send requests, paginate, merge results |
Key Features ​
1. Server-Side Search Proxy ​
- The Typesense API key is read from runtime config on the server only.
- The
Authorizationheader is required (401 if missing). The server calls the backenduserendpoint with it and requires an entry inaccessibleInstanceswhoseworkspace_idequalsworkspace_id_filter; otherwise 401. Missingrequest,workspace_id_filterorcommonSearchParamsis a 400, checked after auth. - The workspace check only gates access. The server adds no workspace or instance filter of its own; scoping comes from the client's
instance_visibilityclause.
2. Multi-Collection Search ​
- One request can name several of the three allowed collections; any other name is a 400 (
Collection "<name>" is not searchable.). query_by,query_by_weightsandinfixmay be one value for all collections or a per-collection map;filterQueryandsort_bycan also be per collection.- The search page only queries the active tab's collection. Folders and collages are skipped when an asset-only filter or asset-type filter is active (see useCommonSearch).
3. Advanced Filtering ​
Filters are Typesense filter_by strings built on the client by useCommonSearch: tags, file type, orientation, date added / last modified, permission (public or not for this portal), custom fields (portal_custom_fields) and the instance visibility clause (instance_visibility:=<brand id> && instance_visibility:!=0). The server passes filterQuery[collection] through unchanged.
4. Sorting ​
- Default asset sort is
modified_at:desc. Sortable list columns are Name, Date Modified, Date Added, File Size and File Type; for folders and collages, name maps tofolder_name/collection_nameand size/type fall back tomodified_at. - With a text query the sort is prefixed with
_text_match:desc,and the client prepends an_evalrelevance band. The server rebuilds the band from the schema only if it names a field that collection does not index. - Each clause must match
field:asc|desc; malformed sorts are a 400.
5. Pagination ​
per_pageandpagecome fromcommonSearchParams; infinite scroll requests the next page for the active tab only and appends hits.- Each collection's Typesense result (
hits,found,page,highlights) is returned as is underdata.
File Structure ​
Core Files ​
Server API Routes ​
server/api/typesense/search.post.ts- auth, validation (collections,text_match_type,query_by, weights 0-127, infixalways|fallback|off,sort_by), per-collection request assembly, error mapping.server/api/typesense/utils.ts-getTypesenseClient(lazy, so the server starts without Typesense env),performMultiSearch,signS3PathsInResults/getCachedSignedUrl(24 h presigned URLs, 23 h in-memory cache).
Composables ​
app/composables/api/useSearchApi.ts-searchAssets(payload)posts to/api/typesense/search.app/composables/core/common/useCommonSearch.ts- filter strings and per-collection filter map.
Stores ​
app/stores/search.ts- applied filter lists (search page, folder, collage), cached search data.
Configuration ​
Environment Variables ​
Read in nuxt.config.ts into runtime config. Values live in .env and are not documented here.
| Variable | Runtime key | Required |
|---|---|---|
TYPESENSE_HOST | typesenseHost | yes |
TYPESENSE_PORT | typesensePort | yes, 1-65535 |
TYPESENSE_PROTOCOL | typesenseProtocol | yes |
TYPESENSE_API_KEY | typesenseApiKey | yes |
TYPESENSE_CONNECTION_TIMEOUT | typesenseConnectionTimeout | no, default 10 s |
API_BASE_URL | public.apiBaseUrl | yes (token check); missing gives 500 |
Server Configuration ​
getTypesenseConfig() in utils.ts:
typescript
{
nodes: [{ host, port, protocol }],
apiKey,
connectionTimeoutSeconds, // TYPESENSE_CONNECTION_TIMEOUT or 10
retryIntervalSeconds: 0.1,
numRetries: 3,
healthcheckIntervalSeconds: 30,
}A missing host, port, protocol or key throws Typesense is not configured. Missing: ... on first use, not at startup.
Search Flow ​
Search Execution ​
useSearch.fetchResults(flag)builds filters, sort and field params for the active tab.useSearchApi.searchAssetsposts to/api/typesense/searchwith the user's token.- The server verifies the token and workspace, then validates the body.
- Requested fields are narrowed to the live schema (
resolveRequestedSearchFields), so a field that is not indexed yet drops out instead of failing. - Each collection gets fixed params: lowercased
q(*stays a wildcard),prefix: true,num_typos: 0,typo_tokens_threshold: 1,prioritize_exact_matchandprioritize_token_positiontrue,text_match_typedefaultmax_score. - One
multi_searchcall; results are keyed by collection and S3 paths are signed. - The client stores hits per tab and attaches
search_matchesfor the Matched In column.
Filter Building ​
See useCommonSearch for the rule table.
API Endpoints ​
Search ​
- Endpoint:
POST /api/typesense/search - Request:typescript
{ request: { q?: string, // default "*" collections: string[], // subset of ALLOWED_COLLECTIONS sort_by?: string | { [collection: string]: string }, include_fields?: { [collection: string]: string[] }, group_by?: string, group_limit?: number, }, commonSearchParams: { query_by: string | { [collection: string]: string }, query_by_weights?: string | { [collection: string]: string | Record<string, number> }, infix?: string | { [collection: string]: string | Record<string, string> }, per_page: number, page: number, sort_by?: string, text_match_type?: 'max_score' | 'max_weight' | 'sum_score', }, workspace_id_filter: string, filterQuery?: { [collection: string]: string }, } - Response:typescript
{ [collection: string]: { data: /* Typesense search result: hits, found, page, ... */ } } - Errors: 401 (no header, invalid token, workspace not accessible), 400 (missing params, bad collection,
text_match_type,query_by, weights, infix orsort_by), 500 (missingAPI_BASE_URL, Typesense failures). Full table in Typesense Search API.
Security Features ​
- Server-side API key: only the Nitro server holds
TYPESENSE_API_KEY. - Token and workspace check: every request is checked against the backend
userendpoint and the caller'saccessibleInstances. - Allow-listed collections and validated parameters: field names, weights, infix modes and sort clauses are pattern-checked before reaching Typesense.
- Portal scoping is client-built: the
instance_visibilityclause comes from the client filter; the server does not add it.
Performance Optimizations ​
- Only the active tab's collection is queried; folder item counts load afterwards, three at a time.
- Schema narrowing avoids failed requests when the index lags the field specs.
- Signed S3 URLs are cached in memory for 23 hours (URLs are valid 24 hours).
- The Typesense client is created once and reused.
Related Documentation ​
- Search & Discovery - Search feature documentation
- Search API - Typesense API endpoints
- Search Store - Search state management
- Search Components - Search UI components