@zabaca/lattice 1.0.3 → 1.0.5

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 (2) hide show
  1. package/dist/main.js +72 -19
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -711,7 +711,14 @@ class VoyageEmbeddingProvider {
711
711
  const embeddings = await this.generateEmbeddings([text]);
712
712
  return embeddings[0];
713
713
  }
714
+ async generateQueryEmbedding(text) {
715
+ const embeddings = await this.generateEmbeddingsWithType([text], "query");
716
+ return embeddings[0];
717
+ }
714
718
  async generateEmbeddings(texts) {
719
+ return this.generateEmbeddingsWithType(texts, this.inputType);
720
+ }
721
+ async generateEmbeddingsWithType(texts, inputType) {
715
722
  if (!texts || texts.length === 0) {
716
723
  return [];
717
724
  }
@@ -726,7 +733,7 @@ class VoyageEmbeddingProvider {
726
733
  model: this.model,
727
734
  input: texts,
728
735
  output_dimension: this.dimensions,
729
- input_type: this.inputType
736
+ input_type: inputType
730
737
  })
731
738
  });
732
739
  if (!response.ok) {
@@ -813,6 +820,15 @@ class EmbeddingService {
813
820
  }
814
821
  return this.provider.generateEmbedding(text);
815
822
  }
823
+ async generateQueryEmbedding(text) {
824
+ if (!text || text.trim().length === 0) {
825
+ throw new Error("Cannot generate embedding for empty text");
826
+ }
827
+ if (this.provider.generateQueryEmbedding) {
828
+ return this.provider.generateQueryEmbedding(text);
829
+ }
830
+ return this.provider.generateEmbedding(text);
831
+ }
816
832
  async generateEmbeddings(texts) {
817
833
  const validTexts = texts.filter((t) => t && t.trim().length > 0);
818
834
  if (validTexts.length === 0) {
@@ -832,7 +848,6 @@ EmbeddingService = __legacyDecorateClassTS([
832
848
  ], EmbeddingService);
833
849
 
834
850
  // src/graph/graph.service.ts
835
- import { existsSync as existsSync3, unlinkSync } from "fs";
836
851
  import { DuckDBInstance } from "@duckdb/node-api";
837
852
  import { Injectable as Injectable6, Logger as Logger3 } from "@nestjs/common";
838
853
  import { ConfigService as ConfigService2 } from "@nestjs/config";
@@ -845,11 +860,35 @@ class GraphService {
845
860
  connecting = null;
846
861
  vectorIndexes = new Set;
847
862
  embeddingDimensions;
863
+ signalHandlersRegistered = false;
848
864
  constructor(configService) {
849
865
  this.configService = configService;
850
866
  ensureLatticeHome();
851
867
  this.dbPath = getDatabasePath();
852
868
  this.embeddingDimensions = this.configService.get("EMBEDDING_DIMENSIONS") || 512;
869
+ this.registerSignalHandlers();
870
+ }
871
+ registerSignalHandlers() {
872
+ if (this.signalHandlersRegistered)
873
+ return;
874
+ this.signalHandlersRegistered = true;
875
+ const gracefulShutdown = async (signal) => {
876
+ this.logger.log(`Received ${signal}, checkpointing before exit...`);
877
+ try {
878
+ await this.checkpoint();
879
+ await this.disconnect();
880
+ } catch (error) {
881
+ this.logger.error(`Error during graceful shutdown: ${error instanceof Error ? error.message : String(error)}`);
882
+ }
883
+ process.exit(0);
884
+ };
885
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
886
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
887
+ process.on("beforeExit", async () => {
888
+ if (this.connection) {
889
+ await this.checkpoint();
890
+ }
891
+ });
853
892
  }
854
893
  async onModuleDestroy() {
855
894
  await this.disconnect();
@@ -875,12 +914,7 @@ class GraphService {
875
914
  }
876
915
  async connect() {
877
916
  try {
878
- const walPath = `${this.dbPath}.wal`;
879
- if (existsSync3(walPath)) {
880
- this.logger.warn(`Removing WAL file to prevent HNSW index replay failure`);
881
- unlinkSync(walPath);
882
- }
883
- this.instance = await DuckDBInstance.create(this.dbPath, {
917
+ this.instance = await DuckDBInstance.create(":memory:", {
884
918
  allow_unsigned_extensions: "true"
885
919
  });
886
920
  this.connection = await this.instance.connect();
@@ -894,8 +928,10 @@ class GraphService {
894
928
  } catch (e) {
895
929
  this.logger.warn(`DuckPGQ extension not available: ${e instanceof Error ? e.message : String(e)}`);
896
930
  }
931
+ await this.connection.run(`ATTACH '${this.dbPath}' AS lattice (READ_WRITE);`);
932
+ await this.connection.run("USE lattice;");
897
933
  await this.initializeSchema();
898
- this.logger.log(`Connected to DuckDB at ${this.dbPath}`);
934
+ this.logger.log(`Connected to DuckDB (in-memory + ATTACH) at ${this.dbPath}`);
899
935
  } catch (error) {
900
936
  this.connection = null;
901
937
  this.instance = null;
@@ -905,11 +941,7 @@ class GraphService {
905
941
  }
906
942
  async disconnect() {
907
943
  if (this.connection) {
908
- try {
909
- await this.connection.run("CHECKPOINT;");
910
- } catch {
911
- this.logger.debug("Checkpoint failed during disconnect");
912
- }
944
+ await this.checkpoint();
913
945
  this.connection.closeSync();
914
946
  this.connection = null;
915
947
  this.logger.log("Disconnected from DuckDB");
@@ -918,6 +950,17 @@ class GraphService {
918
950
  this.instance = null;
919
951
  }
920
952
  }
953
+ async checkpoint() {
954
+ if (!this.connection) {
955
+ return;
956
+ }
957
+ try {
958
+ await this.connection.run("CHECKPOINT;");
959
+ this.logger.debug("Checkpoint completed");
960
+ } catch (error) {
961
+ this.logger.warn(`Checkpoint failed: ${error instanceof Error ? error.message : String(error)}`);
962
+ }
963
+ }
921
964
  async initializeSchema() {
922
965
  if (!this.connection) {
923
966
  throw new Error("Cannot initialize schema: not connected");
@@ -1207,7 +1250,7 @@ class SearchCommand extends CommandRunner3 {
1207
1250
  const query = inputs[0];
1208
1251
  const limit = Math.min(parseInt(options.limit || "20", 10), 100);
1209
1252
  try {
1210
- const queryEmbedding = await this.embeddingService.generateEmbedding(query);
1253
+ const queryEmbedding = await this.embeddingService.generateQueryEmbedding(query);
1211
1254
  let results;
1212
1255
  if (options.label) {
1213
1256
  const labelResults = await this.graphService.vectorSearch(options.label, queryEmbedding, limit);
@@ -1384,7 +1427,7 @@ import { Command as Command4, CommandRunner as CommandRunner4, Option as Option3
1384
1427
 
1385
1428
  // src/sync/manifest.service.ts
1386
1429
  import { createHash as createHash2 } from "crypto";
1387
- import { existsSync as existsSync4 } from "fs";
1430
+ import { existsSync as existsSync3 } from "fs";
1388
1431
  import { readFile as readFile3, writeFile } from "fs/promises";
1389
1432
  import { Injectable as Injectable8 } from "@nestjs/common";
1390
1433
 
@@ -1412,7 +1455,7 @@ class ManifestService {
1412
1455
  }
1413
1456
  async load() {
1414
1457
  try {
1415
- if (existsSync4(this.manifestPath)) {
1458
+ if (existsSync3(this.manifestPath)) {
1416
1459
  const content = await readFile3(this.manifestPath, "utf-8");
1417
1460
  this.manifest = SyncManifestSchema.parse(JSON.parse(content));
1418
1461
  } else {
@@ -1898,7 +1941,7 @@ CascadeService = __legacyDecorateClassTS([
1898
1941
  ], CascadeService);
1899
1942
 
1900
1943
  // src/sync/path-resolver.service.ts
1901
- import { existsSync as existsSync5 } from "fs";
1944
+ import { existsSync as existsSync4 } from "fs";
1902
1945
  import { isAbsolute, resolve as resolve2 } from "path";
1903
1946
  import { Injectable as Injectable10 } from "@nestjs/common";
1904
1947
  class PathResolverService {
@@ -1920,7 +1963,7 @@ class PathResolverService {
1920
1963
  if (requireInDocs && !this.isUnderDocs(resolvedPath)) {
1921
1964
  throw new Error(`Path "${userPath}" resolves to "${resolvedPath}" which is outside the docs directory (${this.docsPath})`);
1922
1965
  }
1923
- if (requireExists && !existsSync5(resolvedPath)) {
1966
+ if (requireExists && !existsSync4(resolvedPath)) {
1924
1967
  throw new Error(`Path "${userPath}" does not exist (resolved to: ${resolvedPath})`);
1925
1968
  }
1926
1969
  return resolvedPath;
@@ -2048,10 +2091,13 @@ class SyncService {
2048
2091
  }
2049
2092
  if (!options.dryRun) {
2050
2093
  result.entityEmbeddingsGenerated = await this.syncEntities(uniqueEntities, options);
2094
+ await this.graph.checkpoint();
2051
2095
  if (options.verbose) {
2052
2096
  this.logger.log(`Synced ${uniqueEntities.size} entities, generated ${result.entityEmbeddingsGenerated} embeddings`);
2053
2097
  }
2054
2098
  }
2099
+ const CHECKPOINT_BATCH_SIZE = 10;
2100
+ let processedCount = 0;
2055
2101
  for (const change of changes) {
2056
2102
  try {
2057
2103
  const doc = docsByPath.get(change.path);
@@ -2074,12 +2120,19 @@ class SyncService {
2074
2120
  if (change.embeddingGenerated) {
2075
2121
  result.embeddingsGenerated++;
2076
2122
  }
2123
+ processedCount++;
2124
+ if (!options.dryRun && processedCount % CHECKPOINT_BATCH_SIZE === 0) {
2125
+ await this.graph.checkpoint();
2126
+ }
2077
2127
  } catch (error) {
2078
2128
  const errorMessage = error instanceof Error ? error.message : String(error);
2079
2129
  result.errors.push({ path: change.path, error: errorMessage });
2080
2130
  this.logger.warn(`Error processing ${change.path}: ${errorMessage}`);
2081
2131
  }
2082
2132
  }
2133
+ if (!options.dryRun && processedCount > 0) {
2134
+ await this.graph.checkpoint();
2135
+ }
2083
2136
  if (!options.dryRun) {
2084
2137
  await this.manifest.save();
2085
2138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zabaca/lattice",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Human-initiated, AI-powered knowledge graph for markdown documentation",
5
5
  "type": "module",
6
6
  "bin": {