@robthepcguy/rag-vault 1.5.1 → 1.5.2

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 CHANGED
@@ -430,6 +430,12 @@ pnpm dev
430
430
 
431
431
  # Run web server locally
432
432
  pnpm web:dev
433
+
434
+ # Release to npm (local, guarded)
435
+ pnpm release:patch
436
+ pnpm release:minor
437
+ pnpm release:major
438
+ pnpm release:dry
433
439
  ```
434
440
 
435
441
 
@@ -440,11 +446,13 @@ pnpm web:dev
440
446
 
441
447
  Use `RUN_EMBEDDING_INTEGRATION=1` to explicitly opt into network/model-dependent suites.
442
448
 
443
- ### CI Strategy
449
+ ### Release Strategy
444
450
 
445
- - `quality.yml` runs on PRs and pushes and enforces the root quality gate (`pnpm check:all`), which includes backend checks and web-ui type/lint/format checks plus unit tests.
446
- - A nightly scheduled job runs the integration/E2E suite so model-dependent workflows stay healthy without blocking every PR.
447
- - `publish-npm.yml` publishes to npm on GitHub Releases, validates tag/version alignment, blocks duplicate npm versions, and supports a manual dry-run, while a real publish requires `NPM_TOKEN`.
451
+ - Releases are local and scripted via `scripts/release-npm.sh`.
452
+ - Supported bumps: `patch`, `minor`, `major`.
453
+ - The script runs dependency installs, `pnpm check:all`, and `pnpm ui:build` before touching version files.
454
+ - `package.json` and `server.json` versions are updated only after checks pass, and auto-restored if any later step fails.
455
+ - `pnpm release:dry` performs the full gate plus npm dry-run publish and always restores version files.
448
456
 
449
457
  ### Project Structure
450
458
 
@@ -149,6 +149,30 @@ export declare class VectorStore {
149
149
  * Record FTS success (resets circuit breaker)
150
150
  */
151
151
  private recordFtsSuccess;
152
+ /**
153
+ * Extract unsupported custom metadata field from LanceDB schema mismatch errors.
154
+ *
155
+ * Returns:
156
+ * - specific key (e.g., "character") for metadata.custom.character mismatch
157
+ * - CUSTOM_METADATA_ALL_FIELDS when metadata.custom itself is unsupported
158
+ * - null when error is unrelated
159
+ */
160
+ private extractUnsupportedCustomMetadataField;
161
+ /**
162
+ * Remove unsupported custom metadata field from chunks for schema compatibility.
163
+ *
164
+ * @param chunks - Source chunks
165
+ * @param field - Unsupported field name, or CUSTOM_METADATA_ALL_FIELDS to drop all custom metadata
166
+ * @returns Sanitized chunks and whether any changes were applied
167
+ */
168
+ private stripUnsupportedCustomMetadata;
169
+ /**
170
+ * Add chunks to existing table with automatic fallback for custom metadata schema mismatches.
171
+ *
172
+ * LanceDB infers struct fields from early inserts, so later custom metadata keys may fail.
173
+ * This method retries by stripping only unsupported custom fields when needed.
174
+ */
175
+ private addChunksWithSchemaFallback;
152
176
  /**
153
177
  * Initialize LanceDB and create table
154
178
  */
@@ -74,6 +74,13 @@ const DELETE_IGNORABLE_PATTERNS = [
74
74
  'no rows',
75
75
  'empty result',
76
76
  ];
77
+ /**
78
+ * LanceDB schema mismatch pattern for custom metadata fields.
79
+ * Example: "Found field not in schema: metadata.custom.character at row 0"
80
+ */
81
+ const CUSTOM_METADATA_SCHEMA_MISMATCH_REGEX = /Found field not in schema:\s*metadata\.custom(?:\.([A-Za-z0-9_-]+))?/i;
82
+ /** Sentinel for "metadata.custom itself is not in schema" */
83
+ const CUSTOM_METADATA_ALL_FIELDS = '__all__';
77
84
  /**
78
85
  * Regex for validating file paths before use in queries.
79
86
  * Allows alphanumeric characters, slashes, dots, underscores, hyphens, colons (Windows), and spaces.
@@ -311,6 +318,108 @@ class VectorStore {
311
318
  this.ftsLastFailure = null;
312
319
  }
313
320
  }
321
+ /**
322
+ * Extract unsupported custom metadata field from LanceDB schema mismatch errors.
323
+ *
324
+ * Returns:
325
+ * - specific key (e.g., "character") for metadata.custom.character mismatch
326
+ * - CUSTOM_METADATA_ALL_FIELDS when metadata.custom itself is unsupported
327
+ * - null when error is unrelated
328
+ */
329
+ extractUnsupportedCustomMetadataField(error) {
330
+ const message = error instanceof Error ? error.message : typeof error === 'string' ? error : String(error);
331
+ const match = CUSTOM_METADATA_SCHEMA_MISMATCH_REGEX.exec(message);
332
+ if (!match)
333
+ return null;
334
+ return match[1] || CUSTOM_METADATA_ALL_FIELDS;
335
+ }
336
+ /**
337
+ * Remove unsupported custom metadata field from chunks for schema compatibility.
338
+ *
339
+ * @param chunks - Source chunks
340
+ * @param field - Unsupported field name, or CUSTOM_METADATA_ALL_FIELDS to drop all custom metadata
341
+ * @returns Sanitized chunks and whether any changes were applied
342
+ */
343
+ stripUnsupportedCustomMetadata(chunks, field) {
344
+ let changed = false;
345
+ const sanitizedChunks = chunks.map((chunk) => {
346
+ const custom = chunk.metadata.custom;
347
+ if (!custom)
348
+ return chunk;
349
+ // Entire custom object is unsupported by table schema
350
+ if (field === CUSTOM_METADATA_ALL_FIELDS) {
351
+ changed = true;
352
+ const metadataWithoutCustom = { ...chunk.metadata };
353
+ delete metadataWithoutCustom.custom;
354
+ return {
355
+ ...chunk,
356
+ metadata: metadataWithoutCustom,
357
+ };
358
+ }
359
+ // Specific custom key is unsupported by table schema
360
+ if (!(field in custom)) {
361
+ return chunk;
362
+ }
363
+ changed = true;
364
+ const filteredCustom = Object.fromEntries(Object.entries(custom).filter(([key]) => key !== field));
365
+ if (Object.keys(filteredCustom).length === 0) {
366
+ const metadataWithoutCustom = { ...chunk.metadata };
367
+ delete metadataWithoutCustom.custom;
368
+ return {
369
+ ...chunk,
370
+ metadata: metadataWithoutCustom,
371
+ };
372
+ }
373
+ return {
374
+ ...chunk,
375
+ metadata: {
376
+ ...chunk.metadata,
377
+ custom: filteredCustom,
378
+ },
379
+ };
380
+ });
381
+ return { chunks: sanitizedChunks, changed };
382
+ }
383
+ /**
384
+ * Add chunks to existing table with automatic fallback for custom metadata schema mismatches.
385
+ *
386
+ * LanceDB infers struct fields from early inserts, so later custom metadata keys may fail.
387
+ * This method retries by stripping only unsupported custom fields when needed.
388
+ */
389
+ async addChunksWithSchemaFallback(chunks) {
390
+ if (!this.table) {
391
+ throw new index_js_1.DatabaseError('VectorStore is not initialized. Call initialize() first.');
392
+ }
393
+ let chunksToInsert = chunks;
394
+ const removedFields = new Set();
395
+ // Multiple unknown keys can surface one-by-one; allow bounded retries.
396
+ for (let attempt = 0; attempt < 10; attempt++) {
397
+ try {
398
+ const records = chunksToInsert.map(toDbRecord);
399
+ await this.table.add(records);
400
+ if (removedFields.size > 0) {
401
+ const removed = Array.from(removedFields)
402
+ .map((field) => (field === CUSTOM_METADATA_ALL_FIELDS ? 'metadata.custom' : field))
403
+ .join(', ');
404
+ console.warn(`VectorStore: Removed unsupported custom metadata field(s) for schema compatibility: ${removed}`);
405
+ }
406
+ return;
407
+ }
408
+ catch (error) {
409
+ const unsupportedField = this.extractUnsupportedCustomMetadataField(error);
410
+ if (!unsupportedField) {
411
+ throw error;
412
+ }
413
+ const { chunks: strippedChunks, changed } = this.stripUnsupportedCustomMetadata(chunksToInsert, unsupportedField);
414
+ if (!changed) {
415
+ throw error;
416
+ }
417
+ removedFields.add(unsupportedField);
418
+ chunksToInsert = strippedChunks;
419
+ }
420
+ }
421
+ throw new index_js_1.DatabaseError('Failed to insert chunks after schema fallback retries');
422
+ }
314
423
  /**
315
424
  * Initialize LanceDB and create table
316
425
  */
@@ -450,8 +559,7 @@ class VectorStore {
450
559
  }
451
560
  }
452
561
  // Add data to existing table
453
- const records = chunksWithFingerprints.map(toDbRecord);
454
- await this.table.add(records);
562
+ await this.addChunksWithSchemaFallback(chunksWithFingerprints);
455
563
  // Rebuild FTS index after adding new data
456
564
  await this.rebuildFtsIndex();
457
565
  console.error(`VectorStore: Inserted ${chunks.length} chunks`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robthepcguy/rag-vault",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Local RAG MCP Server - Easy-to-setup document search with minimal configuration",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -116,6 +116,11 @@
116
116
  "web": "tsx src/web/index.ts",
117
117
  "test:unit": "vitest run --project backend-unit --project web-ui",
118
118
  "test:integration": "RUN_EMBEDDING_INTEGRATION=1 vitest run --project backend-integration",
119
- "hooks:install": "git config core.hooksPath .githooks"
119
+ "hooks:install": "git config core.hooksPath .githooks",
120
+ "release:patch": "bash ./scripts/release-npm.sh --bump patch",
121
+ "release:minor": "bash ./scripts/release-npm.sh --bump minor",
122
+ "release:major": "bash ./scripts/release-npm.sh --bump major",
123
+ "release:dry": "bash ./scripts/release-npm.sh --bump patch --dry-run",
124
+ "release:publish": "bash ./scripts/release-npm.sh --bump patch"
120
125
  }
121
126
  }