@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.
- package/README.md +198 -0
- package/dist/__tests__/standalone.test.d.ts +2 -0
- package/dist/__tests__/standalone.test.d.ts.map +1 -0
- package/dist/__tests__/standalone.test.js +177 -0
- package/dist/__tests__/standalone.test.js.map +1 -0
- package/dist/builders/search-index-builder.d.ts +27 -0
- package/dist/builders/search-index-builder.d.ts.map +1 -0
- package/dist/builders/search-index-builder.js +133 -0
- package/dist/builders/search-index-builder.js.map +1 -0
- package/dist/engines/index.d.ts +62 -0
- package/dist/engines/index.d.ts.map +1 -0
- package/dist/engines/index.js +34 -0
- package/dist/engines/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +15 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +256 -0
- package/dist/indexer.js.map +1 -0
- package/dist/plugin.d.ts +24 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +40 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins/content-id.d.ts +35 -0
- package/dist/plugins/content-id.d.ts.map +1 -0
- package/dist/plugins/content-id.js +267 -0
- package/dist/plugins/content-id.js.map +1 -0
- package/dist/plugins/search-index.d.ts +27 -0
- package/dist/plugins/search-index.d.ts.map +1 -0
- package/dist/plugins/search-index.js +242 -0
- package/dist/plugins/search-index.js.map +1 -0
- package/dist/searcher.d.ts +16 -0
- package/dist/searcher.d.ts.map +1 -0
- package/dist/searcher.js +83 -0
- package/dist/searcher.js.map +1 -0
- package/dist/standalone.d.ts +42 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +211 -0
- package/dist/standalone.js.map +1 -0
- package/dist/tokenizer.d.ts +48 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/tokenizer.js +85 -0
- package/dist/tokenizer.js.map +1 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/extract-text.d.ts +19 -0
- package/dist/utils/extract-text.d.ts.map +1 -0
- package/dist/utils/extract-text.js +55 -0
- package/dist/utils/extract-text.js.map +1 -0
- 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 @@
|
|
|
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"}
|