@zabaca/lattice 1.0.3 → 1.0.4

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 +54 -17
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -832,7 +832,6 @@ EmbeddingService = __legacyDecorateClassTS([
832
832
  ], EmbeddingService);
833
833
 
834
834
  // src/graph/graph.service.ts
835
- import { existsSync as existsSync3, unlinkSync } from "fs";
836
835
  import { DuckDBInstance } from "@duckdb/node-api";
837
836
  import { Injectable as Injectable6, Logger as Logger3 } from "@nestjs/common";
838
837
  import { ConfigService as ConfigService2 } from "@nestjs/config";
@@ -845,11 +844,35 @@ class GraphService {
845
844
  connecting = null;
846
845
  vectorIndexes = new Set;
847
846
  embeddingDimensions;
847
+ signalHandlersRegistered = false;
848
848
  constructor(configService) {
849
849
  this.configService = configService;
850
850
  ensureLatticeHome();
851
851
  this.dbPath = getDatabasePath();
852
852
  this.embeddingDimensions = this.configService.get("EMBEDDING_DIMENSIONS") || 512;
853
+ this.registerSignalHandlers();
854
+ }
855
+ registerSignalHandlers() {
856
+ if (this.signalHandlersRegistered)
857
+ return;
858
+ this.signalHandlersRegistered = true;
859
+ const gracefulShutdown = async (signal) => {
860
+ this.logger.log(`Received ${signal}, checkpointing before exit...`);
861
+ try {
862
+ await this.checkpoint();
863
+ await this.disconnect();
864
+ } catch (error) {
865
+ this.logger.error(`Error during graceful shutdown: ${error instanceof Error ? error.message : String(error)}`);
866
+ }
867
+ process.exit(0);
868
+ };
869
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
870
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
871
+ process.on("beforeExit", async () => {
872
+ if (this.connection) {
873
+ await this.checkpoint();
874
+ }
875
+ });
853
876
  }
854
877
  async onModuleDestroy() {
855
878
  await this.disconnect();
@@ -875,12 +898,7 @@ class GraphService {
875
898
  }
876
899
  async connect() {
877
900
  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, {
901
+ this.instance = await DuckDBInstance.create(":memory:", {
884
902
  allow_unsigned_extensions: "true"
885
903
  });
886
904
  this.connection = await this.instance.connect();
@@ -894,8 +912,10 @@ class GraphService {
894
912
  } catch (e) {
895
913
  this.logger.warn(`DuckPGQ extension not available: ${e instanceof Error ? e.message : String(e)}`);
896
914
  }
915
+ await this.connection.run(`ATTACH '${this.dbPath}' AS lattice (READ_WRITE);`);
916
+ await this.connection.run("USE lattice;");
897
917
  await this.initializeSchema();
898
- this.logger.log(`Connected to DuckDB at ${this.dbPath}`);
918
+ this.logger.log(`Connected to DuckDB (in-memory + ATTACH) at ${this.dbPath}`);
899
919
  } catch (error) {
900
920
  this.connection = null;
901
921
  this.instance = null;
@@ -905,11 +925,7 @@ class GraphService {
905
925
  }
906
926
  async disconnect() {
907
927
  if (this.connection) {
908
- try {
909
- await this.connection.run("CHECKPOINT;");
910
- } catch {
911
- this.logger.debug("Checkpoint failed during disconnect");
912
- }
928
+ await this.checkpoint();
913
929
  this.connection.closeSync();
914
930
  this.connection = null;
915
931
  this.logger.log("Disconnected from DuckDB");
@@ -918,6 +934,17 @@ class GraphService {
918
934
  this.instance = null;
919
935
  }
920
936
  }
937
+ async checkpoint() {
938
+ if (!this.connection) {
939
+ return;
940
+ }
941
+ try {
942
+ await this.connection.run("CHECKPOINT;");
943
+ this.logger.debug("Checkpoint completed");
944
+ } catch (error) {
945
+ this.logger.warn(`Checkpoint failed: ${error instanceof Error ? error.message : String(error)}`);
946
+ }
947
+ }
921
948
  async initializeSchema() {
922
949
  if (!this.connection) {
923
950
  throw new Error("Cannot initialize schema: not connected");
@@ -1384,7 +1411,7 @@ import { Command as Command4, CommandRunner as CommandRunner4, Option as Option3
1384
1411
 
1385
1412
  // src/sync/manifest.service.ts
1386
1413
  import { createHash as createHash2 } from "crypto";
1387
- import { existsSync as existsSync4 } from "fs";
1414
+ import { existsSync as existsSync3 } from "fs";
1388
1415
  import { readFile as readFile3, writeFile } from "fs/promises";
1389
1416
  import { Injectable as Injectable8 } from "@nestjs/common";
1390
1417
 
@@ -1412,7 +1439,7 @@ class ManifestService {
1412
1439
  }
1413
1440
  async load() {
1414
1441
  try {
1415
- if (existsSync4(this.manifestPath)) {
1442
+ if (existsSync3(this.manifestPath)) {
1416
1443
  const content = await readFile3(this.manifestPath, "utf-8");
1417
1444
  this.manifest = SyncManifestSchema.parse(JSON.parse(content));
1418
1445
  } else {
@@ -1898,7 +1925,7 @@ CascadeService = __legacyDecorateClassTS([
1898
1925
  ], CascadeService);
1899
1926
 
1900
1927
  // src/sync/path-resolver.service.ts
1901
- import { existsSync as existsSync5 } from "fs";
1928
+ import { existsSync as existsSync4 } from "fs";
1902
1929
  import { isAbsolute, resolve as resolve2 } from "path";
1903
1930
  import { Injectable as Injectable10 } from "@nestjs/common";
1904
1931
  class PathResolverService {
@@ -1920,7 +1947,7 @@ class PathResolverService {
1920
1947
  if (requireInDocs && !this.isUnderDocs(resolvedPath)) {
1921
1948
  throw new Error(`Path "${userPath}" resolves to "${resolvedPath}" which is outside the docs directory (${this.docsPath})`);
1922
1949
  }
1923
- if (requireExists && !existsSync5(resolvedPath)) {
1950
+ if (requireExists && !existsSync4(resolvedPath)) {
1924
1951
  throw new Error(`Path "${userPath}" does not exist (resolved to: ${resolvedPath})`);
1925
1952
  }
1926
1953
  return resolvedPath;
@@ -2048,10 +2075,13 @@ class SyncService {
2048
2075
  }
2049
2076
  if (!options.dryRun) {
2050
2077
  result.entityEmbeddingsGenerated = await this.syncEntities(uniqueEntities, options);
2078
+ await this.graph.checkpoint();
2051
2079
  if (options.verbose) {
2052
2080
  this.logger.log(`Synced ${uniqueEntities.size} entities, generated ${result.entityEmbeddingsGenerated} embeddings`);
2053
2081
  }
2054
2082
  }
2083
+ const CHECKPOINT_BATCH_SIZE = 10;
2084
+ let processedCount = 0;
2055
2085
  for (const change of changes) {
2056
2086
  try {
2057
2087
  const doc = docsByPath.get(change.path);
@@ -2074,12 +2104,19 @@ class SyncService {
2074
2104
  if (change.embeddingGenerated) {
2075
2105
  result.embeddingsGenerated++;
2076
2106
  }
2107
+ processedCount++;
2108
+ if (!options.dryRun && processedCount % CHECKPOINT_BATCH_SIZE === 0) {
2109
+ await this.graph.checkpoint();
2110
+ }
2077
2111
  } catch (error) {
2078
2112
  const errorMessage = error instanceof Error ? error.message : String(error);
2079
2113
  result.errors.push({ path: change.path, error: errorMessage });
2080
2114
  this.logger.warn(`Error processing ${change.path}: ${errorMessage}`);
2081
2115
  }
2082
2116
  }
2117
+ if (!options.dryRun && processedCount > 0) {
2118
+ await this.graph.checkpoint();
2119
+ }
2083
2120
  if (!options.dryRun) {
2084
2121
  await this.manifest.save();
2085
2122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zabaca/lattice",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Human-initiated, AI-powered knowledge graph for markdown documentation",
5
5
  "type": "module",
6
6
  "bin": {