diff --git a/app/components/searchPanel/SearchPanel.vue b/app/components/searchPanel/SearchPanel.vue index a4aac62..f5555f2 100644 --- a/app/components/searchPanel/SearchPanel.vue +++ b/app/components/searchPanel/SearchPanel.vue @@ -34,7 +34,7 @@ const { locale } = useI18n() const settings = useSettingsStore() const { unlocked } = useDevMode() const toast = useToast() -const { documentsApi } = useTypesenseClient() +const typesenseClient = useTypesenseClient() // ── Restaurar estado desde URL antes de crear los refs ───────────────────── const { query: q0, page: p0, scroll: s0, selectedId: sid0 } = useSearchUrlState() @@ -106,20 +106,17 @@ async function applyBibleStudyFilter() { isValidating.value = true try { - const res = await documentsApi.multiSearch({ - multiSearchParameters: {}, - multiSearchSearchesParameter: { - searches: [{ - collection: props.mainCollection, - q: '*', - queryBy: 'title', - filterBy: `bible_study:=${val}`, - perPage: 1, - includeFields: 'bible_study,title', - useCache: true, - cacheTtl: 3600 - }] - } + const res = await typesenseClient.multiSearch.perform({ + searches: [{ + collection: props.mainCollection, + q: '*', + query_by: 'title', + filter_by: `bible_study:=${val}`, + per_page: 1, + include_fields: 'bible_study,title', + use_cache: true, + cache_ttl: 3600 + }] }) const hit = (res?.results?.[0] as { hits?: Array<{ document: { title?: string } }> })?.hits?.[0] @@ -225,7 +222,7 @@ async function fetchDocumentWithParagraphs(docId: string) { selectedDocument.value = null selectedParagraphs.value = [] try { - const detail = await fetchDocumentDetail(documentsApi, props.mainCollection, props.paragraphsCollection, docId, controller.signal) + const detail = await fetchDocumentDetail(typesenseClient, props.mainCollection, props.paragraphsCollection, docId, controller.signal) if (seq !== detailSeq) return if (detail) { const rawParagraphs = detail.paragraphs as unknown as ParagraphDoc[] diff --git a/app/composables/useDocumentDetailFetch.ts b/app/composables/useDocumentDetailFetch.ts index bb5c60b..f146b45 100644 --- a/app/composables/useDocumentDetailFetch.ts +++ b/app/composables/useDocumentDetailFetch.ts @@ -41,7 +41,7 @@ export function useDocumentDetailFetch() { } async function fetchDocumentDetail( - documentsApi: ReturnType['documentsApi'], + typesenseClient: ReturnType, mainCollection: string, paragraphsCollection: string, docId: string, @@ -50,20 +50,17 @@ export function useDocumentDetailFetch() { const cached = getCached(mainCollection, docId) if (cached) return cached - const res = await documentsApi.multiSearch({ - multiSearchParameters: {}, - multiSearchSearchesParameter: { - searches: [{ - collection: mainCollection, - q: '*', - queryBy: 'title', - filterBy: `id:=${docId} && $${paragraphsCollection}(id: *)`, - includeFields: `*, $${paragraphsCollection}(*)`, - useCache: true, - cacheTtl: 3600 - }] - } - }, { signal }) + const res = await typesenseClient.multiSearch.perform({ + searches: [{ + collection: mainCollection, + q: '*', + query_by: 'title', + filter_by: `id:=${docId} && $${paragraphsCollection}(id: *)`, + include_fields: `*, $${paragraphsCollection}(*)`, + use_cache: true, + cache_ttl: 3600 + }] + }, {}, { abortSignal: signal }) const hit = (res?.results?.[0] as { hits?: Array<{ document: Record }> })?.hits?.[0] if (!hit) return null diff --git a/app/composables/useFilters.ts b/app/composables/useFilters.ts index 527c281..09a1009 100644 --- a/app/composables/useFilters.ts +++ b/app/composables/useFilters.ts @@ -6,7 +6,7 @@ interface BibleStudyChip { const activeBibleStudies = useState('bible-study-chips', () => []) export function useFilters() { - const { documentsApi } = useTypesenseApi() + const typesenseClient = useTypesenseClient() const toast = useToast() const bibleStudyInput = ref(null) @@ -25,20 +25,17 @@ export function useFilters() { isValidating.value = true try { - const res = await documentsApi.multiSearch({ - multiSearchParameters: {}, - multiSearchSearchesParameter: { - searches: [{ - collection: mainCollection, - q: '*', - queryBy: 'title', - filterBy: `bible_study:=${val}`, - perPage: 1, - includeFields: 'bible_study,title', - useCache: true, - cacheTtl: 3600 - }] - } + const res = await typesenseClient.multiSearch.perform({ + searches: [{ + collection: mainCollection, + q: '*', + query_by: 'title', + filter_by: `bible_study:=${val}`, + per_page: 1, + include_fields: 'bible_study,title', + use_cache: true, + cache_ttl: 3600 + }] }) const hit = (res?.results?.[0] as { hits?: Array<{ document: { title?: string } }> })?.hits?.[0] diff --git a/app/composables/usePublicationFetch.ts b/app/composables/usePublicationFetch.ts index 6202ce7..da25315 100644 --- a/app/composables/usePublicationFetch.ts +++ b/app/composables/usePublicationFetch.ts @@ -49,7 +49,7 @@ export function usePublicationFetch() { const detailParagraphs = ref([]) const detailParagraphsLoading = ref(false) - const { documentsApi } = useTypesenseClient() + const typesenseClient = useTypesenseClient() const { fetchDocumentDetail } = useDocumentDetailFetch() let fetchSeq = 0 @@ -76,7 +76,7 @@ export function usePublicationFetch() { detailDocument.value = null detailParagraphs.value = [] try { - const detail = await fetchDocumentDetail(documentsApi, config.main, config.paragraphs, docId, controller.signal) + const detail = await fetchDocumentDetail(typesenseClient, config.main, config.paragraphs, docId, controller.signal) if (seq !== fetchSeq) return if (detail) { const rawParagraphs = detail.paragraphs as unknown as ParagraphDoc[] diff --git a/app/composables/useTypesenseClient.ts b/app/composables/useTypesenseClient.ts index 110b6cb..876a8dd 100644 --- a/app/composables/useTypesenseClient.ts +++ b/app/composables/useTypesenseClient.ts @@ -1,9 +1,6 @@ -/** - * Devuelve la instancia única del cliente Typesense creada por el plugin - * `typesense-client.client.ts`, en vez de construir un `Configuration` + - * 14 sub-APIs nuevos en cada llamada (lo que hace `useTypesenseApi()` crudo). - */ -export function useTypesenseClient() { +import type Client from 'typesense/Typesense/Client' + +export function useTypesenseClient(): Client { const nuxtApp = useNuxtApp() - return nuxtApp.$typesenseApi + return nuxtApp.$typesenseClient as Client } diff --git a/app/composables/useTypesenseSearch.ts b/app/composables/useTypesenseSearch.ts index 3d433ea..86b65e0 100644 --- a/app/composables/useTypesenseSearch.ts +++ b/app/composables/useTypesenseSearch.ts @@ -79,7 +79,7 @@ export interface FlatTypesenseSearchOptions { } export function useFlatTypesenseSearch>(options: FlatTypesenseSearchOptions) { - const { documentsApi } = useTypesenseClient() + const typesenseClient = useTypesenseClient() const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS const query = ref(options.initialQuery) @@ -116,24 +116,21 @@ export function useFlatTypesenseSearch>(options: const typePage = isInfinite ? (append ? currentPage.value + 1 : 1) : page try { - const multi = await documentsApi.multiSearch({ - multiSearchParameters: {}, - multiSearchSearchesParameter: { - searches: [{ - collection: options.collection, - q: exactSearch.value && q ? `"${q}"` : q || '*', - queryBy: options.queryBy, - includeFields: options.includeFields ?? '*', - filterBy: options.filterBy(), - perPage: options.pageSize(), - page: typePage, - highlightFullFields: options.queryBy, - highlightFields: options.queryBy, - highlightStartTag: '', - highlightEndTag: '' - }] - } - }, { signal }) + const multi = await typesenseClient.multiSearch.perform({ + searches: [{ + collection: options.collection, + q: exactSearch.value && q ? `"${q}"` : q || '*', + query_by: options.queryBy, + include_fields: options.includeFields ?? '*', + filter_by: options.filterBy(), + per_page: options.pageSize(), + page: typePage, + highlight_full_fields: options.queryBy, + highlight_fields: options.queryBy, + highlight_start_tag: '', + highlight_end_tag: '' + }] + }, {}, { abortSignal: signal }) if (!runner.isCurrent(seq)) return @@ -215,13 +212,13 @@ export interface TypesenseGroupedParagraphHit { } export interface TypesenseGroupedHit { - groupKey: string[] + group_key: string[] hits: TypesenseGroupedParagraphHit[] } interface GroupedSearchResponse { found: number - groupedHits?: TypesenseGroupedHit[] + grouped_hits?: TypesenseGroupedHit[] hits?: Array<{ document: Record }> } @@ -263,7 +260,7 @@ export interface GroupedTypesenseSearchOptions { const META_FIELDS = 'id,title,date,timestamp,place,city,state,country,type,slug,draft' export function useGroupedTypesenseSearch(options: GroupedTypesenseSearchOptions) { - const { documentsApi } = useTypesenseClient() + const typesenseClient = useTypesenseClient() const docMetaCache = useDocMetaCache() const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS const highlightAffixNumTokens = options.highlightAffixNumTokens ?? 15 @@ -344,8 +341,8 @@ export function useGroupedTypesenseSearch(options: GroupedTypesenseSearchOptions function browseFilterBy() { const base = options.browseFilterBy ? options.browseFilterBy() : options.filterBy() return options.isUnlocked() - ? base - : `${base} && private:=false` + ? `${base} && has_paragraphs:=true` + : `${base} && private:=false && has_paragraphs:=true` } const runner = createAbortableRunner() @@ -367,33 +364,30 @@ export function useGroupedTypesenseSearch(options: GroupedTypesenseSearchOptions try { const shouldSortByDate = sortMode.value === 'date' && q.trim() - const multi = await documentsApi.multiSearch({ - multiSearchParameters: {}, - multiSearchSearchesParameter: { - searches: [{ - collection: options.paragraphsCollection, - q: exactSearch.value && q ? `"${q}"` : q || '*', - queryBy: options.queryBy, - filterBy: searchFilterBy(), - ...(shouldSortByDate ? { sortBy: `$${options.mainCollection}(timestamp:desc)` } : {}), - perPage: options.pageSize(), - page: typePage, - highlightFullFields: options.queryBy, - highlightFields: options.queryBy, - highlightStartTag: '', - highlightEndTag: '', - highlightAffixNumTokens, - groupBy: options.groupByField, - includeFields: `*, $${options.mainCollection}(${META_FIELDS})` - }] - } - }, { signal }) + const multi = await typesenseClient.multiSearch.perform({ + searches: [{ + collection: options.paragraphsCollection, + q: exactSearch.value && q ? `"${q}"` : q || '*', + query_by: options.queryBy, + filter_by: searchFilterBy(), + ...(shouldSortByDate ? { sort_by: `$${options.mainCollection}(timestamp:desc)` } : {}), + per_page: options.pageSize(), + page: typePage, + highlight_full_fields: options.queryBy, + highlight_fields: options.queryBy, + highlight_start_tag: '', + highlight_end_tag: '', + highlight_affix_num_tokens: highlightAffixNumTokens, + group_by: options.groupByField, + include_fields: `*, $${options.mainCollection}(${META_FIELDS})` + }] + }, {}, { abortSignal: signal }) if (!runner.isCurrent(seq)) return const res = (multi?.results?.[0] ?? {}) as GroupedSearchResponse - const rawGroups = res?.groupedHits ?? [] + const rawGroups = res?.grouped_hits ?? [] const newGroups: SearchGroup[] = rawGroups.map(g => ({ - docId: g.groupKey[0]!, + docId: g.group_key[0]!, firstHit: g.hits[0]!, allHits: g.hits })) @@ -435,22 +429,20 @@ export function useGroupedTypesenseSearch(options: GroupedTypesenseSearchOptions const isInfinite = options.paginationType() === 'infinite_scroll' const typePage = isInfinite ? (append ? browsePage.value + 1 : 1) : page + console.log("testing" + browseFilterBy()) try { - const multi = await documentsApi.multiSearch({ - multiSearchParameters: {}, - multiSearchSearchesParameter: { - searches: [{ - collection: options.mainCollection, - q: '*', - queryBy: 'title', - filterBy: browseFilterBy(), - sortBy: 'timestamp:desc', - perPage: options.pageSize(), - page: typePage, - includeFields: META_FIELDS - }] - } - }, { signal }) + const multi = await typesenseClient.multiSearch.perform({ + searches: [{ + collection: options.mainCollection, + q: '*', + query_by: 'title', + filter_by: browseFilterBy(), + sort_by: 'timestamp:desc', + per_page: options.pageSize(), + page: typePage, + include_fields: META_FIELDS + }] + }, {}, { abortSignal: signal }) if (!runner.isCurrent(seq)) return const result = (multi?.results?.[0] as GroupedSearchResponse | undefined) const rawHits = result?.hits ?? [] diff --git a/app/plugins/typesense-client.ts b/app/plugins/typesense-client.ts index 0bf3f3e..b90726a 100644 --- a/app/plugins/typesense-client.ts +++ b/app/plugins/typesense-client.ts @@ -1,23 +1,23 @@ -/** - * Construye el cliente de Typesense (Configuration + 14 sub-APIs) UNA sola - * vez por carga de página, en vez de reconstruirlo en cada componente que - * llama `useTypesenseApi()` (SearchPanel.vue, entrelineas.vue, - * usePublicationFetch.ts). Se expone vía `nuxtApp.$typesenseApi` (tipado - * automáticamente por Nuxt gracias al `provide` de retorno); el acceso lo - * da el composable `useTypesenseClient()`. - * - * Sin sufijo `.client`: debe correr también en SSR porque los composables - * lo consumen de forma síncrona en `setup()` (que sí se ejecuta en SSR, - * aunque los fetches reales solo disparen en cliente) — con `.client` el - * valor provisto queda `undefined` en el render de servidor y todo revienta. - */ +import Typesense from 'typesense' + export default defineNuxtPlugin({ name: 'typesense-client', setup() { - const api = useTypesenseApi() + const config = useRuntimeConfig() + const nodes = JSON.parse(config.public.typeSenseNodes as string) + const apiKey = config.public.typeSenseApiKey as string + + const client = new Typesense.Client({ + nodes, + apiKey, + numRetries: 3, + retryIntervalSeconds: 0.5, + healthcheckIntervalSeconds: 30 + }) + return { provide: { - typesenseApi: api + typesenseClient: client } } } diff --git a/nuxt.config.ts b/nuxt.config.ts index 9aadec0..a078a70 100755 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,6 +1,6 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ - modules: ['@nuxt/eslint', '@nuxt/ui', '@vueuse/nuxt', '@nuxtjs/i18n', '@pinia/nuxt', '@sfxcode/nuxt-typesense','nuxt-driver.js'], + modules: ['@nuxt/eslint', '@nuxt/ui', '@vueuse/nuxt', '@nuxtjs/i18n', '@pinia/nuxt', 'nuxt-driver.js'], app: { head: { @@ -28,7 +28,9 @@ export default defineNuxtConfig({ feedbackMaxPerSession: Number(process.env.NUXT_PUBLIC_FEEDBACK_MAX_PER_SESSION) || 3, feedbackCooldownSec: Number(process.env.NUXT_PUBLIC_FEEDBACK_COOLDOWN_SEC) || 45, feedbackMinSeconds: Number(process.env.NUXT_PUBLIC_FEEDBACK_MIN_SECONDS) || 4, - entrelineasDevKey: process.env.NUXT_PUBLIC_ENTRELINEAS_DEV_KEY || '' + entrelineasDevKey: process.env.NUXT_PUBLIC_ENTRELINEAS_DEV_KEY || '', + typeSenseNodes: process.env.TYPESENSE_NODES || '[]', + typeSenseApiKey: process.env.NUXT_PUBLIC_TYPESENSE_API_KEY || '' } }, @@ -87,10 +89,4 @@ export default defineNuxtConfig({ optimizeTranslationDirective: false, }, }, - - typesense: { - url: process.env.NUXT_PUBLIC_TYPESENSE_URL || 'https://searchts.carpa.com', - apiKey: process.env.NUXT_PUBLIC_TYPESENSE_API_KEY || '', - clientMode: true - } }) diff --git a/package.json b/package.json index 202547b..1b82ec9 100755 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "@nuxt/ui": "^4.7.0", "@nuxtjs/i18n": "^9.5.6", "@pinia/nuxt": "^0.11.2", - "@sfxcode/nuxt-typesense": "^1.2.0", "@tanstack/table-core": "^8.21.3", "@unovis/ts": "^1.6.5", "@unovis/vue": "^1.6.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1355c2f..00584f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: '@pinia/nuxt': specifier: ^0.11.2 version: 0.11.3(magicast@0.5.2)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))) - '@sfxcode/nuxt-typesense': - specifier: ^1.2.0 - version: 1.2.0(magicast@0.5.2) '@tanstack/table-core': specifier: ^8.21.3 version: 8.21.3 @@ -2060,9 +2057,6 @@ packages: cpu: [x64] os: [win32] - '@sfxcode/nuxt-typesense@1.2.0': - resolution: {integrity: sha512-5h/nc7AL4POo/RBG+M7zl2fcK1aMED+LWuP/Ob18g9rGZa4cgArLNtp+8GwYFzGIr7aZa6fH6KM+Uuu4kbXFjQ==} - '@simple-git/args-pathspec@1.0.3': resolution: {integrity: sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==} @@ -8254,14 +8248,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.3': optional: true - '@sfxcode/nuxt-typesense@1.2.0(magicast@0.5.2)': - dependencies: - '@nuxt/kit': 4.4.5(magicast@0.5.2) - consola: 3.4.2 - defu: 6.1.7 - transitivePeerDependencies: - - magicast - '@simple-git/args-pathspec@1.0.3': {} '@simple-git/argv-parser@1.1.1':