Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /**
* @file search.ts
* @module api/analytics/search
*
* @summary
* Item search & lists (global and supplier-scoped) with resilient fallbacks.
*
* Design choices
* - We cache which query param the backend accepts for search (search|q|query|name)
* to avoid probing on every keystroke. Cache lives for the session.
* - We **never** silently downgrade a supplier-scoped search to global at the API level.
* If a nested endpoint is missing, callers should decide how to render that state.
* * - All functions return `[]` on any error to keep typeahead UIs functional.
* * @enterprise
* * - Session caching of working search parameter for performance
* * - Multiple search parameter support with fallback probing
* * - Supplier-scoped item fetching and searching
* * - TypeDoc documentation for item search utilities
*/
import http from '../httpClient';
import { clientFilter, normalizeItemsList } from './util';
import type { ItemRef } from './types';
// Session cache for the working search parameter name used by /api/inventory search.
let INVENTORY_SEARCH_PARAM: 'search' | 'q' | 'query' | 'name' | null = null;
/** Fetch a small list of items (generic dropdowns; may be supplier-scoped).
* Returns array of {id, name}. Empty array on errors.
* @example
* ```typescript
* const items = await getTopItems({ supplierId: 'SUP-001', limit: 30 });
* return <Select options={items} />;
* ```
*/
export async function getTopItems(opts?: { supplierId?: string; limit?: number }): Promise<ItemRef[]> {
const limit = opts?.limit ?? 20;
try {
const params: Record<string, string | number> = { limit };
if (opts?.supplierId) params.supplierId = opts.supplierId;
// Call inventory endpoint
const { data } = await http.get<unknown>('/api/inventory', { params });
return normalizeItemsList(data);
} catch {
return [];
}
}
/** Global item search (no supplier filter).
* Returns array of {id, name}. Empty array on errors.
* @example
* ```typescript
* const items = await searchItemsGlobal('widget', 50);
* return <Select options={items} />;
* ```
*/
export async function searchItemsGlobal(q: string, limit: number = 50): Promise<ItemRef[]> {
const text = q.trim();
if (!text) return [];
// Helper to call /api/inventory with a given param name.
const callWith = async (paramKey: 'search' | 'q' | 'query' | 'name'): Promise<ItemRef[]> => {
try {
const params: Record<string, string | number> = { limit };
params[paramKey] = text;
const { data } = await http.get<unknown>('/api/inventory', { params });
const rows = normalizeItemsList(data);
if (rows.length > 0) {
const narrowed = clientFilter(rows, text, limit);
if (narrowed.length > 0) {
INVENTORY_SEARCH_PARAM = paramKey; // remember what worked
}
return narrowed;
}
return [];
} catch {
return [];
}
};
// If we've learned a working key, try it first.
if (INVENTORY_SEARCH_PARAM) {
const rows = await callWith(INVENTORY_SEARCH_PARAM);
if (rows.length > 0) return rows;
}
// Probe likely parameter names in order.
for (const key of ['search', 'q', 'query', 'name'] as const) {
const rows = await callWith(key);
if (rows.length > 0) return rows;
}
return [];
}
/** Fetch items that belong to a specific supplier (strict scope).
* Returns array of {id, name}. Empty array on errors.
* @example
* ```typescript
* const items = await getItemsForSupplier('SUP-001', 100);
* return <Select options={items} />;
* ```
*/
export async function getItemsForSupplier(supplierId: string, limit: number = 500): Promise<ItemRef[]> {
if (!supplierId) return [];
// 1) Preferred nested endpoint
try {
const { data } = await http.get<unknown>(`/api/suppliers/${encodeURIComponent(supplierId)}/items`, {
params: { limit },
});
const rows = normalizeItemsList(data);
if (rows.length) return rows;
} catch {
/* continue */
}
// 2) Fallback: inventory endpoint that accepts supplierId as a filter
try {
const { data } = await http.get<unknown>('/api/inventory', { params: { supplierId, limit } });
return normalizeItemsList(data);
} catch {
return [];
}
}
/** Supplier-scoped item search (does not silently downgrade to global).
* Returns array of {id, name}. Empty array on errors.
* @example
* ```typescript
* const items = await searchItemsForSupplier('SUP-001', 'widget', 50);
* return <Select options={items} />;
* ```
*/
export async function searchItemsForSupplier(
supplierId: string,
q: string,
limit: number = 50
): Promise<ItemRef[]> {
const text = q.trim();
if (!supplierId || !text) return [];
// Helper to call /api/inventory with a given param name.
const callWith = async (paramKey: 'search' | 'q' | 'query' | 'name'): Promise<ItemRef[]> => {
try {
const params: Record<string, string | number> = { supplierId, limit };
params[paramKey] = text;
const { data } = await http.get<unknown>('/api/inventory', { params });
const rows = normalizeItemsList(data);
if (rows.length > 0) {
const narrowed = clientFilter(rows, text, limit);
if (narrowed.length > 0) {
INVENTORY_SEARCH_PARAM = paramKey;
}
return narrowed;
}
return [];
} catch {
return [];
}
};
// Try cached param first
if (INVENTORY_SEARCH_PARAM) {
const rows = await callWith(INVENTORY_SEARCH_PARAM);
if (rows.length > 0) return rows;
}
// Probe likely parameter names in order.
for (const key of ['search', 'q', 'query', 'name'] as const) {
const rows = await callWith(key);
if (rows.length > 0) return rows;
}
return [];
} |