@ponraaj/dimple-sdk 0.0.0-alpha.7 → 0.0.0-alpha.8

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
@@ -54,18 +54,19 @@ promise with a closed `_tag` (`"Sdk.NotFound"`, `"Sdk.InputError"`, …);
54
54
 
55
55
  ## API surface
56
56
 
57
- | Method | What it does |
58
- | --------------------------------------------------------- | --------------------------------------------------------- |
59
- | `write(input, { embed })` | Store a memory (idempotent per `logicalKey`) |
60
- | `get(id)` / `forget(id)` | Fetch / remove by id or logical key |
61
- | `query(text, options)` | Hybrid search — profiles, budgets, content-type filter |
62
- | `updateEmbedding(id, vec, modelKey?)` | Attach an external embedding |
63
- | `index({ paths })` | Codebase ingestion (idempotent re-index) |
64
- | `graph.addEdge/confirm/reject/expire/candidates/coverage` | Memory relationships: supports / supersedes / contradicts |
65
- | `explain({ kind, id }, options)` | Bounded graph neighborhood |
66
- | `dream(modelKey?)` / `repair(modelKey?)` | Consolidation + maintenance jobs |
67
- | `health(modelKey?)` | Topic-tree invariant health |
68
- | `reload(input?)` | Blue-green config reload |
57
+ | Method | What it does |
58
+ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
+ | `write(input, { embed })` | Store a memory (idempotent per `logicalKey`) |
60
+ | `writeMany(units, { embed })` | Bulk ingest: batch insert + batch embed + routing in one pass (skips the per-write candidate scan — quadratic under bulk; run the dream job afterwards for graph edges) |
61
+ | `get(id)` / `forget(id)` | Fetch / remove by id or logical key |
62
+ | `query(text, options)` | Hybrid search — profiles, budgets, content-type filter |
63
+ | `updateEmbedding(id, vec, modelKey?)` | Attach an external embedding |
64
+ | `index({ paths })` | Codebase ingestion (idempotent re-index) |
65
+ | `graph.addEdge/confirm/reject/expire/candidates/coverage` | Memory relationships: supports / supersedes / contradicts |
66
+ | `explain({ kind, id }, options)` | Bounded graph neighborhood |
67
+ | `dream(modelKey?)` / `repair(modelKey?)` | Consolidation + maintenance jobs |
68
+ | `health(modelKey?)` | Topic-tree invariant health |
69
+ | `reload(input?)` | Blue-green config reload |
69
70
 
70
71
  Errors are a closed hierarchy (`InputError`, `NotFound`, `DuplicateKey`,
71
72
  `JobFailed`, `LLMNotConfigured`, `StoreFailure`) — see the docs site.
@@ -93,5 +94,5 @@ and in `docs/MCP.md` in the repository.
93
94
 
94
95
  ## Changelog
95
96
 
96
- [Release history](https://dimple.ponraaj.me/changelog/sdk) · repository
97
+ [Release history](https://dimple.ponraaj.me/changelog/sdk/) · repository
97
98
  [`CHANGELOG.md`](./CHANGELOG.md)
package/dist/dimple.d.ts CHANGED
@@ -117,6 +117,12 @@ export interface Dimple {
117
117
  * `stats` use when no model is passed. */
118
118
  readonly defaultModelId: string;
119
119
  readonly write: (input: WriteMemoryInput, options?: WriteOptions) => Promise<MemoryUnit>;
120
+ /** Bulk write: batch insert + batch embed + topic routing in ONE pass.
121
+ * Skips the per-write graph candidate scan (O(leaf size) per write —
122
+ * quadratic under bulk ingest); callers that need near-duplicate/
123
+ * conflict edges on bulk content should run the dream job afterwards.
124
+ * All-or-nothing: a duplicate logical key rejects the whole batch. */
125
+ readonly writeMany: (units: readonly WriteMemoryInput[], options?: WriteOptions) => Promise<readonly MemoryUnit[]>;
120
126
  readonly get: (id: string) => Promise<MemoryUnit>;
121
127
  readonly query: (text: string, options?: QueryOptions) => Promise<AugmentedMemory[]>;
122
128
  readonly forget: (id: string) => Promise<void>;
package/dist/index.js CHANGED
@@ -766,6 +766,7 @@ import { Effect as Effect7, Schema as Schema7 } from "effect";
766
766
  import { parse, parseTree, printParseErrorCode } from "jsonc-parser";
767
767
  var CONFIG_FILE_NAMES = ["dimple.jsonc", "dimple.json"];
768
768
  var GLOBAL_DIR = ".config/dimple";
769
+ var resolveCwd = () => process.env["DIMPLE_CWD"] ?? process.cwd();
769
770
  var defaults = {
770
771
  storeUrl: "file:./.dimple/dimple.db",
771
772
  defaultK: 60,
@@ -871,7 +872,7 @@ var loadFs = () => Effect7.tryPromise(() => import("node:fs"));
871
872
  var findProjectConfig = () => Effect7.gen(function* () {
872
873
  const fs = yield* loadFs();
873
874
  const path = yield* Effect7.tryPromise(() => import("node:path"));
874
- let dir = process.cwd();
875
+ let dir = resolveCwd();
875
876
  for (let i = 0;i < 20; i++) {
876
877
  for (const name of CONFIG_FILE_NAMES) {
877
878
  const p = path.join(dir, name);
@@ -940,6 +941,23 @@ var resolveEnvVars = (value) => {
940
941
  }
941
942
  return value;
942
943
  };
944
+ var resolveCwdUrls = (value) => {
945
+ const cwd = process.env["DIMPLE_CWD"];
946
+ if (cwd === undefined)
947
+ return value;
948
+ if (typeof value === "string" && value.startsWith("file:./")) {
949
+ return `file:${cwd}${cwd.endsWith("/") ? "" : "/"}${value.slice("file:./".length)}`;
950
+ }
951
+ if (Array.isArray(value))
952
+ return value.map(resolveCwdUrls);
953
+ if (value !== null && typeof value === "object") {
954
+ const result = {};
955
+ for (const [k, v] of Object.entries(value))
956
+ result[k] = resolveCwdUrls(v);
957
+ return result;
958
+ }
959
+ return value;
960
+ };
943
961
  var deepMerge = (base, over) => {
944
962
  const out = { ...base };
945
963
  for (const [key, value] of Object.entries(over)) {
@@ -957,7 +975,7 @@ var loadConfigFromFile = (path) => Effect7.gen(function* () {
957
975
  const parsed = yield* parseJsoncEffect(content, (cause) => new Error(`config parse ${path}: ${cause.message}`));
958
976
  const merged = deepMerge(defaults, parsed);
959
977
  const resolved = resolveEnvVars(merged);
960
- return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
978
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolveCwdUrls(resolved)).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
961
979
  });
962
980
  var loadConfig = (overrides) => Effect7.gen(function* () {
963
981
  const fileParts = yield* loadFromFiles();
@@ -970,7 +988,7 @@ var loadConfig = (overrides) => Effect7.gen(function* () {
970
988
  if (overrides)
971
989
  merged = deepMerge(merged, overrides);
972
990
  const resolved = resolveEnvVars(merged);
973
- return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolved).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
991
+ return yield* Schema7.decodeUnknownEffect(DimpleConfig, { errors: "all" })(resolveCwdUrls(resolved)).pipe(Effect7.mapError((e) => new Error(`config validation: ${e.message}`)));
974
992
  });
975
993
 
976
994
  // ../config/src/service.ts
@@ -1236,7 +1254,7 @@ var makeBackend = (url, client, db, migrationFolder) => ({
1236
1254
  ORDER BY f.rank
1237
1255
  LIMIT ?`,
1238
1256
  args: [
1239
- `"${query.replaceAll('"', '""')}"`,
1257
+ query.split(/\s+/).filter((token) => token.length > 0).map((token) => `"${token.replaceAll('"', '""')}"`).join(" "),
1240
1258
  namespace,
1241
1259
  ...topicKeys && topicKeys.length > 0 ? [...topicKeys, modelKey ?? ""] : [],
1242
1260
  ...typeof contentType === "string" ? [contentType] : contentType ?? [],
@@ -4610,6 +4628,58 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4610
4628
  embeddingModel: undefined
4611
4629
  };
4612
4630
  });
4631
+ const writeMany = Effect26.fn("Store.writeMany")(function* (inputs) {
4632
+ if (inputs.length === 0)
4633
+ return [];
4634
+ for (const input of inputs) {
4635
+ if (input.content.includes("\x00")) {
4636
+ return yield* new StoreError({
4637
+ message: "Store.writeMany: content contains a NUL byte (libSQL would silently truncate it)"
4638
+ });
4639
+ }
4640
+ }
4641
+ const now = Date.now();
4642
+ const rows = inputs.map((input) => {
4643
+ const metadataBlob = input.typedMetadata !== undefined ? mergeMetadataBlob(input.metadata, input.typedMetadata) : input.metadata ?? null;
4644
+ return {
4645
+ id: crypto.randomUUID(),
4646
+ logicalKey: input.logicalKey,
4647
+ content: input.content,
4648
+ contentType: input.contentType ?? "text/plain",
4649
+ createdAt: now,
4650
+ ttlSecs: input.ttlSecs === null ? null : input.ttlSecs ?? defaultTtlSecs,
4651
+ namespace: input.namespace ?? "default",
4652
+ metadata: metadataBlob
4653
+ };
4654
+ });
4655
+ yield* Effect26.tryPromise({
4656
+ try: () => db.transaction(async (tx) => {
4657
+ await tx.insert(memories2).values(rows);
4658
+ await tx.insert(memoryEvents2).values(rows.map((row) => ({
4659
+ memoryId: row.id,
4660
+ eventType: "created",
4661
+ createdAt: now
4662
+ })));
4663
+ }),
4664
+ catch: (cause) => backend.isUniqueViolation(cause) ? new DuplicateActiveKey({
4665
+ logicalKey: inputs[0].logicalKey,
4666
+ namespace: inputs[0].namespace ?? "default"
4667
+ }) : new StoreError({ message: `Store.writeMany: ${cause}` })
4668
+ });
4669
+ return rows.map((row) => ({
4670
+ id: row.id,
4671
+ logicalKey: row.logicalKey,
4672
+ content: row.content,
4673
+ contentType: row.contentType,
4674
+ state: "active",
4675
+ createdAt: row.createdAt,
4676
+ ttlSecs: row.ttlSecs,
4677
+ namespace: row.namespace,
4678
+ metadata: row.metadata ?? undefined,
4679
+ embedding: undefined,
4680
+ embeddingModel: undefined
4681
+ }));
4682
+ });
4613
4683
  const get = Effect26.fn("Store.get")(function* (id) {
4614
4684
  const rows = yield* Effect26.tryPromise({
4615
4685
  try: () => db.select(memoryFields).from(memories2).where(eq8(memories2.id, id)).limit(1),
@@ -4972,7 +5042,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
4972
5042
  }
4973
5043
  return yield* topicHealth(modelKey, topicsConfig).pipe(Effect26.provideService(Service2, backend));
4974
5044
  });
4975
- const updateEmbedding = Effect26.fn("Store.updateEmbedding")(function* (id, embedding, modelName) {
5045
+ const updateEmbedding = Effect26.fn("Store.updateEmbedding")(function* (id, embedding, modelName, options) {
4976
5046
  const now = Date.now();
4977
5047
  const affected = yield* Effect26.tryPromise({
4978
5048
  try: () => db.transaction(async (tx) => {
@@ -5028,7 +5098,9 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5028
5098
  yield* backend.insertVector(id, embedding, modelConfig);
5029
5099
  if (topicsEnabled) {
5030
5100
  const routed = yield* routeInsert(id, embedding, modelConfig, topicsConfig).pipe(Effect26.provideService(Service2, backend), Effect26.mapError((cause) => new StoreError({ message: `Store.updateEmbedding topic route: ${cause}` })));
5031
- yield* scanLeafCandidates(id, embedding, routed.topicKey, modelConfig);
5101
+ if (options?.scanCandidates !== false) {
5102
+ yield* scanLeafCandidates(id, embedding, routed.topicKey, modelConfig);
5103
+ }
5032
5104
  if (routed.overflow) {
5033
5105
  const enqueueSplit = topicsOptions?.enqueueSplit;
5034
5106
  if (enqueueSplit) {
@@ -5588,6 +5660,7 @@ var makeStore2 = (backend, modelConfig, topicsOptions, graphConfig, auditOptions
5588
5660
  });
5589
5661
  return {
5590
5662
  write,
5663
+ writeMany,
5591
5664
  writeSuperseding,
5592
5665
  reconcile,
5593
5666
  reconcileRenameCandidates,
@@ -6553,10 +6626,16 @@ import { existsSync as existsSync2, readFileSync } from "node:fs";
6553
6626
  import { join as join2 } from "node:path";
6554
6627
  import { pathToFileURL } from "node:url";
6555
6628
 
6556
- // ../embedder/src/auto-install.ts
6557
- import { execFile } from "node:child_process";
6629
+ // ../installer/src/index.ts
6558
6630
  import { existsSync } from "node:fs";
6559
6631
  import { join } from "node:path";
6632
+
6633
+ class InstallerError extends Error {
6634
+ constructor(message) {
6635
+ super(message);
6636
+ this.name = "InstallerError";
6637
+ }
6638
+ }
6560
6639
  var detectPackageManager = (cwd) => {
6561
6640
  let dir = cwd;
6562
6641
  while (true) {
@@ -6571,38 +6650,74 @@ var detectPackageManager = (cwd) => {
6571
6650
  return { pm: "npm", root: dir };
6572
6651
  const parent = join(dir, "..");
6573
6652
  if (parent === dir)
6574
- return { pm: "bun", root: cwd };
6653
+ return { pm: "npm", root: cwd };
6575
6654
  dir = parent;
6576
6655
  }
6577
6656
  };
6578
- var installCommand = (pm) => {
6579
- switch (pm) {
6580
- case "bun":
6581
- return ["bun", "add", "@huggingface/transformers"];
6582
- case "npm":
6583
- return [
6584
- "npm",
6585
- "install",
6586
- "@huggingface/transformers",
6587
- "--ignore-scripts",
6588
- "--no-audit",
6589
- "--no-fund"
6590
- ];
6591
- case "pnpm":
6592
- return ["pnpm", "add", "@huggingface/transformers", "--ignore-scripts"];
6593
- case "yarn":
6594
- return ["yarn", "add", "@huggingface/transformers", "--ignore-scripts"];
6595
- }
6657
+ var resolveExecutable = (pm, platform = process.platform) => platform === "win32" && pm !== "bun" ? `${pm}.cmd` : pm;
6658
+ var installArgs = (pm, packages, root) => {
6659
+ const argv = [pm === "npm" ? "install" : "add", ...packages];
6660
+ if (pm !== "bun")
6661
+ argv.push("--ignore-scripts");
6662
+ if (pm === "npm")
6663
+ argv.push("--no-audit", "--no-fund");
6664
+ if (pm === "pnpm" && existsSync(join(root, "pnpm-workspace.yaml")))
6665
+ argv.push("-w");
6666
+ return argv;
6596
6667
  };
6597
- var installTransformers = (cwd) => new Promise((resolve, reject) => {
6668
+ var installOnce = async (target, packages, opts) => {
6669
+ const { spawn } = await import("node:child_process");
6670
+ return new Promise((resolve, reject) => {
6671
+ const timeoutMs = opts.timeoutMs ?? 300000;
6672
+ const cmd = resolveExecutable(target.pm, opts.platform);
6673
+ const args = installArgs(target.pm, packages, target.root);
6674
+ const manual = `${cmd} ${args.join(" ")}`;
6675
+ const child = spawn(cmd, args, { cwd: target.root, stdio: "ignore" });
6676
+ let settled = false;
6677
+ const timer = setTimeout(() => {
6678
+ child.kill("SIGKILL");
6679
+ if (settled)
6680
+ return;
6681
+ settled = true;
6682
+ reject(new InstallerError(`auto-install timed out after ${timeoutMs} ms — the child was killed; run it manually: ${manual}`));
6683
+ }, timeoutMs);
6684
+ child.on("error", (cause) => {
6685
+ clearTimeout(timer);
6686
+ if (settled)
6687
+ return;
6688
+ settled = true;
6689
+ reject(new InstallerError(`auto-install failed to start (${manual}): ${cause.message} — run it manually`));
6690
+ });
6691
+ child.on("exit", (code, signal) => {
6692
+ clearTimeout(timer);
6693
+ if (settled)
6694
+ return;
6695
+ settled = true;
6696
+ if (code === 0) {
6697
+ resolve();
6698
+ return;
6699
+ }
6700
+ reject(new InstallerError(`auto-install failed (${manual} exited ${signal !== null ? `with ${signal}` : `code ${code ?? "?"}`}) — run it manually`));
6701
+ });
6702
+ });
6703
+ };
6704
+ var inflight = new Map;
6705
+ var installOptionalPeers = (packages, opts) => {
6706
+ const cwd = opts?.cwd ?? process.cwd();
6598
6707
  const target = detectPackageManager(cwd);
6599
- const [cmd, ...args] = installCommand(target.pm);
6600
- execFile(cmd, args, { cwd: target.root, encoding: "utf8" }, (error) => {
6601
- if (error) {
6602
- reject(new Error(`auto-install of @huggingface/transformers failed (${target.pm} add …): ${error.message} — ` + "run it manually or set `autoInstall: false` and configure a hosted embedding provider"));
6603
- return;
6604
- }
6605
- resolve();
6708
+ const key = `${target.root}\x00${packages.join(",")}`;
6709
+ const existing = inflight.get(key);
6710
+ if (existing !== undefined)
6711
+ return existing;
6712
+ const run = installOnce(target, packages, opts ?? {});
6713
+ inflight.set(key, run);
6714
+ run.then(() => inflight.delete(key), () => inflight.delete(key));
6715
+ return run;
6716
+ };
6717
+ // ../embedder/src/auto-install.ts
6718
+ var installTransformers = (cwd) => installOptionalPeers(["@huggingface/transformers"], { cwd, timeoutMs: 300000 }).catch((cause) => {
6719
+ throw new EmbeddingError({
6720
+ message: cause instanceof InstallerError ? `${cause.message} — or set \`autoInstall: false\` and configure a hosted embedding provider` : `auto-install of @huggingface/transformers failed: ${String(cause)} — run it manually ` + "or set `autoInstall: false` and configure a hosted embedding provider"
6606
6721
  });
6607
6722
  });
6608
6723
 
@@ -6667,9 +6782,12 @@ var make5 = (options) => {
6667
6782
  }
6668
6783
  return Effect32.gen(function* () {
6669
6784
  yield* Effect32.logInfo("auto-installing @huggingface/transformers (first local-embed use — scripts skipped)").pipe(Effect32.annotateLogs({ scope: "@dimple/embedder", event: "embedder.auto-install" }));
6670
- yield* Effect32.tryPromise(() => installTransformers(process.cwd())).pipe(Effect32.timeout("5 minutes"), Effect32.catch((cause) => Effect32.fail(new EmbeddingError({
6671
- message: cause._tag === "TimeoutError" ? "auto-install of @huggingface/transformers timed out (5 minutes) — run " + "`bun add @huggingface/transformers` manually" : cause.cause instanceof Error ? cause.cause.message : cause instanceof Error ? cause.message : `auto-install failed: ${String(cause)}`
6672
- }))));
6785
+ yield* Effect32.tryPromise(() => installTransformers(process.cwd())).pipe(Effect32.catch((cause) => {
6786
+ const inner = cause.cause;
6787
+ return Effect32.fail(inner instanceof EmbeddingError ? inner : new EmbeddingError({
6788
+ message: `auto-install failed: ${inner instanceof Error ? inner.message : String(cause)}`
6789
+ }));
6790
+ }));
6673
6791
  return yield* Effect32.tryPromise(importFromDisk).pipe(Effect32.mapError(() => new EmbeddingError({
6674
6792
  message: "@huggingface/transformers still unavailable after auto-install — run `bun add " + "@huggingface/transformers` manually (or set `autoInstall: false` and configure a hosted embedding provider)"
6675
6793
  })));
@@ -8417,8 +8535,8 @@ var walkFiles = (paths2, extensions) => {
8417
8535
  const files = [];
8418
8536
  const errors3 = [];
8419
8537
  let unsupported = 0;
8420
- const resolved = paths2.map((p) => resolve(p));
8421
- const root = commonRoot([...resolved, process.cwd()]);
8538
+ const resolved = paths2.map((p) => resolve(process.env["DIMPLE_CWD"] ?? process.cwd(), p));
8539
+ const root = commonRoot([...resolved, process.env["DIMPLE_CWD"] ?? process.cwd()]);
8422
8540
  const visit = (path2) => {
8423
8541
  let stat;
8424
8542
  try {
@@ -8811,6 +8929,30 @@ var makeFacade = (state, reload, dispose) => {
8811
8929
  return yield* invalid;
8812
8930
  return yield* writeUnit(input, options?.embed !== false);
8813
8931
  })),
8932
+ writeMany: (units, options) => run(Effect49.gen(function* () {
8933
+ if (!Array.isArray(units)) {
8934
+ return yield* new InputError({ message: "writeMany: units must be an array" });
8935
+ }
8936
+ for (const input of units) {
8937
+ const invalid = validateWriteInput(input);
8938
+ if (invalid !== undefined)
8939
+ return yield* invalid;
8940
+ }
8941
+ const mems = yield* core().store.writeMany(units);
8942
+ if (options?.embed !== false) {
8943
+ yield* core().store.ensureTopicTree(core().defaultModel.id);
8944
+ const vectors = yield* core().defaultEmbedder.embed(mems.map((m) => m.content));
8945
+ for (let i = 0;i < mems.length; i++) {
8946
+ yield* core().store.updateEmbedding(mems[i].id, [...vectors[i]], core().defaultModel.id, {
8947
+ scanCandidates: false
8948
+ });
8949
+ }
8950
+ return yield* Effect49.forEach(mems, (mem) => core().store.get(mem.id), {
8951
+ discard: false
8952
+ });
8953
+ }
8954
+ return mems;
8955
+ })),
8814
8956
  get: (id) => run(resolveMemory(id)),
8815
8957
  query: runQuery,
8816
8958
  forget: (id) => run(resolveMemory(id).pipe(Effect49.flatMap((memory) => core().store.forget(memory.id)))),
@@ -9111,7 +9253,7 @@ var makeDimple = async (options) => {
9111
9253
  let disposed = false;
9112
9254
  let gate = Promise.resolve();
9113
9255
  let releaseGate;
9114
- let inflight = 0;
9256
+ let inflight2 = 0;
9115
9257
  let drainWaiters = [];
9116
9258
  let reloadChain = Promise.resolve();
9117
9259
  const run = (effect) => {
@@ -9123,12 +9265,12 @@ var makeDimple = async (options) => {
9123
9265
  return Promise.reject(new StoreFailure({ message: "SDK disposed" }));
9124
9266
  }
9125
9267
  const stack = current;
9126
- inflight += 1;
9268
+ inflight2 += 1;
9127
9269
  return stack.runPromise(effect.pipe(Effect50.mapError(remapError))).catch((cause) => {
9128
9270
  return Promise.reject(remapError(cause));
9129
9271
  }).finally(() => {
9130
- inflight -= 1;
9131
- if (inflight === 0 && drainWaiters.length > 0) {
9272
+ inflight2 -= 1;
9273
+ if (inflight2 === 0 && drainWaiters.length > 0) {
9132
9274
  const waiters = drainWaiters;
9133
9275
  drainWaiters = [];
9134
9276
  for (const wake of waiters)
@@ -9149,7 +9291,7 @@ var makeDimple = async (options) => {
9149
9291
  release?.();
9150
9292
  };
9151
9293
  const drain = () => {
9152
- if (inflight === 0)
9294
+ if (inflight2 === 0)
9153
9295
  return Promise.resolve();
9154
9296
  return new Promise((resolve2) => {
9155
9297
  drainWaiters.push(resolve2);
@@ -1,35 +1,10 @@
1
- /**
2
- * First-use auto-install of the OPTIONAL `@huggingface/transformers` peer.
3
- *
4
- * `@huggingface/transformers` is an optional peer (its tree pulls the
5
- * `sharp` + `onnxruntime-node` postinstall scripts that abort npm 11
6
- * installs). When the config declares a `transformers` provider model,
7
- * the intent is explicit — so the FIRST local-embed use installs the
8
- * package automatically with the project's own package manager, instead
9
- * of failing with a manual step.
10
- *
11
- * Safety notes:
12
- * - The install is scoped to the detected project root (the manifest's
13
- * directory) and uses the manager the project already uses.
14
- * - EVERY install runs `--ignore-scripts` (npm/pnpm/yarn) or bun's
15
- * default script-blocking — verified harmless: the onnxruntime-node
16
- * binaries ship in the package; its postinstall is not required for
17
- * embedding, and sharp is never loaded for text embeddings.
18
- * - Failure (offline, restricted env) is a typed EmbeddingError with the
19
- * manual command — never a crash.
20
- */
21
- export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
22
- export interface InstallTarget {
23
- readonly pm: PackageManager;
24
- readonly root: string;
25
- }
26
- /** Detect the project's package manager by walking UP from `cwd` for a
27
- * lockfile (bun.lock/bun.lockb → bun, package-lock.json → npm,
28
- * pnpm-lock.yaml → pnpm, yarn.lock → yarn). Falls back to bun + cwd. */
29
- export declare const detectPackageManager: (cwd: string) => InstallTarget;
30
- /** The install argv for a manager. `--ignore-scripts` on npm/pnpm/yarn:
31
- * the blocked postinstalls are proven unnecessary for embedding (bun
32
- * blocks them by default already). */
1
+ import { type PackageManager } from "./internal/installer/index.js";
2
+ export type { InstallTarget, PackageManager } from "./internal/installer/index.js";
3
+ export { detectPackageManager } from "./internal/installer/index.js";
4
+ /** The install argv for a manager with the transformers package (kept for
5
+ * callers/tests that assert the shape). */
33
6
  export declare const installCommand: (pm: PackageManager) => readonly [string, ...string[]];
34
- /** Run the install for the project at `cwd`; rejects on non-zero exit. */
7
+ /** Install transformers at the project root detected from `cwd`. The
8
+ * shared installer kills the child on timeout and dedups concurrent
9
+ * first-use installs; failures reject with the manual command. */
35
10
  export declare const installTransformers: (cwd: string) => Promise<void>;
@@ -0,0 +1,26 @@
1
+ export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
2
+ export interface InstallTarget {
3
+ readonly pm: PackageManager;
4
+ readonly root: string;
5
+ }
6
+ export declare class InstallerError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /** Detect the project's package manager by walking UP from `cwd` for a
10
+ * lockfile. NO lockfile → npm (the most universal — a bun fallback broke
11
+ * Node-only machines with ENOENT). */
12
+ export declare const detectPackageManager: (cwd: string) => InstallTarget;
13
+ /** The executable name for a manager. win32 ships `.cmd` shims for
14
+ * npm/pnpm/yarn — the bare names fail with ENOENT. */
15
+ export declare const resolveExecutable: (pm: PackageManager, platform?: NodeJS.Platform) => string;
16
+ /** The base install argv for a manager. Scripts are skipped everywhere;
17
+ * pnpm at a workspace root additionally gets `-w`. */
18
+ export declare const installArgs: (pm: PackageManager, packages: readonly string[], root: string) => string[];
19
+ export interface InstallOptions {
20
+ readonly cwd?: string;
21
+ /** Hard deadline; the child is SIGKILLed when it trips (default 5 min). */
22
+ readonly timeoutMs?: number;
23
+ /** Injectable for tests (defaults to process.platform). */
24
+ readonly platform?: NodeJS.Platform;
25
+ }
26
+ export declare const installOptionalPeers: (packages: readonly string[], opts?: InstallOptions) => Promise<void>;
@@ -65,6 +65,11 @@ export interface ExplainResult {
65
65
  }
66
66
  export interface Interface {
67
67
  readonly write: (input: WriteMemoryInput) => Effect.Effect<MemoryUnit, StoreError | DuplicateActiveKey>;
68
+ /** Bulk write: batch insert in ONE transaction (all-or-nothing; a
69
+ * duplicate logical key fails the whole batch with DuplicateActiveKey).
70
+ * The per-write graph candidate scan is NOT part of this — pair with
71
+ * `updateEmbedding(..., { scanCandidates: false })` for bulk ingest. */
72
+ readonly writeMany: (inputs: readonly WriteMemoryInput[]) => Effect.Effect<readonly MemoryUnit[], StoreError | DuplicateActiveKey>;
68
73
  /** Upsert for the index path — the identity rule (same logical key ⇒
69
74
  * confirmed `memory_supersedes`) made reachable for ACTIVE keys: demote
70
75
  * the active holder of the key (if any), write the new unit, and link
@@ -139,7 +144,9 @@ export interface Interface {
139
144
  readonly forget: (id: string) => Effect.Effect<void, NotFound | StoreError>;
140
145
  readonly sweep: (namespace: string) => Effect.Effect<SweepResult, StoreError>;
141
146
  readonly events: (memoryId: string) => Effect.Effect<readonly MemoryEvent[], StoreError>;
142
- readonly updateEmbedding: (id: string, embedding: ReadonlyArray<number>, model: string) => Effect.Effect<void, NotFound | StoreError>;
147
+ readonly updateEmbedding: (id: string, embedding: ReadonlyArray<number>, model: string, options?: {
148
+ readonly scanCandidates?: boolean;
149
+ }) => Effect.Effect<void, NotFound | StoreError>;
143
150
  readonly findStale: (model: string) => Effect.Effect<readonly string[], StoreError>;
144
151
  readonly createConcept: (name: string, embedding: ReadonlyArray<number>) => Effect.Effect<Concept, StoreError>;
145
152
  readonly getConcept: (id: string) => Effect.Effect<Concept, NotFound | StoreError>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponraaj/dimple-sdk",
3
- "version": "0.0.0-alpha.7",
3
+ "version": "0.0.0-alpha.8",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist"