@openuji/speculator-search 0.1.0

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.
Files changed (54) hide show
  1. package/README.md +198 -0
  2. package/dist/__tests__/standalone.test.d.ts +2 -0
  3. package/dist/__tests__/standalone.test.d.ts.map +1 -0
  4. package/dist/__tests__/standalone.test.js +177 -0
  5. package/dist/__tests__/standalone.test.js.map +1 -0
  6. package/dist/builders/search-index-builder.d.ts +27 -0
  7. package/dist/builders/search-index-builder.d.ts.map +1 -0
  8. package/dist/builders/search-index-builder.js +133 -0
  9. package/dist/builders/search-index-builder.js.map +1 -0
  10. package/dist/engines/index.d.ts +62 -0
  11. package/dist/engines/index.d.ts.map +1 -0
  12. package/dist/engines/index.js +34 -0
  13. package/dist/engines/index.js.map +1 -0
  14. package/dist/index.d.ts +23 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +25 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/indexer.d.ts +15 -0
  19. package/dist/indexer.d.ts.map +1 -0
  20. package/dist/indexer.js +256 -0
  21. package/dist/indexer.js.map +1 -0
  22. package/dist/plugin.d.ts +24 -0
  23. package/dist/plugin.d.ts.map +1 -0
  24. package/dist/plugin.js +40 -0
  25. package/dist/plugin.js.map +1 -0
  26. package/dist/plugins/content-id.d.ts +35 -0
  27. package/dist/plugins/content-id.d.ts.map +1 -0
  28. package/dist/plugins/content-id.js +267 -0
  29. package/dist/plugins/content-id.js.map +1 -0
  30. package/dist/plugins/search-index.d.ts +27 -0
  31. package/dist/plugins/search-index.d.ts.map +1 -0
  32. package/dist/plugins/search-index.js +242 -0
  33. package/dist/plugins/search-index.js.map +1 -0
  34. package/dist/searcher.d.ts +16 -0
  35. package/dist/searcher.d.ts.map +1 -0
  36. package/dist/searcher.js +83 -0
  37. package/dist/searcher.js.map +1 -0
  38. package/dist/standalone.d.ts +42 -0
  39. package/dist/standalone.d.ts.map +1 -0
  40. package/dist/standalone.js +211 -0
  41. package/dist/standalone.js.map +1 -0
  42. package/dist/tokenizer.d.ts +48 -0
  43. package/dist/tokenizer.d.ts.map +1 -0
  44. package/dist/tokenizer.js +85 -0
  45. package/dist/tokenizer.js.map +1 -0
  46. package/dist/types.d.ts +70 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +8 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utils/extract-text.d.ts +19 -0
  51. package/dist/utils/extract-text.d.ts.map +1 -0
  52. package/dist/utils/extract-text.js +55 -0
  53. package/dist/utils/extract-text.js.map +1 -0
  54. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # @openuji/speculator-search
2
+
3
+ Search index builder for Speculator with content mapping and navigation support.
4
+
5
+ ## Features
6
+
7
+ - 🔍 **Full-text search indexing** - Extract searchable content from Speculator AST
8
+ - 🎯 **Content-addressable navigation** - Map search results to rendered locations
9
+ - 🏷️ **In-memory search IDs** - Generate hierarchical IDs without polluting AST
10
+ - 🚀 **Static build-time generation** - No Node server needed at runtime
11
+ - 🔌 **Pluggable architecture** - Extends Speculator via plugins
12
+ - 🎨 **Client-side ready** - Pure JavaScript search with optional FlexSearch
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @openuji/speculator @openuji/speculator-search
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### Build-Time Index Generation
23
+
24
+ ```typescript
25
+ import { speculate, corePlugins, NodeFileProvider } from '@openuji/speculator';
26
+ import { contentIdPlugin, searchIndexPlugin, buildSearchIndex } from '@openuji/speculator-search';
27
+ import fs from 'fs/promises';
28
+
29
+ // Run Speculator with search plugins
30
+ const result = await speculate({
31
+ entry: 'spec/index.md',
32
+ plugins: [
33
+ ...corePlugins,
34
+ contentIdPlugin,
35
+ searchIndexPlugin()
36
+ ],
37
+ fileProvider: new NodeFileProvider()
38
+ });
39
+
40
+ // Build search index
41
+ const searchIndex = buildSearchIndex(result.workspace);
42
+
43
+ // Save as static JSON
44
+ await fs.writeFile(
45
+ 'public/search-index.json',
46
+ JSON.stringify(searchIndex)
47
+ );
48
+ ```
49
+
50
+ ### Client-Side Search
51
+
52
+ ```html
53
+ <script>
54
+ // Load search index
55
+ const response = await fetch('/search-index.json');
56
+ const index = await response.json();
57
+
58
+ // Simple search
59
+ const results = index.documents
60
+ .flatMap(doc => doc.entries)
61
+ .filter(entry => entry.plainText.includes('query'));
62
+
63
+ // Navigate to result
64
+ window.location.href = results[0].url; // e.g., "/docs/api#intro"
65
+ </script>
66
+ ```
67
+
68
+ ## How It Works
69
+
70
+ ### 1. Content ID Plugin
71
+
72
+ Generates hierarchical search IDs **in-memory only** (does NOT modify AST):
73
+
74
+ ```typescript
75
+ // AST remains unchanged:
76
+ { type: 'paragraph', id: 'my-para', children: [...] }
77
+
78
+ // Plugin creates internal mapping:
79
+ contentIdMap.set('intro.p-2', {
80
+ searchId: 'intro.p-2',
81
+ canonicalId: 'my-para', // Original ID preserved
82
+ node: paragraphRef
83
+ });
84
+ ```
85
+
86
+ ### 2. Search Index Plugin
87
+
88
+ Extracts searchable text and builds index:
89
+
90
+ ```json
91
+ {
92
+ "version": "1.0.0",
93
+ "documents": [{
94
+ "documentId": "spec/index.md",
95
+ "route": "/docs/api",
96
+ "title": "API Documentation",
97
+ "entries": [{
98
+ "searchId": "intro.p-2",
99
+ "text": "The API provides...",
100
+ "plainText": "the api provides",
101
+ "anchor": "#intro",
102
+ "context": {
103
+ "sectionTitle": "Introduction",
104
+ "nodeType": "paragraph"
105
+ }
106
+ }]
107
+ }]
108
+ }
109
+ ```
110
+
111
+ ### 3. Client Navigation
112
+
113
+ Search results link to existing canonical IDs:
114
+
115
+ ```
116
+ User searches "API" → Result: { anchor: "#intro", route: "/docs/api" }
117
+ Click result → Navigate to "/docs/api#intro"
118
+ Browser scrolls to <section id="intro">
119
+ Optional: Highlight "API" text within section
120
+ ```
121
+
122
+ ## Configuration
123
+
124
+ Create `config.search.json`:
125
+
126
+ ```json
127
+ {
128
+ "routing": {
129
+ "strategy": "pattern",
130
+ "pattern": "/docs/{shortName}",
131
+ "fallback": "/docs/index"
132
+ },
133
+ "search": {
134
+ "mode": "raw",
135
+ "filters": {
136
+ "enabled": true,
137
+ "fields": ["documentType", "sectionType"]
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ ## API
144
+
145
+ ### Plugins
146
+
147
+ #### `contentIdPlugin`
148
+
149
+ Generates hierarchical search IDs without modifying AST.
150
+
151
+ ```typescript
152
+ import { contentIdPlugin } from '@openuji/speculator-search';
153
+
154
+ const plugins = [...corePlugins, contentIdPlugin];
155
+ ```
156
+
157
+ #### `searchIndexPlugin(config?)`
158
+
159
+ Collects searchable content from documents.
160
+
161
+ ```typescript
162
+ import { searchIndexPlugin } from '@openuji/speculator-search';
163
+
164
+ const plugins = [
165
+ contentIdPlugin,
166
+ searchIndexPlugin({
167
+ configPath: 'config.search.json'
168
+ })
169
+ ];
170
+ ```
171
+
172
+ ### Builders
173
+
174
+ #### `buildSearchIndex(workspace, options?)`
175
+
176
+ Builds final search index from workspace AST.
177
+
178
+ ```typescript
179
+ const searchIndex = buildSearchIndex(result.workspace, {
180
+ mode: 'raw',
181
+ includeSourcePos: false
182
+ });
183
+ ```
184
+
185
+ ### Types
186
+
187
+ - `SearchIndex` - Complete search index structure
188
+ - `DocumentSearchData` - Search data for a single document
189
+ - `SearchEntry` - Individual searchable content entry
190
+ - `SearchContext` - Context information for search results
191
+
192
+ ## Examples
193
+
194
+ See `/Users/zavalit/Projects/openuji/speculator/apps/demo` for complete working example.
195
+
196
+ ## License
197
+
198
+ MIT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=standalone.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/standalone.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,177 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildSearchIndex } from '../standalone.js';
3
+ import { createRawEngine } from '../engines/index.js';
4
+ /**
5
+ * Create a minimal test document
6
+ */
7
+ function createTestDocument(options) {
8
+ const { title = 'Test Document', shortName, sections = [] } = options;
9
+ return {
10
+ type: 'document',
11
+ children: sections,
12
+ metadata: { title, shortName },
13
+ sourcePos: { file: '/test/doc.md', line: 1, column: 1 },
14
+ indexes: { definitions: [] }
15
+ };
16
+ }
17
+ /**
18
+ * Create a minimal section
19
+ */
20
+ function createTestSection(options) {
21
+ const { id = 'test-section', title = 'Test Section', paragraphs = [] } = options;
22
+ const blocks = paragraphs.map((text, i) => ({
23
+ type: 'paragraph',
24
+ children: [{ type: 'text', value: text }],
25
+ sourcePos: { file: '/test/doc.md', line: 10 + i, column: 1 }
26
+ }));
27
+ return {
28
+ type: 'section',
29
+ id,
30
+ heading: {
31
+ type: 'heading',
32
+ depth: 1,
33
+ children: [{ type: 'text', value: title }],
34
+ sourcePos: { file: '/test/doc.md', line: 5, column: 1 }
35
+ },
36
+ children: blocks,
37
+ sourcePos: { file: '/test/doc.md', line: 5, column: 1 }
38
+ };
39
+ }
40
+ describe('buildSearchIndex', () => {
41
+ it('should build search index from workspace with one document', async () => {
42
+ const workspace = {
43
+ type: 'workspace',
44
+ documents: [
45
+ createTestDocument({
46
+ title: 'My Spec',
47
+ shortName: 'myspec',
48
+ sections: [
49
+ createTestSection({
50
+ id: 'intro',
51
+ title: 'Introduction',
52
+ paragraphs: ['Hello world', 'This is a test']
53
+ })
54
+ ]
55
+ })
56
+ ]
57
+ };
58
+ const { engine, data } = await buildSearchIndex(workspace);
59
+ expect(engine).toBe('raw');
60
+ expect(data.version).toBe('1.0.0');
61
+ expect(data.documents).toHaveLength(1);
62
+ expect(data.documents[0].documentId).toBe('/test/doc.md');
63
+ expect(data.documents[0].title).toBe('My Spec');
64
+ expect(data.documents[0].shortName).toBe('myspec');
65
+ expect(data.documents[0].entries.length).toBeGreaterThan(0);
66
+ });
67
+ it('should extract text from paragraphs', async () => {
68
+ const workspace = {
69
+ type: 'workspace',
70
+ documents: [
71
+ createTestDocument({
72
+ sections: [
73
+ createTestSection({
74
+ paragraphs: ['First paragraph', 'Second paragraph']
75
+ })
76
+ ]
77
+ })
78
+ ]
79
+ };
80
+ const { data } = await buildSearchIndex(workspace);
81
+ const entries = data.documents[0].entries;
82
+ const texts = entries.map(e => e.text);
83
+ expect(texts).toContain('First paragraph');
84
+ expect(texts).toContain('Second paragraph');
85
+ });
86
+ it('should set correct anchors based on section id', async () => {
87
+ const workspace = {
88
+ type: 'workspace',
89
+ documents: [
90
+ createTestDocument({
91
+ sections: [
92
+ createTestSection({
93
+ id: 'my-section',
94
+ paragraphs: ['Content in section']
95
+ })
96
+ ]
97
+ })
98
+ ]
99
+ };
100
+ const { data } = await buildSearchIndex(workspace);
101
+ const entry = data.documents[0].entries.find(e => e.text === 'Content in section');
102
+ expect(entry?.anchor).toBe('#my-section');
103
+ expect(entry?.sectionId).toBe('my-section');
104
+ });
105
+ it('should include heading path in context', async () => {
106
+ const workspace = {
107
+ type: 'workspace',
108
+ documents: [
109
+ createTestDocument({
110
+ sections: [
111
+ createTestSection({
112
+ title: 'Getting Started',
113
+ paragraphs: ['Start here']
114
+ })
115
+ ]
116
+ })
117
+ ]
118
+ };
119
+ const { data } = await buildSearchIndex(workspace);
120
+ const entry = data.documents[0].entries.find(e => e.text === 'Start here');
121
+ expect(entry?.context.headingPath).toEqual(['Getting Started']);
122
+ expect(entry?.context.sectionTitle).toBe('Getting Started');
123
+ });
124
+ it('should handle empty workspace', async () => {
125
+ const workspace = {
126
+ type: 'workspace',
127
+ documents: []
128
+ };
129
+ const { data } = await buildSearchIndex(workspace);
130
+ expect(data.documents).toHaveLength(0);
131
+ });
132
+ });
133
+ describe('createRawEngine', () => {
134
+ it('should create engine with correct name', () => {
135
+ const engine = createRawEngine();
136
+ expect(engine.name).toBe('raw');
137
+ });
138
+ it('should accumulate documents on addDocument', async () => {
139
+ const engine = createRawEngine();
140
+ await engine.addDocument([{ searchId: 'e1', text: 'Test', plainText: 'test', anchor: '#a', context: { nodeType: 'paragraph' } }], { documentId: '/doc1.md', title: 'Doc 1' });
141
+ await engine.addDocument([{ searchId: 'e2', text: 'Another', plainText: 'another', anchor: '#b', context: { nodeType: 'paragraph' } }], { documentId: '/doc2.md', title: 'Doc 2' });
142
+ const result = await engine.finalize();
143
+ expect(result.engine).toBe('raw');
144
+ expect(result.data.documents).toHaveLength(2);
145
+ expect(result.data.documents[0].documentId).toBe('/doc1.md');
146
+ expect(result.data.documents[1].documentId).toBe('/doc2.md');
147
+ });
148
+ it('should strip sourcePos when includeSourcePos is false', async () => {
149
+ const engine = createRawEngine({ includeSourcePos: false });
150
+ await engine.addDocument([{
151
+ searchId: 'e1',
152
+ text: 'Test',
153
+ plainText: 'test',
154
+ anchor: '#a',
155
+ context: { nodeType: 'paragraph' },
156
+ sourcePos: { file: '/test.md', line: 1, column: 1 }
157
+ }], { documentId: '/doc.md', title: 'Doc' });
158
+ const result = await engine.finalize();
159
+ const entry = result.data.documents[0].entries[0];
160
+ expect(entry.sourcePos).toBeUndefined();
161
+ });
162
+ it('should keep sourcePos when includeSourcePos is true', async () => {
163
+ const engine = createRawEngine({ includeSourcePos: true });
164
+ await engine.addDocument([{
165
+ searchId: 'e1',
166
+ text: 'Test',
167
+ plainText: 'test',
168
+ anchor: '#a',
169
+ context: { nodeType: 'paragraph' },
170
+ sourcePos: { file: '/test.md', line: 1, column: 1 }
171
+ }], { documentId: '/doc.md', title: 'Doc' });
172
+ const result = await engine.finalize();
173
+ const entry = result.data.documents[0].entries[0];
174
+ expect(entry.sourcePos).toEqual({ file: '/test.md', line: 1, column: 1 });
175
+ });
176
+ });
177
+ //# sourceMappingURL=standalone.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.test.js","sourceRoot":"","sources":["../../src/__tests__/standalone.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Search Index Builder
3
+ *
4
+ * Builds final search index from Speculator result.
5
+ * The plugins attach search data to the runtime workspace which we extract here.
6
+ */
7
+ import type { SearchIndex, BuildSearchIndexOptions, SearchConfig } from '../types.js';
8
+ /**
9
+ * Build search index from plugin execution results
10
+ *
11
+ * This function expects that the speculate pipeline has been run with
12
+ * contentIdPlugin and searchIndexPlugin.
13
+ */
14
+ export declare function buildSearchIndex(result: any, // SpeculateResult from speculate() call
15
+ options?: BuildSearchIndexOptions): SearchIndex;
16
+ /**
17
+ * Load search configuration from file
18
+ */
19
+ export declare function loadSearchConfig(configPath: string): Promise<SearchConfig>;
20
+ /**
21
+ * Apply routing configuration to search index
22
+ */
23
+ /**
24
+ * Apply routing configuration to search index
25
+ */
26
+ export declare function applyRoutingConfig(searchIndex: SearchIndex, config: SearchConfig, rootDir?: string): void;
27
+ //# sourceMappingURL=search-index-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index-builder.d.ts","sourceRoot":"","sources":["../../src/builders/search-index-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACR,WAAW,EAEX,uBAAuB,EACvB,YAAY,EAEf,MAAM,aAAa,CAAC;AAIrB;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,GAAG,EAAG,wCAAwC;AACtD,OAAO,GAAE,uBAA4B,GACtC,WAAW,CA+Bb;AA6CD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAQhF;AAED;;GAEG;AACH;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,MAAM,GACjB,IAAI,CAkCN"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Search Index Builder
3
+ *
4
+ * Builds final search index from Speculator result.
5
+ * The plugins attach search data to the runtime workspace which we extract here.
6
+ */
7
+ import { SEARCH_ENTRIES_SYMBOL } from '../plugins/search-index.js';
8
+ import fs from 'fs/promises';
9
+ /**
10
+ * Build search index from plugin execution results
11
+ *
12
+ * This function expects that the speculate pipeline has been run with
13
+ * contentIdPlugin and searchIndexPlugin.
14
+ */
15
+ export function buildSearchIndex(result, // SpeculateResult from speculate() call
16
+ options = {}) {
17
+ const { mode = 'raw', includeSourcePos = false } = options;
18
+ const searchIndex = {
19
+ version: '1.0.0',
20
+ documents: []
21
+ };
22
+ // Access the workspace from result
23
+ const workspace = result.workspace || result;
24
+ if (!workspace || !workspace.documents) {
25
+ console.warn('[buildSearchIndex] No documents found in workspace');
26
+ return searchIndex;
27
+ }
28
+ // Process each document
29
+ for (const doc of workspace.documents) {
30
+ const documentData = buildDocumentSearchData(doc, {
31
+ includeSourcePos
32
+ });
33
+ if (documentData && documentData.entries.length > 0) {
34
+ searchIndex.documents.push(documentData);
35
+ }
36
+ }
37
+ return searchIndex;
38
+ }
39
+ /**
40
+ * Build search data for a single document
41
+ */
42
+ function buildDocumentSearchData(doc, options) {
43
+ // Get document metadata
44
+ const metadata = doc.metadata || {};
45
+ const documentId = doc.sourcePos?.file || 'unknown';
46
+ const title = metadata.title || '';
47
+ const shortName = metadata.shortName;
48
+ // Default route (can be overridden by configuration)
49
+ const route = shortName ? `/${shortName}` : '/';
50
+ // Extract search entries from document's runtime context
51
+ // The plugin attaches entries to the document during index phase
52
+ const searchEntries = doc[SEARCH_ENTRIES_SYMBOL] || [];
53
+ const documentData = {
54
+ documentId,
55
+ route,
56
+ title,
57
+ shortName,
58
+ entries: options.includeSourcePos
59
+ ? searchEntries
60
+ : searchEntries.map(entry => {
61
+ const { sourcePos, ...rest } = entry;
62
+ return rest;
63
+ })
64
+ };
65
+ // Add document-level filters
66
+ if (metadata.status) {
67
+ documentData.filters = {
68
+ documentType: metadata.status
69
+ };
70
+ }
71
+ return documentData;
72
+ }
73
+ /**
74
+ * Load search configuration from file
75
+ */
76
+ export async function loadSearchConfig(configPath) {
77
+ try {
78
+ const content = await fs.readFile(configPath, 'utf-8');
79
+ return JSON.parse(content);
80
+ }
81
+ catch (error) {
82
+ console.warn(`[search-index] Could not load config from ${configPath}:`, error);
83
+ return {};
84
+ }
85
+ }
86
+ /**
87
+ * Apply routing configuration to search index
88
+ */
89
+ /**
90
+ * Apply routing configuration to search index
91
+ */
92
+ export function applyRoutingConfig(searchIndex, config, rootDir) {
93
+ const routing = config.routing;
94
+ if (!routing)
95
+ return;
96
+ for (const doc of searchIndex.documents) {
97
+ let applied = false;
98
+ if (routing.strategy === 'map' && routing.map && rootDir) {
99
+ // Apply map-based routing
100
+ const docPath = doc.documentId.split(/[/\\]/).join('/');
101
+ const rootPath = rootDir.split(/[/\\]/).join('/');
102
+ let relativePath = docPath;
103
+ if (docPath.startsWith(rootPath)) {
104
+ relativePath = docPath.substring(rootPath.length);
105
+ if (relativePath.startsWith('/')) {
106
+ relativePath = relativePath.substring(1);
107
+ }
108
+ }
109
+ if (routing.map[relativePath]) {
110
+ doc.route = routing.map[relativePath];
111
+ applied = true;
112
+ }
113
+ }
114
+ else if (routing.strategy === 'pattern' && routing.pattern) {
115
+ // Apply pattern-based routing
116
+ doc.route = applyRoutePattern(routing.pattern, doc);
117
+ applied = true;
118
+ }
119
+ if (!applied && routing.fallback && (!doc.route || doc.route === '/')) {
120
+ doc.route = routing.fallback;
121
+ }
122
+ }
123
+ }
124
+ /**
125
+ * Apply route pattern with variable substitution
126
+ */
127
+ function applyRoutePattern(pattern, doc) {
128
+ return pattern
129
+ .replace('{shortName}', doc.shortName || '')
130
+ .replace('{documentId}', doc.documentId)
131
+ .replace('{title}', encodeURIComponent(doc.title));
132
+ }
133
+ //# sourceMappingURL=search-index-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index-builder.js","sourceRoot":"","sources":["../../src/builders/search-index-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Index Engine Abstraction
3
+ *
4
+ * Defines the interface for search index engines.
5
+ * Engines transform raw search entries into their specific format.
6
+ */
7
+ import type { SearchEntry, DocumentSearchData } from '../types.js';
8
+ /**
9
+ * Result from an index engine
10
+ */
11
+ export interface IndexEngineResult<T = unknown> {
12
+ /** Engine identifier */
13
+ engine: string;
14
+ /** Engine-specific index data */
15
+ data: T;
16
+ }
17
+ /**
18
+ * Context passed to index engines during indexing
19
+ */
20
+ export interface IndexEngineContext {
21
+ /** Document metadata */
22
+ documentId: string;
23
+ title: string;
24
+ shortName?: string;
25
+ }
26
+ /**
27
+ * Index Engine Interface
28
+ *
29
+ * Implement this to create custom search index formats.
30
+ */
31
+ export interface IndexEngine<T = unknown> {
32
+ /** Unique engine identifier */
33
+ readonly name: string;
34
+ /**
35
+ * Initialize the engine (called once before indexing)
36
+ */
37
+ init?(): void | Promise<void>;
38
+ /**
39
+ * Add entries from a document to the index
40
+ */
41
+ addDocument(entries: SearchEntry[], context: IndexEngineContext): void | Promise<void>;
42
+ /**
43
+ * Finalize and return the index data
44
+ */
45
+ finalize(): IndexEngineResult<T> | Promise<IndexEngineResult<T>>;
46
+ }
47
+ /**
48
+ * Raw Index Engine
49
+ *
50
+ * Outputs search entries as-is in a plain JSON structure.
51
+ * This is the default engine for client-side search.
52
+ */
53
+ export interface RawIndexData {
54
+ version: string;
55
+ documents: DocumentSearchData[];
56
+ }
57
+ export interface RawEngineOptions {
58
+ /** Include source positions in output */
59
+ includeSourcePos?: boolean;
60
+ }
61
+ export declare function createRawEngine(options?: RawEngineOptions): IndexEngine<RawIndexData>;
62
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC1C,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,IAAI,EAAE,CAAC,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACpC,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B;;OAEG;IACH,WAAW,CACP,OAAO,EAAE,WAAW,EAAE,EACtB,OAAO,EAAE,kBAAkB,GAC5B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;OAEG;IACH,QAAQ,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;CACpE;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC7B,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,WAAW,CAAC,YAAY,CAAC,CA8BzF"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Index Engine Abstraction
3
+ *
4
+ * Defines the interface for search index engines.
5
+ * Engines transform raw search entries into their specific format.
6
+ */
7
+ export function createRawEngine(options = {}) {
8
+ const documents = [];
9
+ const { includeSourcePos = false } = options;
10
+ return {
11
+ name: 'raw',
12
+ addDocument(entries, context) {
13
+ const processedEntries = includeSourcePos
14
+ ? entries
15
+ : entries.map(({ sourcePos: _sourcePos, ...rest }) => rest);
16
+ documents.push({
17
+ documentId: context.documentId,
18
+ title: context.title,
19
+ shortName: context.shortName,
20
+ entries: processedEntries
21
+ });
22
+ },
23
+ finalize() {
24
+ return {
25
+ engine: 'raw',
26
+ data: {
27
+ version: '1.0.0',
28
+ documents
29
+ }
30
+ };
31
+ }
32
+ };
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoEH,MAAM,UAAU,eAAe,CAAC,UAA4B,EAAE;IAC1D,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,MAAM,EAAE,gBAAgB,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAE7C,OAAO;QACH,IAAI,EAAE,KAAK;QAEX,WAAW,CAAC,OAAsB,EAAE,OAA2B;YAC3D,MAAM,gBAAgB,GAAG,gBAAgB;gBACrC,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YAEhE,SAAS,CAAC,IAAI,CAAC;gBACX,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,gBAAiC;aAC7C,CAAC,CAAC;QACP,CAAC;QAED,QAAQ;YACJ,OAAO;gBACH,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE;oBACF,OAAO,EAAE,OAAO;oBAChB,SAAS;iBACZ;aACJ,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC"}