@embedpdf/plugin-search 3.0.0-next.13 → 3.0.0-next.15

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["EngineError","EngineErrorCode","StageToken","SearchToken","SearchToken","StageToken"],"sources":["../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/search.plugin.ts"],"sourcesContent":["import type { PageObjectNumber, PluginContext } from '@embedpdf/core';\nimport { type PointIn, type TextQuad, applyPoint, boundsOfRects, pageGeometry, textQuadBounds } from '@embedpdf/core-geometry';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport { EngineError, EngineErrorCode } from '@embedpdf/engine-core/runtime';\nimport type {\n PdfQuad,\n PdfRect,\n SearchMatch,\n SearchMode,\n SearchQuery,\n SearchSlice,\n} from '@embedpdf/engine-core/runtime';\n\nimport type {\n SearchAction,\n SearchCapability,\n SearchHit,\n SearchPluginConfig,\n SearchRevealOptions,\n SearchState,\n} from './types';\n\nconst EMPTY: SearchHit[] = [];\n\n/** The browser find-bar feel: hit at the top-middle, no zoom change. */\nconst DEFAULT_REVEAL: SearchRevealOptions = { anchor: { y: 0.35 }, behavior: 'smooth' };\n\n/** Local engines reject a denied scope before the worker; cloud maps 403. */\nconst isPermissionDenied = (err: unknown): boolean =>\n (err instanceof Error && err.name === 'PermissionDenied') ||\n EngineError.is(err, EngineErrorCode.Forbidden);\n\n/**\n * The search capability: a client-driven cursor loop over the engine's\n * budgeted slices. One generation counter arbitrates: a new `search()` (or\n * `clear()`, or a document mutation via `rerun()`) supersedes the running\n * loop, which aborts its in-flight slice and stops dispatching.\n *\n * Rect conversion happens HERE, once per slice: the engine's PDF-space\n * line rects → content space through the same page geometry the selection\n * plugin uses, so search highlights and selection highlights are pixel-twins.\n *\n * Stage integration is optional and additive: with a Stage present the scan\n * starts at the current page (viewport-first — the hits the user is looking\n * at arrive in the first slice) and `next()`/`prev()`/`goTo()` reveal the\n * hit's page. Without one, navigation still works; the host scrolls.\n */\nexport function createSearchCapability(\n ctx: PluginContext<SearchState, SearchAction>,\n config: SearchPluginConfig = {},\n): SearchCapability {\n let generation = 0;\n let inflight: { abort(reason?: unknown): void } | null = null;\n\n interface PageConverter {\n pageIndex: number;\n toQuad: (q: PdfQuad) => TextQuad;\n }\n\n function hitsFromSlice(slice: SearchSlice): SearchHit[] {\n // zoom = 1: content space is scale-free; the layer applies the page\n // transform at render time (the selection idiom).\n const converters = new Map<PageObjectNumber, PageConverter | null>();\n const converterFor = (pon: PageObjectNumber): PageConverter | null => {\n let conv = converters.get(pon);\n if (conv !== undefined) return conv;\n const layout = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n if (!layout) {\n conv = null; // page vanished mid-search (delete); drop its hits\n } else {\n const { pdfToContent } = pageGeometry(\n { crop: layout.boxes.crop, rotation: layout.rotation, userUnit: layout.userUnit },\n 1,\n );\n // Engine quads carry frame-geometric slot semantics (p1..p4 =\n // upper-start, upper-end, lower-start, lower-end) — a trusted\n // producer, so the y-flip maps corners straight onto their names.\n const toPoint = (pt: { x: number; y: number }) =>\n applyPoint(pdfToContent as never, pt as PointIn<'pdf'>) as { x: number; y: number };\n conv = {\n pageIndex: layout.index,\n toQuad: (q) => ({\n upperStart: toPoint(q.p1),\n upperEnd: toPoint(q.p2),\n lowerStart: toPoint(q.p3),\n lowerEnd: toPoint(q.p4),\n }),\n };\n }\n converters.set(pon, conv);\n return conv;\n };\n\n const hits: SearchHit[] = [];\n for (const match of slice.matches) {\n const conv = converterFor(match.pageObjectNumber);\n if (!conv) continue;\n const segments = match.segments.map((segment) => {\n const quad = conv.toQuad(segment.quad);\n // `rect` is recomputed from the MAPPED quad so the pair can't drift.\n return { quad, rect: textQuadBounds(quad), advance: segment.advance };\n });\n hits.push({\n pon: match.pageObjectNumber,\n pageIndex: conv.pageIndex,\n charStart: match.charStart,\n charCount: match.charCount,\n segments,\n ...(segments.length\n ? { bounds: boundsOfRects(segments.map((s) => s.rect)) ?? undefined }\n : {}),\n ...(match.snippet ? { snippet: match.snippet } : {}),\n });\n }\n return hits;\n }\n\n /** How a collect() consumer observes the loop — the session dispatches\n * into state, findAll accumulates locally. */\n interface CollectSink {\n /** Called before the first slice AND again on a stale-cursor restart —\n * reset any accumulation. */\n onStart: () => void;\n onSlice: (hits: SearchHit[], scanned: number, total: number) => void;\n /** Superseded / aborted — the loop stops without completing. */\n isCancelled: () => boolean;\n setInflight: (pending: { abort(reason?: unknown): void } | null) => void;\n }\n\n /**\n * THE MECHANISM — one cursor loop over the engine's budgeted slices,\n * shared by the session (`search`) and the session-free service\n * (`findAll`): permission fallback ('full' → 'rects' when snippets are\n * denied, unless the caller pinned a mode), restart-once on a stale\n * cursor, rect conversion per slice. Resolves true on exhaustion, false\n * when cancelled; throws on error.\n */\n async function collect(\n query: SearchQuery,\n opts: { startPage?: PageObjectNumber; mode?: SearchMode },\n sink: CollectSink,\n ): Promise<boolean> {\n const doc = ctx.doc;\n if (!doc) return false;\n\n sink.onStart();\n\n let mode: SearchMode = opts.mode ?? 'full';\n const modePinned = opts.mode !== undefined;\n let cursor: string | undefined;\n let restarted = false;\n for (;;) {\n let slice: SearchSlice;\n try {\n const request = {\n query,\n mode,\n ...(cursor !== undefined\n ? { cursor }\n : opts.startPage !== undefined\n ? { startPage: opts.startPage }\n : {}),\n };\n const pending = doc.search.query(request);\n sink.setInflight(pending);\n slice = await pending;\n } catch (err) {\n if (sink.isCancelled()) return false; // superseded — the abort was ours\n // Snippets denied (no doc.text.copy)? Degrade to rect-only matches\n // instead of failing the whole search — find still works.\n if (!modePinned && mode === 'full' && cursor === undefined && isPermissionDenied(err)) {\n mode = 'rects';\n continue;\n }\n // A stale cursor rejects with InvalidArg — the document changed\n // under the loop, or the engine's session was recycled. Position\n // is lost, the query isn't: restart once from scratch.\n if (cursor !== undefined && !restarted && EngineError.is(err, EngineErrorCode.InvalidArg)) {\n restarted = true;\n cursor = undefined;\n sink.onStart();\n continue;\n }\n throw err;\n }\n if (sink.isCancelled()) return false;\n sink.onSlice(hitsFromSlice(slice), slice.scannedPages, slice.totalPages);\n if (slice.nextCursor === null) {\n sink.setInflight(null);\n return true;\n }\n cursor = slice.nextCursor;\n }\n }\n\n /** THE SESSION — collect() with state as the sink, generation-guarded. */\n async function runSession(query: SearchQuery, startPage?: PageObjectNumber): Promise<void> {\n const gen = ++generation;\n inflight?.abort('superseded');\n inflight = null;\n\n if (!ctx.doc) {\n ctx.dispatch({ type: 'CLEAR' });\n return;\n }\n\n // Viewport-first: begin scanning where the user is looking.\n if (startPage === undefined) {\n const stage = ctx.tryGet(StageToken);\n if (stage) startPage = ctx.document()?.pages[stage.currentPage()]?.pageObjectNumber;\n }\n\n try {\n const complete = await collect(\n query,\n { startPage },\n {\n onStart: () => ctx.dispatch({ type: 'START', query }),\n onSlice: (hits, scanned, total) => ctx.dispatch({ type: 'APPEND', hits, scanned, total }),\n isCancelled: () => gen !== generation,\n setInflight: (pending) => {\n if (gen === generation) inflight = pending;\n },\n },\n );\n if (complete && gen === generation) ctx.dispatch({ type: 'COMPLETE' });\n } catch (err) {\n if (gen !== generation) return;\n ctx.dispatch({ type: 'ERROR', message: err instanceof Error ? err.message : String(err) });\n }\n }\n\n function goTo(index: number, reveal?: SearchRevealOptions): SearchHit | null {\n const { hits } = ctx.getState();\n if (hits.length === 0) return null;\n const wrapped = ((index % hits.length) + hits.length) % hits.length;\n ctx.dispatch({ type: 'SET_ACTIVE', index: wrapped });\n const hit = hits[wrapped];\n // Positioned reveal: the HIT (not just its page) arrives at the anchor —\n // per-call override > plugin config > find-bar default. Zoom never changes.\n const arrival = { ...DEFAULT_REVEAL, ...config.reveal, ...reveal };\n ctx.tryGet(StageToken)?.reveal(hit.pageIndex, {\n rect: hit.bounds,\n anchor: arrival.anchor,\n behavior: arrival.behavior,\n });\n return hit;\n }\n\n const clear = () => {\n generation++;\n inflight?.abort('cleared');\n inflight = null;\n ctx.dispatch({ type: 'CLEAR' });\n };\n\n return {\n // The twin. Search is a pure request, so the VERBS carry no client gate\n // (the engine enforces; the ERROR state reports) — the twin exists for\n // affordance hiding. 'full' composes both capabilities because a snippet\n // reproduces document text; the session's own degradation means plain\n // `canSearch()` is exactly \"would `search()` produce hits\".\n canSearch: (mode) => {\n const sec = ctx.doc?.security;\n if (!sec) return false;\n return sec.allows('doc.text.search') && (mode !== 'full' || sec.allows('doc.text.copy'));\n },\n\n // An empty text is \"stop searching\", not a query — identical to clear().\n search: (query, exec) =>\n query.text.length === 0 ? clear() : void runSession(query, exec?.startPage),\n\n // Replay the stored query verbatim; the rescan starts viewport-first.\n rerun: () => {\n const { query, status } = ctx.getState();\n if (query && status !== 'idle') void runSession(query);\n },\n\n clear,\n\n next: () => goTo(ctx.getState().activeIndex + 1),\n prev: () => goTo(ctx.getState().activeIndex - 1),\n goTo,\n\n query: () => ctx.getState().query,\n\n findAll: async (query, opts = {}) => {\n if (!ctx.doc) return [];\n const all: SearchHit[] = [];\n const signal = opts.signal;\n // Independent lifecycle: its own abort handle, never the session's\n // generation counter — a findAll can neither supersede the user's\n // visible search nor be superseded by it.\n let pending: { abort(reason?: unknown): void } | null = null;\n const onAbort = () => pending?.abort(signal?.reason);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n await collect(\n query,\n { mode: opts.mode },\n {\n onStart: () => {\n all.length = 0;\n },\n onSlice: (hits) => all.push(...hits),\n isCancelled: () => signal?.aborted ?? false,\n setInflight: (p) => {\n pending = p;\n },\n },\n );\n if (signal?.aborted) throw signal.reason ?? new Error('findAll aborted');\n return all;\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n },\n\n status: () => ctx.getState().status,\n hits: () => ctx.getState().hits,\n hitCount: () => ctx.getState().hits.length,\n activeIndex: () => ctx.getState().activeIndex,\n activeHit: () => {\n const { hits, activeIndex } = ctx.getState();\n return activeIndex >= 0 && activeIndex < hits.length ? hits[activeIndex] : null;\n },\n hitsForPage: (pon) => {\n const { hits, hitsByPage } = ctx.getState();\n const indices = hitsByPage[pon];\n return indices?.length ? indices.map((i) => hits[i]) : EMPTY;\n },\n progress: () => ctx.getState().progress,\n errorMessage: () => ctx.getState().error,\n };\n}\n","/**\n * Keeps results honest across document mutations. Every confirmed\n * mutation (own or remote) bumps the engine's mutation sequence, which\n * invalidates search cursors AND can change what's findable (redaction,\n * page delete, import). The correct reaction is always the same: re-run\n * the current query from scratch. Reruns are coalesced — a burst of\n * events (multi-part mutation, SSE catch-up) triggers one rescan.\n */\nimport type { EffectContext } from '@embedpdf/core';\n\nimport { SearchToken } from './types';\nimport type { SearchAction, SearchState } from './types';\n\nconst RERUN_DELAY_MS = 250;\n\nexport function registerSearchEffects(ctx: EffectContext<SearchState, SearchAction>): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n const unsubscribe = doc.events.subscribe(() => {\n if (ctx.getState().status === 'idle') return;\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n ctx.get(SearchToken).rerun();\n }, RERUN_DELAY_MS);\n });\n\n ctx.cleanup(() => {\n if (timer !== null) clearTimeout(timer);\n unsubscribe();\n });\n}\n","import type { SearchAction, SearchState } from './types';\n\nexport const initialSearchState = (): SearchState => ({\n query: null,\n status: 'idle',\n hits: [],\n hitsByPage: {},\n activeIndex: -1,\n progress: { scanned: 0, total: 0 },\n error: null,\n});\n\nexport const searchReducer = (state: SearchState, action: SearchAction): SearchState => {\n switch (action.type) {\n case 'START':\n return { ...initialSearchState(), query: action.query, status: 'searching' };\n case 'APPEND': {\n if (action.hits.length === 0) {\n return { ...state, progress: { scanned: action.scanned, total: action.total } };\n }\n const hits = state.hits.concat(action.hits);\n const hitsByPage = { ...state.hitsByPage };\n for (let i = state.hits.length; i < hits.length; i++) {\n const pon = hits[i].pon;\n hitsByPage[pon] = (hitsByPage[pon] ?? []).concat(i);\n }\n return {\n ...state,\n hits,\n hitsByPage,\n // The first hit becomes active (so \"1/N\" reads right) — navigation,\n // not the stream, moves the camera.\n activeIndex: state.activeIndex === -1 ? 0 : state.activeIndex,\n progress: { scanned: action.scanned, total: action.total },\n };\n }\n case 'COMPLETE':\n return { ...state, status: 'complete' };\n case 'ERROR':\n return { ...state, status: 'error', error: action.message };\n case 'SET_ACTIVE':\n return { ...state, activeIndex: action.index };\n case 'CLEAR':\n return initialSearchState();\n default:\n return state;\n }\n};\n","import { definePlugin } from '@embedpdf/core';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { createSearchCapability } from './capability';\nimport { registerSearchEffects } from './effects';\nimport { initialSearchState, searchReducer } from './reducer';\nimport { SearchToken } from './types';\nimport type { SearchAction, SearchCapability, SearchPluginConfig, SearchState } from './types';\n\n/**\n * Document text search over `doc.search` — the engine's budgeted,\n * cursor-resumable slices. Document-scoped; no pointer handling, so it\n * needs no interaction hub. The Stage is optional: when present, scans\n * start at the current page (viewport-first) and hit navigation reveals\n * the hit's page; without it the host owns scrolling.\n */\nexport const searchPlugin = (config?: SearchPluginConfig) =>\n definePlugin<SearchState, SearchAction, SearchCapability>({\n id: 'search',\n token: SearchToken,\n scope: 'document',\n optional: [StageToken],\n initialState: initialSearchState,\n reduce: searchReducer,\n capability: (ctx) => createSearchCapability(ctx, config),\n effects: registerSearchEffects,\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["StageToken","PluginError","SearchToken","StageToken","SEARCH_DEFAULTS","SearchToken"],"sources":["../src/model.ts","../src/controller.ts","../src/search.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The search session: the query, the hits found so far (also grouped by\n * page), the active hit, and progress. Every function below is a pure\n * transition; the controller applies them with `ctx.state.update`.\n */\nimport { pageRefsEqual, type PageRef, type PluginErrorInfo } from '@embedpdf/core';\nimport type { SearchQuery } from '@embedpdf/engine-core/runtime';\n\nimport type { SearchHit, SearchProgress, SearchStatus } from './contract';\n\nexport interface SearchState {\n /** The query being searched, as the engine matches it. Null when idle. */\n readonly query: SearchQuery | null;\n readonly status: SearchStatus;\n /** The run that owns the session, so a run that lost it (superseded, cleared) leaves it alone. */\n readonly operationId: string | null;\n readonly hits: readonly SearchHit[];\n /** Page object number → the hits on that page; a page's array is replaced only when it gains hits. */\n readonly hitsByPage: Readonly<Record<number, readonly SearchHit[]>>;\n /** Index into `hits`, -1 when no hit is active. */\n readonly activeHitIndex: number;\n readonly progress: SearchProgress;\n readonly error: PluginErrorInfo | null;\n}\n\n/** The progress of no search, one object for every session that hasn't searched a page yet. */\nexport const NO_PROGRESS: SearchProgress = Object.freeze({ pagesSearched: 0, pageCount: 0 });\n\nexport const initialSearchState = (): SearchState => ({\n query: null,\n status: 'idle',\n operationId: null,\n hits: [],\n hitsByPage: {},\n activeHitIndex: -1,\n progress: NO_PROGRESS,\n error: null,\n});\n\n/** A new scan starts: everything from the previous session is dropped. */\nexport const startSession = (\n _state: SearchState,\n query: SearchQuery,\n operationId: string,\n): SearchState => ({ ...initialSearchState(), query, status: 'searching', operationId });\n\nconst sameProgress = (left: SearchProgress, right: SearchProgress): boolean =>\n left.pagesSearched === right.pagesSearched && left.pageCount === right.pageCount;\n\n/**\n * A slice of hits arrived. The first hit becomes active so \"1 of N\" reads\n * right; moving the camera is navigation's job, not the stream's. Progress\n * with the same counts keeps its object: readers, and `onProgressChanged`,\n * see a change only when the counts change.\n */\nexport function appendHits(\n state: SearchState,\n hits: readonly SearchHit[],\n slice: SearchProgress,\n): SearchState {\n const progress = sameProgress(state.progress, slice) ? state.progress : slice;\n if (hits.length === 0) return progress === state.progress ? state : { ...state, progress };\n const hitsByPage: Record<number, readonly SearchHit[]> = { ...state.hitsByPage };\n for (const hit of hits) {\n const pageObjectNumber = hit.page.objectNumber;\n hitsByPage[pageObjectNumber] = [...(hitsByPage[pageObjectNumber] ?? []), hit];\n }\n return {\n ...state,\n hits: [...state.hits, ...hits],\n hitsByPage,\n activeHitIndex: state.activeHitIndex === -1 ? 0 : state.activeHitIndex,\n progress,\n };\n}\n\nexport const completeSession = (state: SearchState): SearchState => ({\n ...state,\n status: 'complete',\n});\n\nexport const cancelSession = (state: SearchState): SearchState => ({\n ...state,\n status: 'cancelled',\n});\n\nexport const failSession = (state: SearchState, error: PluginErrorInfo): SearchState => ({\n ...state,\n status: 'error',\n error,\n});\n\nexport const setActiveHit = (state: SearchState, index: number): SearchState =>\n state.activeHitIndex === index ? state : { ...state, activeHitIndex: index };\n\nexport const clearSession = (state: SearchState): SearchState =>\n state.status === 'idle' ? state : initialSearchState();\n\n/**\n * Where `hit` is in `hits`: the same object, or else the hit that covers the\n * same characters of the same page, so a hit kept from before the search ran\n * again still finds its place. -1 when it isn't there.\n */\nexport function indexOfHit(hits: readonly SearchHit[], hit: SearchHit): number {\n const same = hits.indexOf(hit);\n if (same >= 0) return same;\n return hits.findIndex(\n (candidate) =>\n candidate.start === hit.start &&\n candidate.count === hit.count &&\n pageRefsEqual(candidate.page, hit.page),\n );\n}\n\n/**\n * The pages that have at least one hit, in document order: `pages` is the\n * document's page list, whose order moving a page changes (object numbers\n * don't follow it).\n */\nexport const pagesWithHits = (\n state: SearchState,\n pages: readonly { readonly ref: PageRef }[],\n): readonly PageRef[] =>\n pages\n .filter((page) => (state.hitsByPage[page.ref.objectNumber]?.length ?? 0) > 0)\n .map((page) => page.ref);\n","import {\n PluginError,\n isPluginError,\n toPluginError,\n toPluginErrorInfo,\n type PluginContext,\n type LatestCancellation,\n type PageRef,\n} from '@embedpdf/core';\nimport { boundsOfRects } from '@embedpdf/core-geometry';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\nimport type { SearchQuery, SearchSlice } from '@embedpdf/engine-core/runtime';\nimport type {\n SearchActiveHitChangedEvent,\n SearchCancelledEvent,\n SearchCapability,\n SearchClearedEvent,\n SearchCompletedEvent,\n SearchFailedEvent,\n SearchHit,\n SearchOptions,\n SearchProgressChangedEvent,\n SearchResult,\n SearchRevealOptions,\n SearchSettings,\n SearchStartedEvent,\n} from './contract';\nimport {\n appendHits,\n cancelSession,\n clearSession,\n completeSession,\n failSession,\n indexOfHit,\n pagesWithHits,\n setActiveHit,\n startSession,\n type SearchState,\n} from './model';\n\nconst EMPTY: readonly SearchHit[] = Object.freeze([]);\nconst EMPTY_PAGES: readonly PageRef[] = Object.freeze([]);\n\n/** Reruns after document mutations are coalesced: a burst of events triggers one rescan. */\nconst RERUN_DELAY_MS = 250;\n\n/** How a `collect()` consumer observes the loop: the session updates state, findAll accumulates. */\ninterface CollectSink {\n /** Before the first slice, and again on a stale-cursor restart: reset any accumulation. */\n onStart(): void;\n onSlice(hits: readonly SearchHit[], pagesSearched: number, pageCount: number): void;\n}\n\n/**\n * The search controller. `search` runs on a `latest` lane (a newer search\n * supersedes the older one, which can no longer publish); `findAll` runs on\n * its own signal and never touches the session. Both share `collect`, the\n * one cursor loop over the engine's budgeted slices.\n *\n * The scan's own events (started, completed, cancelled, failed) fire where\n * the scan reaches those points. The progress, active-hit and cleared events\n * are derived from state changes, so they always agree with the getters.\n *\n * The settings are read where they're used, never copied, so\n * `updateSettings({ reveal })` applies to the next move at once.\n */\nexport function createSearchController(ctx: PluginContext<SearchState, SearchSettings>) {\n const settings = ctx.settings();\n const session = ctx.latest('session');\n const started = ctx.events.source<SearchStartedEvent>();\n const progressChanged = ctx.events.source<SearchProgressChangedEvent>();\n const completed = ctx.events.source<SearchCompletedEvent>();\n const cancelled = ctx.events.source<SearchCancelledEvent>();\n const failed = ctx.events.source<SearchFailedEvent>();\n const activeHitChanged = ctx.events.source<SearchActiveHitChangedEvent>();\n const cleared = ctx.events.source<SearchClearedEvent>();\n\n const state = () => ctx.state.get();\n\n ctx.state.onChange(({ previous, next }) => {\n // A slice moves it forward; a new search or clear() puts it back to none.\n if (previous.progress !== next.progress) {\n progressChanged.emit({ ...next.progress, hitCount: next.hits.length });\n }\n if (previous.activeHitIndex !== next.activeHitIndex) {\n activeHitChanged.emit({\n index: next.activeHitIndex,\n hit: next.hits[next.activeHitIndex] ?? null,\n });\n }\n if (previous.status !== 'idle' && next.status === 'idle') cleared.emit({});\n });\n\n // ── projection: engine slice → page-space hits ───────────────────────────\n\n function hitsFromSlice(slice: SearchSlice): SearchHit[] {\n const hits: SearchHit[] = [];\n for (const match of slice.matches) {\n // A page that vanished mid-search (deleted) drops its hits.\n const page = ctx.getPage(match.page);\n if (!page) continue;\n // Segments arrive in page space, ready to draw.\n const segments = match.segments;\n hits.push({\n page: match.page,\n pageIndex: page.index,\n start: match.start,\n count: match.count,\n segments,\n ...(segments.length\n ? { bounds: boundsOfRects(segments.map((segment) => segment.rect)) ?? undefined }\n : {}),\n ...(match.snippet ? { snippet: match.snippet } : {}),\n });\n }\n return hits;\n }\n\n // ── the mechanism ────────────────────────────────────────────────────────\n\n /**\n * One cursor loop over budgeted slices: permission fallback (snippets → none\n * when they are denied, unless pinned), restart-once on a stale cursor,\n * projection per slice. Returns true on exhaustion, false when the signal\n * aborted; throws on a real error (already a PluginError: ctx.doc is guarded).\n */\n async function collect(\n query: SearchQuery,\n options: { from?: PageRef; snippets?: boolean; signal?: AbortSignal },\n sink: CollectSink,\n ): Promise<boolean> {\n const { signal } = options;\n sink.onStart();\n\n let snippets = options.snippets ?? true;\n const snippetsPinned = options.snippets !== undefined;\n let cursor: string | undefined;\n let restarted = false;\n for (;;) {\n if (signal?.aborted) return false;\n let slice: SearchSlice;\n const request = {\n ...query,\n snippets,\n ...(cursor !== undefined\n ? { cursor }\n : options.from !== undefined\n ? { from: options.from }\n : {}),\n };\n try {\n // A scan nobody waits on, so it runs once the views' work is done.\n // Cancelling stops the engine working on the slice, not only the wait for it.\n slice = await ctx.cancellable(\n signal,\n ctx.doc.with({ priority: 'low' }).search.query(request),\n );\n } catch (error) {\n if (signal?.aborted) return false;\n // Snippets denied (no doc.text.copy)? Degrade to matches without them.\n if (\n !snippetsPinned &&\n snippets &&\n cursor === undefined &&\n isPluginError(error, 'permission-denied')\n ) {\n snippets = false;\n continue;\n }\n // A stale cursor rejects with InvalidArg: the document changed under the\n // loop. Position is lost, the query is not: restart once from scratch.\n if (cursor !== undefined && !restarted && isPluginError(error, 'invalid-input')) {\n restarted = true;\n cursor = undefined;\n sink.onStart();\n continue;\n }\n throw error;\n }\n if (signal?.aborted) return false;\n sink.onSlice(hitsFromSlice(slice), slice.pagesSearched, slice.pageCount);\n if (slice.nextCursor === null) return true;\n cursor = slice.nextCursor;\n }\n }\n\n // ── the session ──────────────────────────────────────────────────────────\n\n const viewportFirstPage = (): PageRef | undefined => {\n const stage = ctx.tryGet(StageToken);\n return stage?.getCurrentPage() ?? undefined;\n };\n\n // Async so a refusal or a page that isn't in the document rejects, like any other failure,\n // before the session is touched. The run still starts synchronously, so a newer search\n // supersedes an older one in the order they were called.\n async function search(query: SearchQuery, options: SearchOptions = {}): Promise<SearchResult> {\n if (query.text.length === 0) {\n clear();\n return { status: 'complete', hitCount: 0 };\n }\n ctx.assertAllowed('doc.text.search', 'search');\n const from = options.from === undefined ? viewportFirstPage() : ctx.pageOf(options.from).ref;\n let operationId = '';\n return session\n .run(async (run) => {\n operationId = run.id;\n const complete = await collect(\n query,\n { from, signal: run.signal },\n {\n onStart: () =>\n run.commit(() => {\n ctx.state.update(startSession, query, run.id);\n started.emit({ query });\n }),\n onSlice: (hits, pagesSearched, pageCount) =>\n run.commit(() => ctx.state.update(appendHits, hits, { pagesSearched, pageCount })),\n },\n );\n if (!complete) throw new PluginError('operation-cancelled', 'search', 'search aborted');\n run.commit(() => {\n ctx.state.update(completeSession);\n completed.emit({ hitCount: state().hits.length });\n });\n return { status: 'complete', hitCount: state().hits.length } as SearchResult;\n }, options)\n .catch((error: unknown): SearchResult => {\n if (isPluginError(error, 'instance-closed')) return { status: 'cancelled', hitCount: 0 };\n if (isPluginError(error, 'operation-cancelled')) {\n const why = (error.details as LatestCancellation | undefined)?.reason ?? 'cancelled';\n const reason: SearchCancelledEvent['reason'] =\n why === 'superseded' ? 'superseded' : why === 'cleared' ? 'cleared' : 'cancelled';\n // A superseded or cleared run has already lost the session; a cancelled one keeps its hits.\n if (reason === 'cancelled' && state().operationId === operationId) {\n ctx.state.update(cancelSession);\n }\n cancelled.emit({ reason });\n return {\n status: reason === 'superseded' ? 'superseded' : 'cancelled',\n hitCount: state().hits.length,\n };\n }\n const info = toPluginErrorInfo(toPluginError('search', error));\n if (state().operationId === operationId) ctx.state.update(failSession, info);\n failed.emit({ error: info });\n throw error;\n });\n }\n\n function clear(): void {\n session.cancel('cleared');\n ctx.state.update(clearSession);\n }\n\n function goToHit(target: SearchHit | number, options?: SearchRevealOptions): SearchHit | null {\n const { hits } = state();\n if (hits.length === 0) return null;\n const index =\n typeof target === 'number'\n ? ((target % hits.length) + hits.length) % hits.length\n : indexOfHit(hits, target);\n if (index < 0) return null;\n ctx.state.update(setActiveHit, index);\n reveal(hits[index], options);\n return hits[index];\n }\n\n function reveal(hit: SearchHit, options?: SearchRevealOptions): void {\n // Positioned reveal: the hit (not just its page) arrives at the anchor —\n // the call's options over the `reveal` setting. Zoom never changes.\n const arrival = { ...settings.get().reveal, ...options };\n ctx.tryGet(StageToken)?.reveal(hit.page, {\n rect: hit.bounds,\n anchor: arrival.anchor,\n behavior: arrival.behavior,\n });\n }\n\n /**\n * One page's hits, by the page's ref or index. A page that isn't in the\n * document has none: a layer may read while its page is being deleted.\n */\n const hitsOn = (page: PageRef | number): readonly SearchHit[] => {\n const found = ctx.getPage(page);\n return found ? (state().hitsByPage[found.ref.objectNumber] ?? EMPTY) : EMPTY;\n };\n\n const api: SearchCapability = {\n ...settings.api,\n\n // The twin of search() and findAll(), which refuse without doc.text.search.\n // Snippets need doc.text.copy too, but a search without it still runs: its\n // matches come without snippets (see collect).\n canSearch: (options) =>\n ctx.allows('doc.text.search') && (!options?.snippets || ctx.allows('doc.text.copy')),\n\n search,\n refresh: (options) => {\n const { query, status } = state();\n if (!query || status === 'idle') return Promise.resolve({ status: 'complete', hitCount: 0 });\n return search(query, options);\n },\n cancel: () => session.cancel('cancelled'),\n clear,\n\n nextHit: (options) => goToHit(state().activeHitIndex + 1, options),\n previousHit: (options) => goToHit(state().activeHitIndex - 1, options),\n goToHit,\n revealActiveHit: (options) => {\n const hit = api.getActiveHit();\n if (hit) reveal(hit, options);\n },\n\n getQuery: () => state().query,\n getStatus: () => state().status,\n listHits: (filter) => (filter?.page === undefined ? state().hits : hitsOn(filter.page)),\n getHitCount: (page) => (page === undefined ? state().hits : hitsOn(page)).length,\n listPagesWithHits: () =>\n state().hits.length ? pagesWithHits(state(), ctx.document()?.pages ?? []) : EMPTY_PAGES,\n getActiveHitIndex: () => state().activeHitIndex,\n getActiveHit: () => state().hits[state().activeHitIndex] ?? null,\n getProgress: () => state().progress,\n getError: () => state().error,\n\n // ── the service ──────────────────────────────────────────────────────\n findAll: async (query, options = {}) => {\n ctx.assertAllowed('doc.text.search', 'findAll');\n const all: SearchHit[] = [];\n const complete = await collect(\n query,\n {\n ...(options.snippets !== undefined ? { snippets: options.snippets } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n },\n {\n onStart: () => {\n all.length = 0;\n },\n onSlice: (hits) => all.push(...hits),\n },\n );\n if (!complete) throw new PluginError('operation-cancelled', 'search', 'findAll aborted');\n return all;\n },\n\n onStarted: started.on,\n onProgressChanged: progressChanged.on,\n onCompleted: completed.on,\n onCancelled: cancelled.on,\n onFailed: failed.on,\n onActiveHitChanged: activeHitChanged.on,\n onCleared: cleared.on,\n };\n\n return {\n api,\n connect() {\n // Every confirmed mutation (own or remote) invalidates cursors and can change\n // what is findable, so the running or finished session re-runs its query.\n let cancelRerun: (() => void) | null = null;\n ctx.listen(ctx.doc.events, () => {\n if (state().status === 'idle') return;\n cancelRerun?.();\n cancelRerun = ctx.clock.after(RERUN_DELAY_MS, () => {\n cancelRerun = null;\n void api.refresh().catch(() => {\n /* surfaced through getStatus() and onFailed */\n });\n });\n });\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { SEARCH_DEFAULTS, SearchToken, type SearchConfig } from './contract';\nimport { createSearchController } from './controller';\nimport { initialSearchState } from './model';\n\n/**\n * Document text search over the engine's budgeted, resumable slices.\n * Document-scoped; no pointer handling. The stage is optional: with one,\n * scans start at the current page and navigation reveals hits. `config` is\n * the settings the app registers, over {@link SEARCH_DEFAULTS}.\n */\nexport const searchPlugin = (config?: SearchConfig) =>\n definePlugin({\n id: 'search',\n token: SearchToken,\n scope: 'document',\n optional: [StageToken],\n state: initialSearchState,\n settings: { defaults: SEARCH_DEFAULTS, registered: config },\n create: createSearchController,\n });\n","/**\n * The search page's State table as code: what `useSearchState()` returns, and\n * the same fields in every other framework. The matches themselves are not\n * here: `useSearchHits()` reads them, so a component that only shows the\n * count doesn't re-render for every match that arrives.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { SearchToken } from './contract';\nimport { NO_PROGRESS } from './model';\n\nexport const searchState = defineState(SearchToken, {\n read: (search) => ({\n query: search.getQuery(),\n status: search.getStatus(),\n hitCount: search.getHitCount(),\n activeHitIndex: search.getActiveHitIndex(),\n activeHit: search.getActiveHit(),\n progress: search.getProgress(),\n error: search.getError(),\n }),\n empty: {\n query: null,\n status: 'idle',\n hitCount: 0,\n activeHitIndex: -1,\n activeHit: null,\n progress: NO_PROGRESS,\n error: null,\n },\n});\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,13 +1,41 @@
1
- import { _ as SearchToken, a as SearchSnippet, c as SearchAction, d as SearchFindAllOptions, f as SearchHit, g as SearchStatus, h as SearchState, i as SearchRegexValidation, l as SearchCapability, m as SearchRevealOptions, n as SearchQueryIssue, o as validateSearchQuery, p as SearchPluginConfig, r as SearchQueryValidation, s as validateSearchRegex, t as SearchQuery, u as SearchExecOptions } from "./contract-CSb66POC.cjs";
1
+ import { A as SearchToken, C as SearchSettings, D as TextSegment, E as SearchStatus, O as validateSearchQuery, S as SearchRevealOptions, T as SearchStartedEvent, _ as SearchQueryIssue, a as SearchCapability, b as SearchResult, c as SearchConfig, d as SearchHit, f as SearchHitFilter, g as SearchQuery, h as SearchProgressChangedEvent, i as SearchCancelledEvent, k as validateSearchRegex, l as SearchFailedEvent, m as SearchProgress, n as SearchActiveHitChangedEvent, o as SearchClearedEvent, p as SearchOptions, r as SearchBlendMode, s as SearchCompletedEvent, t as SEARCH_DEFAULTS, u as SearchFindAllOptions, v as SearchQueryValidation, w as SearchSnippet, x as SearchReveal, y as SearchRegexValidation } from "./contract-DqeptOSn.cjs";
2
+ import { PageRef, PluginErrorInfo } from "@embedpdf/core";
3
+ import { SearchQuery as SearchQuery$1 } from "@embedpdf/engine-core/runtime";
4
+ //#region src/model.d.ts
5
+ interface SearchState {
6
+ /** The query being searched, as the engine matches it. Null when idle. */
7
+ readonly query: SearchQuery$1 | null;
8
+ readonly status: SearchStatus;
9
+ /** The run that owns the session, so a run that lost it (superseded, cleared) leaves it alone. */
10
+ readonly operationId: string | null;
11
+ readonly hits: readonly SearchHit[];
12
+ /** Page object number → the hits on that page; a page's array is replaced only when it gains hits. */
13
+ readonly hitsByPage: Readonly<Record<number, readonly SearchHit[]>>;
14
+ /** Index into `hits`, -1 when no hit is active. */
15
+ readonly activeHitIndex: number;
16
+ readonly progress: SearchProgress;
17
+ readonly error: PluginErrorInfo | null;
18
+ }
19
+ //#endregion
2
20
  //#region src/search.plugin.d.ts
3
21
  /**
4
- * Document text search over `doc.search` — the engine's budgeted,
5
- * cursor-resumable slices. Document-scoped; no pointer handling, so it
6
- * needs no interaction hub. The Stage is optional: when present, scans
7
- * start at the current page (viewport-first) and hit navigation reveals
8
- * the hit's page; without it the host owns scrolling.
22
+ * Document text search over the engine's budgeted, resumable slices.
23
+ * Document-scoped; no pointer handling. The stage is optional: with one,
24
+ * scans start at the current page and navigation reveals hits. `config` is
25
+ * the settings the app registers, over {@link SEARCH_DEFAULTS}.
9
26
  */
10
- declare const searchPlugin: (config?: SearchPluginConfig) => import("@embedpdf/core").PluginDef<SearchState, SearchAction, SearchCapability>;
27
+ declare const searchPlugin: (config?: SearchConfig) => import("@embedpdf/core").PluginDef<SearchState, SearchCapability, SearchSettings>;
28
+ //#endregion
29
+ //#region src/state.d.ts
30
+ declare const searchState: import("@embedpdf/core").StateDeclaration<SearchCapability, {
31
+ query: import("@embedpdf/engine-core/runtime").SearchQuery | null;
32
+ status: SearchStatus;
33
+ hitCount: number;
34
+ activeHitIndex: number;
35
+ activeHit: SearchHit | null;
36
+ progress: SearchProgress;
37
+ error: import("@embedpdf/core").PluginErrorInfo | null;
38
+ }>;
11
39
  //#endregion
12
- export { type SearchCapability, type SearchExecOptions, type SearchFindAllOptions, type SearchHit, type SearchPluginConfig, type SearchQuery, type SearchQueryIssue, type SearchQueryValidation, type SearchRegexValidation, type SearchRevealOptions, type SearchSnippet, type SearchState, type SearchStatus, SearchToken, searchPlugin, validateSearchQuery, validateSearchRegex };
40
+ export { SEARCH_DEFAULTS, SearchActiveHitChangedEvent, SearchBlendMode, SearchCancelledEvent, SearchCapability, SearchClearedEvent, SearchCompletedEvent, SearchConfig, SearchFailedEvent, SearchFindAllOptions, SearchHit, SearchHitFilter, SearchOptions, SearchProgress, SearchProgressChangedEvent, type SearchQuery, type SearchQueryIssue, type SearchQueryValidation, type SearchRegexValidation, SearchResult, SearchReveal, SearchRevealOptions, SearchSettings, type SearchSnippet, SearchStartedEvent, SearchStatus, SearchToken, TextSegment, searchPlugin, searchState, validateSearchQuery, validateSearchRegex };
13
41
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,13 +1,41 @@
1
- import { _ as SearchToken, a as SearchSnippet, c as SearchAction, d as SearchFindAllOptions, f as SearchHit, g as SearchStatus, h as SearchState, i as SearchRegexValidation, l as SearchCapability, m as SearchRevealOptions, n as SearchQueryIssue, o as validateSearchQuery, p as SearchPluginConfig, r as SearchQueryValidation, s as validateSearchRegex, t as SearchQuery, u as SearchExecOptions } from "./contract-Cj4xgdMj.js";
1
+ import { A as SearchToken, C as SearchSettings, D as TextSegment, E as SearchStatus, O as validateSearchQuery, S as SearchRevealOptions, T as SearchStartedEvent, _ as SearchQueryIssue, a as SearchCapability, b as SearchResult, c as SearchConfig, d as SearchHit, f as SearchHitFilter, g as SearchQuery, h as SearchProgressChangedEvent, i as SearchCancelledEvent, k as validateSearchRegex, l as SearchFailedEvent, m as SearchProgress, n as SearchActiveHitChangedEvent, o as SearchClearedEvent, p as SearchOptions, r as SearchBlendMode, s as SearchCompletedEvent, t as SEARCH_DEFAULTS, u as SearchFindAllOptions, v as SearchQueryValidation, w as SearchSnippet, x as SearchReveal, y as SearchRegexValidation } from "./contract-B7maCczh.js";
2
+ import { PageRef, PluginErrorInfo } from "@embedpdf/core";
3
+ import { SearchQuery as SearchQuery$1 } from "@embedpdf/engine-core/runtime";
4
+ //#region src/model.d.ts
5
+ interface SearchState {
6
+ /** The query being searched, as the engine matches it. Null when idle. */
7
+ readonly query: SearchQuery$1 | null;
8
+ readonly status: SearchStatus;
9
+ /** The run that owns the session, so a run that lost it (superseded, cleared) leaves it alone. */
10
+ readonly operationId: string | null;
11
+ readonly hits: readonly SearchHit[];
12
+ /** Page object number → the hits on that page; a page's array is replaced only when it gains hits. */
13
+ readonly hitsByPage: Readonly<Record<number, readonly SearchHit[]>>;
14
+ /** Index into `hits`, -1 when no hit is active. */
15
+ readonly activeHitIndex: number;
16
+ readonly progress: SearchProgress;
17
+ readonly error: PluginErrorInfo | null;
18
+ }
19
+ //#endregion
2
20
  //#region src/search.plugin.d.ts
3
21
  /**
4
- * Document text search over `doc.search` — the engine's budgeted,
5
- * cursor-resumable slices. Document-scoped; no pointer handling, so it
6
- * needs no interaction hub. The Stage is optional: when present, scans
7
- * start at the current page (viewport-first) and hit navigation reveals
8
- * the hit's page; without it the host owns scrolling.
22
+ * Document text search over the engine's budgeted, resumable slices.
23
+ * Document-scoped; no pointer handling. The stage is optional: with one,
24
+ * scans start at the current page and navigation reveals hits. `config` is
25
+ * the settings the app registers, over {@link SEARCH_DEFAULTS}.
9
26
  */
10
- declare const searchPlugin: (config?: SearchPluginConfig) => import("@embedpdf/core").PluginDef<SearchState, SearchAction, SearchCapability>;
27
+ declare const searchPlugin: (config?: SearchConfig) => import("@embedpdf/core").PluginDef<SearchState, SearchCapability, SearchSettings>;
28
+ //#endregion
29
+ //#region src/state.d.ts
30
+ declare const searchState: import("@embedpdf/core").StateDeclaration<SearchCapability, {
31
+ query: import("@embedpdf/engine-core/runtime").SearchQuery | null;
32
+ status: SearchStatus;
33
+ hitCount: number;
34
+ activeHitIndex: number;
35
+ activeHit: SearchHit | null;
36
+ progress: SearchProgress;
37
+ error: import("@embedpdf/core").PluginErrorInfo | null;
38
+ }>;
11
39
  //#endregion
12
- export { type SearchCapability, type SearchExecOptions, type SearchFindAllOptions, type SearchHit, type SearchPluginConfig, type SearchQuery, type SearchQueryIssue, type SearchQueryValidation, type SearchRegexValidation, type SearchRevealOptions, type SearchSnippet, type SearchState, type SearchStatus, SearchToken, searchPlugin, validateSearchQuery, validateSearchRegex };
40
+ export { SEARCH_DEFAULTS, SearchActiveHitChangedEvent, SearchBlendMode, SearchCancelledEvent, SearchCapability, SearchClearedEvent, SearchCompletedEvent, SearchConfig, SearchFailedEvent, SearchFindAllOptions, SearchHit, SearchHitFilter, SearchOptions, SearchProgress, SearchProgressChangedEvent, type SearchQuery, type SearchQueryIssue, type SearchQueryValidation, type SearchRegexValidation, SearchResult, SearchReveal, SearchRevealOptions, SearchSettings, type SearchSnippet, SearchStartedEvent, SearchStatus, SearchToken, TextSegment, searchPlugin, searchState, validateSearchQuery, validateSearchRegex };
13
41
  //# sourceMappingURL=index.d.ts.map