Skip to content

Commit effe803

Browse files
author
banxuan.zyx
committed
feat: 支持以只读模式打开已有 zvec
1 parent c792560 commit effe803

5 files changed

Lines changed: 59 additions & 5 deletions

File tree

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ await ctx.close();
5757
| Parameter | Type | Default | Description |
5858
|-----------|------|---------|-------------|
5959
| `vectorsDir` | `string` | `.context/vectors` | Directory to store vector files |
60+
| `readOnly` | `boolean` | `false` | Open existing `.zvec` files only. In read-only mode, missing libraries are not created and `load()`/writes throw. |
6061
| `basePath` | `string` | `process.cwd()` | Base path for resolving document IDs. Set for cross-machine consistent IDs. |
6162
| `onProgress` | `(phase, detail) => void` || Progress callback for `load()` phases: `'load'``'embed'``'insert'`. |
6263
| queryExpansion | QueryExpansionOptions | false | false | Query expansion with user-provided synonym map. false disables. Without synonyms, expansion is a no-op. |
@@ -98,6 +99,24 @@ const ctxNoExpand = await Context.create({
9899
});
99100
```
100101
102+
#### Read-only zvec Example
103+
104+
Use `readOnly: true` when `.zvec` files are prepared elsewhere and the current
105+
process should only query them:
106+
107+
```typescript
108+
const ctx = await Context.create({
109+
vectorsDir: '.context/vectors',
110+
readOnly: true,
111+
});
112+
113+
const results = await ctx.query('How to configure a line chart', { library: 'g2' });
114+
```
115+
116+
In read-only mode, `Context` opens existing `${library}.zvec` files with
117+
`ZVecOpen`. It does not create missing stores, and `load()` will throw because
118+
it would mutate the zvec file.
119+
101120
### `ctx.load(library, pattern)`
102121
103122
Load files into a specified library with automatic batch vectorization. Documents are embedded in batches and inserted into the vector store. A content-hash change detection mechanism re-embeds files whose content has changed since the last load.
@@ -219,4 +238,4 @@ await ctx.close();
219238
220239
## License
221240
222-
MIT
241+
MIT

src/context.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,18 @@ export class Context {
4646

4747
const vectorsDir = options.vectorsDir ?? '.context/vectors';
4848

49-
if (!fs.existsSync(vectorsDir)) {
49+
if (!options.readOnly && !fs.existsSync(vectorsDir)) {
5050
fs.mkdirSync(vectorsDir, { recursive: true });
5151
}
5252

5353
return new Context(options, embedder, { dimensions: embedder.dimensions });
5454
}
5555

5656
async load(library: string, pattern: string | string[]): Promise<void> {
57+
if (this.options.readOnly) {
58+
throw new Error(`Cannot load docs into library "${library}" in read-only mode`);
59+
}
60+
5761
const patterns = Array.isArray(pattern) ? pattern : [pattern];
5862
const files = await glob(patterns, { absolute: true });
5963

@@ -164,4 +168,4 @@ export class Context {
164168
async close(): Promise<void> {
165169
this.store.close();
166170
}
167-
}
171+
}

src/storage/store.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class Store {
4343
if (fs.existsSync(filePath)) {
4444
collection = ZVecOpen(filePath);
4545
} else {
46+
if (this.options?.readOnly) {
47+
throw new Error(`Cannot open zvec for library "${library}" in read-only mode: ${filePath} does not exist`);
48+
}
49+
4650
const schema = buildZvecSchema(this.embedder.dimensions, tokenizerName);
4751
collection = ZVecCreateAndOpen(filePath, schema);
4852
}
@@ -53,6 +57,10 @@ export class Store {
5357

5458
/** Insert docs (upsert semantics). */
5559
addDoc(library: string, docs: ZvecDoc[]): void {
60+
if (this.options?.readOnly) {
61+
throw new Error(`Cannot add docs to library "${library}" in read-only mode`);
62+
}
63+
5664
const collection = this.acquireZvec(library);
5765
if (docs.length === 0) return;
5866

@@ -166,4 +174,4 @@ export class Store {
166174
fields: r.fields ?? {},
167175
}));
168176
}
169-
}
177+
}

src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ export interface ContextOptions {
4848
* Defaults to `'.context/vectors'`.
4949
*/
5050
vectorsDir?: string;
51+
/**
52+
* Open existing vector files without creating or mutating zvec stores.
53+
*
54+
* When enabled, `query()` can read from existing `${library}.zvec` files,
55+
* while `load()` and any write path will throw. Missing zvec files are
56+
* reported as errors instead of being created.
57+
*
58+
* Defaults to `false`.
59+
*/
60+
readOnly?: boolean;
5161
/**
5262
* Base path for resolving document IDs.
5363
*

test/storage/store.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ describe('Store', () => {
4343
const zvec2 = store.acquireZvec('test-lib', 'jieba');
4444
expect(zvec1).toBe(zvec2);
4545
});
46+
47+
it('should not create missing zvec in read-only mode', () => {
48+
const readOnlyStore = new Store(testDir, embedder, { readOnly: true });
49+
expect(() => readOnlyStore.acquireZvec('missing-lib', 'jieba')).toThrow(/read-only mode/);
50+
expect(fs.existsSync(path.join(testDir, 'missing-lib.zvec'))).toBe(false);
51+
readOnlyStore.close();
52+
});
4653
});
4754

4855
describe('addDoc', () => {
@@ -60,6 +67,12 @@ describe('Store', () => {
6067
it('should not throw for empty docs array', () => {
6168
expect(() => store.addDoc('test-lib', [])).not.toThrow();
6269
});
70+
71+
it('should reject writes in read-only mode', () => {
72+
const readOnlyStore = new Store(testDir, embedder, { readOnly: true });
73+
expect(() => readOnlyStore.addDoc('test-lib', [])).toThrow(/read-only mode/);
74+
readOnlyStore.close();
75+
});
6376
});
6477

6578
describe('fetchDocs', () => {
@@ -110,4 +123,4 @@ describe('Store', () => {
110123
expect(() => store.close()).not.toThrow();
111124
});
112125
});
113-
});
126+
});

0 commit comments

Comments
 (0)