@danielsimonjr/memoryjs 2.3.0 → 2.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # MemoryJS
2
2
 
3
- [![Version](https://img.shields.io/badge/version-2.3.0-blue.svg)](https://github.com/danielsimonjr/memoryjs)
3
+ [![Version](https://img.shields.io/badge/version-2.5.0-blue.svg)](https://github.com/danielsimonjr/memoryjs)
4
4
  [![NPM](https://img.shields.io/npm/v/@danielsimonjr/memoryjs.svg)](https://www.npmjs.com/package/@danielsimonjr/memoryjs)
5
5
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
package/dist/cli/index.js CHANGED
@@ -959,12 +959,34 @@ var init_indexes = __esm({
959
959
  });
960
960
 
961
961
  // src/utils/searchCache.ts
962
+ var searchCache_exports = {};
963
+ __export(searchCache_exports, {
964
+ SearchCache: () => SearchCache,
965
+ cleanupAllCaches: () => cleanupAllCaches,
966
+ clearAllSearchCaches: () => clearAllSearchCaches,
967
+ getAllCacheStats: () => getAllCacheStats,
968
+ searchCaches: () => searchCaches
969
+ });
962
970
  function clearAllSearchCaches() {
963
971
  searchCaches.basic.clear();
964
972
  searchCaches.ranked.clear();
965
973
  searchCaches.boolean.clear();
966
974
  searchCaches.fuzzy.clear();
967
975
  }
976
+ function getAllCacheStats() {
977
+ return {
978
+ basic: searchCaches.basic.getStats(),
979
+ ranked: searchCaches.ranked.getStats(),
980
+ boolean: searchCaches.boolean.getStats(),
981
+ fuzzy: searchCaches.fuzzy.getStats()
982
+ };
983
+ }
984
+ function cleanupAllCaches() {
985
+ searchCaches.basic.cleanupExpired();
986
+ searchCaches.ranked.cleanupExpired();
987
+ searchCaches.boolean.cleanupExpired();
988
+ searchCaches.fuzzy.cleanupExpired();
989
+ }
968
990
  var SearchCache, searchCaches;
969
991
  var init_searchCache = __esm({
970
992
  "src/utils/searchCache.ts"() {
@@ -35763,6 +35785,105 @@ var init_inspect = __esm({
35763
35785
  }
35764
35786
  });
35765
35787
 
35788
+ // src/cli/commands/check.ts
35789
+ var check_exports = {};
35790
+ __export(check_exports, {
35791
+ applyFixes: () => applyFixes,
35792
+ detectIssues: () => detectIssues,
35793
+ registerCheckCommand: () => registerCheckCommand
35794
+ });
35795
+ async function detectIssues(ctx) {
35796
+ const graph = await ctx.storage.loadGraph();
35797
+ const names = new Set(graph.entities.map((e) => e.name));
35798
+ const orphans = [];
35799
+ for (const r of graph.relations) {
35800
+ const fromMissing = !names.has(r.from);
35801
+ const toMissing = !names.has(r.to);
35802
+ if (fromMissing || toMissing) {
35803
+ orphans.push({
35804
+ from: r.from,
35805
+ to: r.to,
35806
+ relationType: r.relationType,
35807
+ reason: fromMissing && toMissing ? "both-missing" : fromMissing ? "from-missing" : "to-missing"
35808
+ });
35809
+ }
35810
+ }
35811
+ const missing = [];
35812
+ const cycles = [];
35813
+ const byName = /* @__PURE__ */ new Map();
35814
+ for (const e of graph.entities) byName.set(e.name, { name: e.name, parentId: e.parentId });
35815
+ for (const e of graph.entities) {
35816
+ if (!e.parentId) continue;
35817
+ if (!byName.has(e.parentId)) {
35818
+ missing.push({ entity: e.name, parentId: e.parentId });
35819
+ continue;
35820
+ }
35821
+ const visited = /* @__PURE__ */ new Set([e.name]);
35822
+ let cur = byName.get(e.parentId);
35823
+ while (cur && cur.parentId) {
35824
+ if (visited.has(cur.name)) {
35825
+ cycles.push({ entityInCycle: e.name, cycleThrough: cur.name });
35826
+ break;
35827
+ }
35828
+ visited.add(cur.name);
35829
+ cur = byName.get(cur.parentId);
35830
+ }
35831
+ }
35832
+ return { orphans, missing, cycles };
35833
+ }
35834
+ async function applyFixes(ctx, orphans, missing) {
35835
+ let deleted = 0;
35836
+ let cleared = 0;
35837
+ if (orphans.length > 0) {
35838
+ await ctx.relationManager.deleteRelations(
35839
+ orphans.map((o) => ({ from: o.from, to: o.to, relationType: o.relationType }))
35840
+ );
35841
+ deleted = orphans.length;
35842
+ }
35843
+ for (const m of missing) {
35844
+ try {
35845
+ await ctx.hierarchyManager.setEntityParent(m.entity, null);
35846
+ cleared += 1;
35847
+ } catch {
35848
+ }
35849
+ }
35850
+ return { orphanRelationsDeleted: deleted, missingParentsCleared: cleared };
35851
+ }
35852
+ function registerCheckCommand(program2) {
35853
+ program2.command("check").description("Detect orphan relations + missing parents + hierarchy cycles. Dry-run by default.").option("--apply", "Actually delete orphan relations + clear missing parentIds (cycles always left for human review)").action(async (opts) => {
35854
+ const options = getOptions(program2);
35855
+ const logger2 = createLogger(options);
35856
+ const ctx = createContext(options);
35857
+ try {
35858
+ const { orphans, missing, cycles } = await detectIssues(ctx);
35859
+ const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
35860
+ const report = {
35861
+ ok,
35862
+ applied: Boolean(opts.apply),
35863
+ orphanRelations: orphans,
35864
+ missingParents: missing,
35865
+ hierarchyCycles: cycles
35866
+ };
35867
+ if (opts.apply && (orphans.length > 0 || missing.length > 0)) {
35868
+ report.actions = await applyFixes(ctx, orphans, missing);
35869
+ }
35870
+ console.log(JSON.stringify(report, null, 2));
35871
+ if (!ok && !opts.apply) process.exit(1);
35872
+ } catch (error) {
35873
+ logger2.error(formatError(error.message));
35874
+ process.exit(1);
35875
+ }
35876
+ });
35877
+ }
35878
+ var init_check = __esm({
35879
+ "src/cli/commands/check.ts"() {
35880
+ "use strict";
35881
+ init_esm_shims();
35882
+ init_helpers();
35883
+ init_formatters2();
35884
+ }
35885
+ });
35886
+
35766
35887
  // src/cli/interactive.ts
35767
35888
  var interactive_exports = {};
35768
35889
  __export(interactive_exports, {
@@ -36071,6 +36192,57 @@ Path (${pathResult.length} hops): ${pathResult.path.join(" -> ")}`);
36071
36192
  console.log(JSON.stringify(report, null, 2));
36072
36193
  break;
36073
36194
  }
36195
+ case "check": {
36196
+ const apply = args[0] === "--apply";
36197
+ const { detectIssues: detectIssues2, applyFixes: applyFixes2 } = await Promise.resolve().then(() => (init_check(), check_exports));
36198
+ const { orphans, missing, cycles } = await detectIssues2(ctx);
36199
+ const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
36200
+ const result = {
36201
+ ok,
36202
+ applied: apply,
36203
+ orphanRelations: orphans,
36204
+ missingParents: missing,
36205
+ hierarchyCycles: cycles
36206
+ };
36207
+ if (apply && (orphans.length > 0 || missing.length > 0)) {
36208
+ result.actions = await applyFixes2(ctx, orphans, missing);
36209
+ }
36210
+ console.log(JSON.stringify(result, null, 2));
36211
+ break;
36212
+ }
36213
+ case "heuristics": {
36214
+ const heuristics = await ctx.heuristicManager.list();
36215
+ console.log(JSON.stringify({ heuristics, count: heuristics.length }, null, 2));
36216
+ break;
36217
+ }
36218
+ case "spell": {
36219
+ const query = args.join(" ");
36220
+ if (!query) {
36221
+ console.log(chalk2.yellow("Usage: spell <query>"));
36222
+ break;
36223
+ }
36224
+ const suggestions = await ctx.spellChecker.suggest(query);
36225
+ console.log(JSON.stringify({ query, suggestions, count: suggestions.length }, null, 2));
36226
+ break;
36227
+ }
36228
+ case "cache": {
36229
+ const sub = args[0];
36230
+ const { getAllCacheStats: getAllCacheStats2, clearAllSearchCaches: clearAllSearchCaches2 } = await Promise.resolve().then(() => (init_searchCache(), searchCache_exports));
36231
+ if (sub === "clear") {
36232
+ clearAllSearchCaches2();
36233
+ console.log(JSON.stringify({ cleared: true }, null, 2));
36234
+ } else {
36235
+ console.log(JSON.stringify({ stats: getAllCacheStats2() }, null, 2));
36236
+ }
36237
+ break;
36238
+ }
36239
+ case "reindex": {
36240
+ const t = Date.now();
36241
+ await ctx.rankedSearch.buildIndex();
36242
+ await ctx.spellChecker.rebuild();
36243
+ console.log(JSON.stringify({ ok: true, ranked: "rebuilt", spell: "rebuilt", durationMs: Date.now() - t }, null, 2));
36244
+ break;
36245
+ }
36074
36246
  default:
36075
36247
  console.log(chalk2.yellow(`Unknown command: ${command}. Type "help" for available commands.`));
36076
36248
  }
@@ -36094,6 +36266,11 @@ ${chalk2.green("Available Commands:")}
36094
36266
  ${chalk2.cyan("neighbors <name>")} Incoming + outgoing relations + degree counts
36095
36267
  ${chalk2.cyan("diag")} / ${chalk2.cyan("health")} Quick integrity summary
36096
36268
  ${chalk2.cyan("size")} Graph + storage footprint
36269
+ ${chalk2.cyan("check [--apply]")} Orphan + missing-parent + cycle detect/repair
36270
+ ${chalk2.cyan("heuristics")} List all heuristics
36271
+ ${chalk2.cyan("spell <query>")} Spell suggestions for a query
36272
+ ${chalk2.cyan("cache [clear]")} Search-cache stats; "cache clear" to bust
36273
+ ${chalk2.cyan("reindex")} Rebuild ranked-search + spell vocabulary
36097
36274
  ${chalk2.cyan("history")} Show command history
36098
36275
  ${chalk2.cyan("clear")} Clear screen
36099
36276
  ${chalk2.cyan("help")} Show this help
@@ -38036,89 +38213,100 @@ function registerSpellCommands(program2) {
38036
38213
  });
38037
38214
  }
38038
38215
 
38039
- // src/cli/commands/check.ts
38216
+ // src/cli/commands/index.ts
38217
+ init_check();
38218
+
38219
+ // src/cli/commands/cache.ts
38040
38220
  init_esm_shims();
38221
+ init_searchCache();
38041
38222
  init_helpers();
38042
38223
  init_formatters2();
38043
- async function detectIssues(ctx) {
38044
- const graph = await ctx.storage.loadGraph();
38045
- const names = new Set(graph.entities.map((e) => e.name));
38046
- const orphans = [];
38047
- for (const r of graph.relations) {
38048
- const fromMissing = !names.has(r.from);
38049
- const toMissing = !names.has(r.to);
38050
- if (fromMissing || toMissing) {
38051
- orphans.push({
38052
- from: r.from,
38053
- to: r.to,
38054
- relationType: r.relationType,
38055
- reason: fromMissing && toMissing ? "both-missing" : fromMissing ? "from-missing" : "to-missing"
38056
- });
38057
- }
38058
- }
38059
- const missing = [];
38060
- const cycles = [];
38061
- const byName = /* @__PURE__ */ new Map();
38062
- for (const e of graph.entities) byName.set(e.name, { name: e.name, parentId: e.parentId });
38063
- for (const e of graph.entities) {
38064
- if (!e.parentId) continue;
38065
- if (!byName.has(e.parentId)) {
38066
- missing.push({ entity: e.name, parentId: e.parentId });
38067
- continue;
38224
+ function emitJson4(payload) {
38225
+ console.log(JSON.stringify(payload, null, 2));
38226
+ }
38227
+ function registerCacheCommands(program2) {
38228
+ const cache = program2.command("cache").description("Inspect or bust the per-tier search caches (basic / ranked / boolean / fuzzy)");
38229
+ cache.command("stats").description("Per-cache hits/misses/size/hitRate snapshot. Stats are process-local \u2014 fresh CLI invocations start at zero.").action(() => {
38230
+ try {
38231
+ const stats = getAllCacheStats();
38232
+ emitJson4({ stats });
38233
+ } catch (error) {
38234
+ const logger2 = createLogger(getOptions(program2));
38235
+ logger2.error(formatError(error.message));
38236
+ process.exit(1);
38068
38237
  }
38069
- const visited = /* @__PURE__ */ new Set([e.name]);
38070
- let cur = byName.get(e.parentId);
38071
- while (cur && cur.parentId) {
38072
- if (visited.has(cur.name)) {
38073
- cycles.push({ entityInCycle: e.name, cycleThrough: cur.name });
38074
- break;
38075
- }
38076
- visited.add(cur.name);
38077
- cur = byName.get(cur.parentId);
38238
+ });
38239
+ cache.command("clear").description("Clear all four search caches. Idempotent; safe to call any time.").action(() => {
38240
+ try {
38241
+ clearAllSearchCaches();
38242
+ emitJson4({ cleared: true, caches: ["basic", "ranked", "boolean", "fuzzy"] });
38243
+ } catch (error) {
38244
+ const logger2 = createLogger(getOptions(program2));
38245
+ logger2.error(formatError(error.message));
38246
+ process.exit(1);
38078
38247
  }
38079
- }
38080
- return { orphans, missing, cycles };
38081
- }
38082
- async function applyFixes(ctx, orphans, missing) {
38083
- let deleted = 0;
38084
- let cleared = 0;
38085
- if (orphans.length > 0) {
38086
- await ctx.relationManager.deleteRelations(
38087
- orphans.map((o) => ({ from: o.from, to: o.to, relationType: o.relationType }))
38088
- );
38089
- deleted = orphans.length;
38090
- }
38091
- for (const m of missing) {
38248
+ });
38249
+ cache.command("cleanup").description("Sweep expired entries (TTL) across all caches without dropping live entries.").action(() => {
38092
38250
  try {
38093
- await ctx.hierarchyManager.setEntityParent(m.entity, null);
38094
- cleared += 1;
38095
- } catch {
38251
+ cleanupAllCaches();
38252
+ emitJson4({ cleaned: true, caches: ["basic", "ranked", "boolean", "fuzzy"] });
38253
+ } catch (error) {
38254
+ const logger2 = createLogger(getOptions(program2));
38255
+ logger2.error(formatError(error.message));
38256
+ process.exit(1);
38096
38257
  }
38097
- }
38098
- return { orphanRelationsDeleted: deleted, missingParentsCleared: cleared };
38258
+ });
38099
38259
  }
38100
- function registerCheckCommand(program2) {
38101
- program2.command("check").description("Detect orphan relations + missing parents + hierarchy cycles. Dry-run by default.").option("--apply", "Actually delete orphan relations + clear missing parentIds (cycles always left for human review)").action(async (opts) => {
38260
+
38261
+ // src/cli/commands/reindex.ts
38262
+ init_esm_shims();
38263
+ init_RankedSearch();
38264
+ init_helpers();
38265
+ init_formatters2();
38266
+ import { dirname as dirname10 } from "path";
38267
+ import { performance as performance3 } from "perf_hooks";
38268
+ function emitJson5(payload) {
38269
+ console.log(JSON.stringify(payload, null, 2));
38270
+ }
38271
+ function registerReindexCommand(program2) {
38272
+ program2.command("reindex").description("Rebuild search-side indexes (TF-IDF/BM25 ranked + spell vocabulary).").option("--ranked", "Rebuild only the ranked-search (TF-IDF/BM25) index").option("--spell", "Rebuild only the spell-checker vocabulary").action(async (opts) => {
38102
38273
  const options = getOptions(program2);
38103
38274
  const logger2 = createLogger(options);
38104
38275
  const ctx = createContext(options);
38105
- try {
38106
- const { orphans, missing, cycles } = await detectIssues(ctx);
38107
- const ok = orphans.length === 0 && missing.length === 0 && cycles.length === 0;
38108
- const report = {
38109
- ok,
38110
- applied: Boolean(opts.apply),
38111
- orphanRelations: orphans,
38112
- missingParents: missing,
38113
- hierarchyCycles: cycles
38114
- };
38115
- if (opts.apply && (orphans.length > 0 || missing.length > 0)) {
38116
- report.actions = await applyFixes(ctx, orphans, missing);
38276
+ const targets = opts.ranked === true || opts.spell === true ? { ranked: Boolean(opts.ranked), spell: Boolean(opts.spell) } : { ranked: true, spell: true };
38277
+ const result = {};
38278
+ if (targets.ranked) {
38279
+ const t = performance3.now();
38280
+ try {
38281
+ const storageDir = dirname10(options.storage);
38282
+ const ranked = new RankedSearch(ctx.storage, storageDir);
38283
+ await ranked.buildIndex();
38284
+ result.ranked = { ok: true, durationMs: performance3.now() - t };
38285
+ } catch (e) {
38286
+ result.ranked = {
38287
+ ok: false,
38288
+ durationMs: performance3.now() - t,
38289
+ detail: e instanceof Error ? e.message : String(e)
38290
+ };
38117
38291
  }
38118
- console.log(JSON.stringify(report, null, 2));
38119
- if (!ok && !opts.apply) process.exit(1);
38120
- } catch (error) {
38121
- logger2.error(formatError(error.message));
38292
+ }
38293
+ if (targets.spell) {
38294
+ const t = performance3.now();
38295
+ try {
38296
+ await ctx.spellChecker.rebuild();
38297
+ result.spell = { ok: true, durationMs: performance3.now() - t };
38298
+ } catch (e) {
38299
+ result.spell = {
38300
+ ok: false,
38301
+ durationMs: performance3.now() - t,
38302
+ detail: e instanceof Error ? e.message : String(e)
38303
+ };
38304
+ }
38305
+ }
38306
+ const failed = Object.values(result).filter((r) => !r.ok).length;
38307
+ emitJson5({ ok: failed === 0, failed, result });
38308
+ if (failed > 0) {
38309
+ logger2.error(formatError(`${failed} index(es) failed to rebuild`));
38122
38310
  process.exit(1);
38123
38311
  }
38124
38312
  });
@@ -38146,6 +38334,8 @@ function registerCommands(program2) {
38146
38334
  registerObservationDedupCommands(program2);
38147
38335
  registerSpellCommands(program2);
38148
38336
  registerCheckCommand(program2);
38337
+ registerCacheCommands(program2);
38338
+ registerReindexCommand(program2);
38149
38339
  }
38150
38340
 
38151
38341
  // src/cli/index.ts
@@ -38153,7 +38343,7 @@ init_logger();
38153
38343
  import { readFileSync as readFileSync5 } from "fs";
38154
38344
  import { createInterface as createInterface2 } from "readline";
38155
38345
  import { fileURLToPath as fileURLToPath4 } from "url";
38156
- import { dirname as dirname10, join as join10 } from "path";
38346
+ import { dirname as dirname11, join as join10 } from "path";
38157
38347
  process.on("unhandledRejection", (reason) => {
38158
38348
  logger.error("Unhandled promise rejection:", reason);
38159
38349
  });
@@ -38163,7 +38353,7 @@ process.on("uncaughtException", (err) => {
38163
38353
  function getVersion() {
38164
38354
  try {
38165
38355
  const __filename2 = fileURLToPath4(import.meta.url);
38166
- const __dirname2 = dirname10(__filename2);
38356
+ const __dirname2 = dirname11(__filename2);
38167
38357
  const pkgPath = join10(__dirname2, "..", "..", "package.json");
38168
38358
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
38169
38359
  return pkg.version;