@remnic/core 9.3.651 → 9.3.652
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/dist/access-cli.js +9 -9
- package/dist/access-http.d.ts +3 -2
- package/dist/access-http.js +10 -10
- package/dist/access-mcp.d.ts +3 -2
- package/dist/access-mcp.js +9 -9
- package/dist/{access-service-DIZRHQ7Q.d.ts → access-service-CdJFd3_b.d.ts} +23 -2
- package/dist/access-service.d.ts +3 -2
- package/dist/access-service.js +8 -8
- package/dist/bootstrap.d.ts +2 -1
- package/dist/{chunk-QT4THOLT.js → chunk-2DGQLOOM.js} +1 -1
- package/dist/chunk-2DGQLOOM.js.map +1 -0
- package/dist/{chunk-SLYD3AH4.js → chunk-5V3TAB7D.js} +176 -4
- package/dist/chunk-5V3TAB7D.js.map +1 -0
- package/dist/{chunk-FOVPSMGI.js → chunk-7WEB3FLJ.js} +2 -2
- package/dist/{chunk-WJK75OCH.js → chunk-GI45G4BK.js} +2 -2
- package/dist/{chunk-76QTEJ2Q.js → chunk-JBHXMCYN.js} +2 -2
- package/dist/{chunk-4PTKFBST.js → chunk-JVRPJ7D4.js} +126 -26
- package/dist/chunk-JVRPJ7D4.js.map +1 -0
- package/dist/{chunk-TQUWNX7C.js → chunk-JX2RINDR.js} +2 -2
- package/dist/{chunk-RSS2KWN6.js → chunk-MGGNV3H2.js} +2 -2
- package/dist/{chunk-I4COC5XW.js → chunk-PYWNNF2I.js} +47 -9
- package/dist/chunk-PYWNNF2I.js.map +1 -0
- package/dist/{chunk-5WSDHTBO.js → chunk-TCX4WLKK.js} +7 -4
- package/dist/chunk-TCX4WLKK.js.map +1 -0
- package/dist/{chunk-RKN5J4RO.js → chunk-WSFNYPAT.js} +6 -6
- package/dist/{chunk-LFTLXOFX.js → chunk-WTI35CVJ.js} +2 -2
- package/dist/{chunk-6UKL6IXM.js → chunk-YM3LR4LS.js} +5 -5
- package/dist/{chunk-MF32AL7N.js → chunk-YOVKPOMD.js} +3 -3
- package/dist/{cli-BG4ybtJr.d.ts → cli-DDo7Qgs-.d.ts} +2 -2
- package/dist/cli.d.ts +4 -3
- package/dist/cli.js +13 -13
- package/dist/explicit-capture.d.ts +2 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +14 -14
- package/dist/mcp-memory-inspector-app.d.ts +3 -2
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/search.d.ts +18 -1
- package/dist/namespaces/search.js +7 -7
- package/dist/operator-toolkit.js +9 -9
- package/dist/{orchestrator-CX-oqwJq.d.ts → orchestrator-8fTZsa0y.d.ts} +2 -0
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +8 -8
- package/dist/qmd.d.ts +2 -1
- package/dist/qmd.js +2 -2
- package/dist/schemas.d.ts +22 -22
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +6 -6
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +6 -0
- package/dist/search/port.js +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +1 -1
- package/src/access-service-health.test.ts +402 -0
- package/src/access-service.ts +274 -2
- package/src/namespaces/search.test.ts +258 -3
- package/src/namespaces/search.ts +184 -30
- package/src/orchestrator.ts +11 -1
- package/src/qmd.test.ts +102 -0
- package/src/qmd.ts +54 -7
- package/src/search/port.ts +6 -0
- package/dist/chunk-4PTKFBST.js.map +0 -1
- package/dist/chunk-5WSDHTBO.js.map +0 -1
- package/dist/chunk-I4COC5XW.js.map +0 -1
- package/dist/chunk-QT4THOLT.js.map +0 -1
- package/dist/chunk-SLYD3AH4.js.map +0 -1
- /package/dist/{chunk-FOVPSMGI.js.map → chunk-7WEB3FLJ.js.map} +0 -0
- /package/dist/{chunk-WJK75OCH.js.map → chunk-GI45G4BK.js.map} +0 -0
- /package/dist/{chunk-76QTEJ2Q.js.map → chunk-JBHXMCYN.js.map} +0 -0
- /package/dist/{chunk-TQUWNX7C.js.map → chunk-JX2RINDR.js.map} +0 -0
- /package/dist/{chunk-RSS2KWN6.js.map → chunk-MGGNV3H2.js.map} +0 -0
- /package/dist/{chunk-RKN5J4RO.js.map → chunk-WSFNYPAT.js.map} +0 -0
- /package/dist/{chunk-LFTLXOFX.js.map → chunk-WTI35CVJ.js.map} +0 -0
- /package/dist/{chunk-6UKL6IXM.js.map → chunk-YM3LR4LS.js.map} +0 -0
- /package/dist/{chunk-MF32AL7N.js.map → chunk-YOVKPOMD.js.map} +0 -0
package/src/qmd.test.ts
CHANGED
|
@@ -183,6 +183,108 @@ test("QmdClient preserves configured qmdPath diagnostics when all probes fail",
|
|
|
183
183
|
}
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
test("QmdClient read-only availability failures preserve operational state", async () => {
|
|
187
|
+
const { QmdClient } = await import("./qmd.js");
|
|
188
|
+
const originalPath = process.env.PATH;
|
|
189
|
+
const originalWindowsPath = process.env.Path;
|
|
190
|
+
const missingQmdPath = path.join(
|
|
191
|
+
os.tmpdir(),
|
|
192
|
+
`remnic-missing-readonly-qmd-${process.pid}-${Date.now()}`,
|
|
193
|
+
"qmd.cmd",
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
process.env.PATH = "";
|
|
197
|
+
process.env.Path = "";
|
|
198
|
+
try {
|
|
199
|
+
const client = new QmdClient("test-collection", 10, {
|
|
200
|
+
qmdPath: missingQmdPath,
|
|
201
|
+
qmdFallbackPaths: [],
|
|
202
|
+
});
|
|
203
|
+
(client as any).available = true;
|
|
204
|
+
(client as any).qmdPath = "qmd";
|
|
205
|
+
(client as any).qmdPathSource = "auto-path";
|
|
206
|
+
(client as any).cliVersion = "qmd 2.5.3";
|
|
207
|
+
(client as any).qmdCapabilities = resolveQmdCapabilities("qmd 2.5.3");
|
|
208
|
+
(client as any).lastCliProbeError = null;
|
|
209
|
+
|
|
210
|
+
assert.equal(await client.checkAvailability(), false);
|
|
211
|
+
|
|
212
|
+
assert.equal(client.isAvailable(), true);
|
|
213
|
+
assert.equal((client as any).qmdPath, "qmd");
|
|
214
|
+
assert.equal((client as any).qmdPathSource, "auto-path");
|
|
215
|
+
assert.equal((client as any).cliVersion, "qmd 2.5.3");
|
|
216
|
+
assert.equal((client as any).lastCliProbeError, null);
|
|
217
|
+
} finally {
|
|
218
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
219
|
+
else process.env.PATH = originalPath;
|
|
220
|
+
if (originalWindowsPath === undefined) delete process.env.Path;
|
|
221
|
+
else process.env.Path = originalWindowsPath;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("QmdClient read-only availability preserves live daemon availability", async () => {
|
|
226
|
+
const { QmdClient } = await import("./qmd.js");
|
|
227
|
+
const originalPath = process.env.PATH;
|
|
228
|
+
const originalWindowsPath = process.env.Path;
|
|
229
|
+
const missingQmdPath = path.join(
|
|
230
|
+
os.tmpdir(),
|
|
231
|
+
`remnic-missing-daemon-qmd-${process.pid}-${Date.now()}`,
|
|
232
|
+
"qmd.cmd",
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
process.env.PATH = "";
|
|
236
|
+
process.env.Path = "";
|
|
237
|
+
try {
|
|
238
|
+
const client = new QmdClient("test-collection", 10, {
|
|
239
|
+
qmdPath: missingQmdPath,
|
|
240
|
+
qmdFallbackPaths: [],
|
|
241
|
+
});
|
|
242
|
+
(client as any).daemonAvailable = true;
|
|
243
|
+
(client as any).daemonSession = { isActive: () => true };
|
|
244
|
+
|
|
245
|
+
assert.equal(await client.checkAvailability(), true);
|
|
246
|
+
assert.equal(client.isAvailable(), true);
|
|
247
|
+
} finally {
|
|
248
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
249
|
+
else process.env.PATH = originalPath;
|
|
250
|
+
if (originalWindowsPath === undefined) delete process.env.Path;
|
|
251
|
+
else process.env.Path = originalWindowsPath;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("QmdClient read-only availability clears stale daemon availability", async () => {
|
|
256
|
+
const { QmdClient } = await import("./qmd.js");
|
|
257
|
+
const originalPath = process.env.PATH;
|
|
258
|
+
const originalWindowsPath = process.env.Path;
|
|
259
|
+
const missingQmdPath = path.join(
|
|
260
|
+
os.tmpdir(),
|
|
261
|
+
`remnic-missing-stale-daemon-qmd-${process.pid}-${Date.now()}`,
|
|
262
|
+
"qmd.cmd",
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
process.env.PATH = "";
|
|
266
|
+
process.env.Path = "";
|
|
267
|
+
try {
|
|
268
|
+
const client = new QmdClient("test-collection", 10, {
|
|
269
|
+
qmdPath: missingQmdPath,
|
|
270
|
+
qmdFallbackPaths: [],
|
|
271
|
+
});
|
|
272
|
+
(client as any).daemonAvailable = true;
|
|
273
|
+
(client as any).daemonSession = { isActive: () => false };
|
|
274
|
+
(client as any).lastDaemonCheckAtMs = Date.now();
|
|
275
|
+
|
|
276
|
+
assert.equal(await client.checkAvailability(), false);
|
|
277
|
+
assert.equal(client.isAvailable(), false);
|
|
278
|
+
assert.equal((client as any).daemonAvailable, false);
|
|
279
|
+
assert.equal((client as any).lastDaemonCheckAtMs, 0);
|
|
280
|
+
} finally {
|
|
281
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
282
|
+
else process.env.PATH = originalPath;
|
|
283
|
+
if (originalWindowsPath === undefined) delete process.env.Path;
|
|
284
|
+
else process.env.Path = originalWindowsPath;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
186
288
|
test("QmdClient applies chunk strategy to normal and forced embed args", async () => {
|
|
187
289
|
const { QmdClient } = await import("./qmd.js");
|
|
188
290
|
const client = new QmdClient("test", 5, {
|
package/src/qmd.ts
CHANGED
|
@@ -1362,6 +1362,22 @@ export class QmdClient implements SearchBackend {
|
|
|
1362
1362
|
return cliOk || this.daemonAvailable;
|
|
1363
1363
|
}
|
|
1364
1364
|
|
|
1365
|
+
async checkAvailability(execution?: SearchExecutionOptions): Promise<boolean> {
|
|
1366
|
+
const cliAvailable = await this.probeCli({
|
|
1367
|
+
allowAutoUpgrade: false,
|
|
1368
|
+
preserveStateOnFailure: true,
|
|
1369
|
+
signal: execution?.signal,
|
|
1370
|
+
});
|
|
1371
|
+
if (this.daemonAvailable && this.daemonSession?.isActive()) {
|
|
1372
|
+
return true;
|
|
1373
|
+
}
|
|
1374
|
+
if (this.daemonAvailable) {
|
|
1375
|
+
this.daemonAvailable = false;
|
|
1376
|
+
this.lastDaemonCheckAtMs = 0;
|
|
1377
|
+
}
|
|
1378
|
+
return cliAvailable;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1365
1381
|
private async probeDaemon(): Promise<boolean> {
|
|
1366
1382
|
this.lastDaemonCheckAtMs = Date.now();
|
|
1367
1383
|
const normalizedPath = this.qmdPath.trim() || "qmd";
|
|
@@ -1409,7 +1425,32 @@ export class QmdClient implements SearchBackend {
|
|
|
1409
1425
|
}
|
|
1410
1426
|
}
|
|
1411
1427
|
|
|
1412
|
-
private async probeCli(
|
|
1428
|
+
private async probeCli(
|
|
1429
|
+
options: {
|
|
1430
|
+
allowAutoUpgrade?: boolean;
|
|
1431
|
+
preserveStateOnFailure?: boolean;
|
|
1432
|
+
signal?: AbortSignal;
|
|
1433
|
+
} = {},
|
|
1434
|
+
): Promise<boolean> {
|
|
1435
|
+
const priorState = options.preserveStateOnFailure === true
|
|
1436
|
+
? {
|
|
1437
|
+
available: this.available,
|
|
1438
|
+
qmdPath: this.qmdPath,
|
|
1439
|
+
qmdPathSource: this.qmdPathSource,
|
|
1440
|
+
cliVersion: this.cliVersion,
|
|
1441
|
+
lastCliProbeError: this.lastCliProbeError,
|
|
1442
|
+
qmdCapabilities: this.qmdCapabilities,
|
|
1443
|
+
}
|
|
1444
|
+
: null;
|
|
1445
|
+
const restorePriorState = (): void => {
|
|
1446
|
+
if (!priorState) return;
|
|
1447
|
+
this.available = priorState.available;
|
|
1448
|
+
this.qmdPath = priorState.qmdPath;
|
|
1449
|
+
this.qmdPathSource = priorState.qmdPathSource;
|
|
1450
|
+
this.cliVersion = priorState.cliVersion;
|
|
1451
|
+
this.lastCliProbeError = priorState.lastCliProbeError;
|
|
1452
|
+
this.qmdCapabilities = priorState.qmdCapabilities;
|
|
1453
|
+
};
|
|
1413
1454
|
let configuredProbeFailure: string | null = null;
|
|
1414
1455
|
const markProbeFailure = (err: unknown): void => {
|
|
1415
1456
|
this.lastCliProbeError = err instanceof Error ? err.message : String(err);
|
|
@@ -1431,12 +1472,14 @@ export class QmdClient implements SearchBackend {
|
|
|
1431
1472
|
this.cliVersion = parseQmdVersionOutput(result.stdout, result.stderr);
|
|
1432
1473
|
this.qmdCapabilities = resolveQmdCapabilities(this.cliVersion);
|
|
1433
1474
|
this.lastCliProbeError = null;
|
|
1434
|
-
|
|
1475
|
+
if (options.allowAutoUpgrade !== false) {
|
|
1476
|
+
await this.maybeAutoUpgradeQmd();
|
|
1477
|
+
}
|
|
1435
1478
|
};
|
|
1436
1479
|
|
|
1437
1480
|
if (this.configuredQmdPath) {
|
|
1438
1481
|
try {
|
|
1439
|
-
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath,
|
|
1482
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath, options.signal, this.qmdRuntimeEnv);
|
|
1440
1483
|
await recordProbeSuccess(result, this.configuredQmdPath, "configured");
|
|
1441
1484
|
return true;
|
|
1442
1485
|
} catch (err) {
|
|
@@ -1452,7 +1495,7 @@ export class QmdClient implements SearchBackend {
|
|
|
1452
1495
|
|
|
1453
1496
|
// Try PATH first
|
|
1454
1497
|
try {
|
|
1455
|
-
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd",
|
|
1498
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd", options.signal, this.qmdRuntimeEnv);
|
|
1456
1499
|
await recordProbeSuccess(result, "qmd", "auto-path");
|
|
1457
1500
|
return true;
|
|
1458
1501
|
} catch (err) {
|
|
@@ -1460,7 +1503,7 @@ export class QmdClient implements SearchBackend {
|
|
|
1460
1503
|
// Try fallback paths
|
|
1461
1504
|
for (const fallbackPath of this.qmdFallbackPaths) {
|
|
1462
1505
|
try {
|
|
1463
|
-
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath,
|
|
1506
|
+
const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath, options.signal, this.qmdRuntimeEnv);
|
|
1464
1507
|
await recordProbeSuccess(result, fallbackPath, "auto-fallback");
|
|
1465
1508
|
log.info(`QMD: found at ${fallbackPath}`);
|
|
1466
1509
|
return true;
|
|
@@ -1469,8 +1512,12 @@ export class QmdClient implements SearchBackend {
|
|
|
1469
1512
|
// Continue to next fallback
|
|
1470
1513
|
}
|
|
1471
1514
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1515
|
+
if (priorState) {
|
|
1516
|
+
restorePriorState();
|
|
1517
|
+
} else {
|
|
1518
|
+
this.available = false;
|
|
1519
|
+
restoreConfiguredProbeFailure();
|
|
1520
|
+
}
|
|
1474
1521
|
return false;
|
|
1475
1522
|
}
|
|
1476
1523
|
}
|
package/src/search/port.ts
CHANGED
|
@@ -42,6 +42,12 @@ export function resolveEnsureCollectionArgs(
|
|
|
42
42
|
export interface SearchBackend {
|
|
43
43
|
// ── Lifecycle ──
|
|
44
44
|
probe(): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Optional non-mutating availability probe for health/readiness checks.
|
|
47
|
+
* Implementations must avoid auto-upgrades, collection creation, daemon
|
|
48
|
+
* startup, or any other runtime-modifying side effects.
|
|
49
|
+
*/
|
|
50
|
+
checkAvailability?(execution?: SearchExecutionOptions): Promise<boolean>;
|
|
45
51
|
isAvailable(): boolean;
|
|
46
52
|
debugStatus(): string;
|
|
47
53
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/namespaces/search.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { PluginConfig, QmdSearchResult } from \"../types.js\";\nimport type {\n SearchBackend,\n SearchExecutionOptions,\n SearchQueryOptions,\n} from \"../search/port.js\";\nimport { createSearchBackend } from \"../search/factory.js\";\nimport { namespaceIdentityToken, normalizeNamespaceIdentity } from \"./identity.js\";\n\nconst NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR = 4;\nconst NESTED_NAMESPACE_FILTER_OVERFETCH_MIN = 50;\n\nexport function namespaceCollectionName(\n baseCollection: string,\n namespace: string,\n options?: {\n defaultNamespace?: string;\n useLegacyDefaultCollection?: boolean;\n },\n): string {\n const trimmed = normalizeNamespaceIdentity(namespace);\n const defaultNamespace = normalizeNamespaceIdentity(options?.defaultNamespace ?? \"\") || \"default\";\n if (\n options?.useLegacyDefaultCollection === true &&\n trimmed === defaultNamespace\n ) {\n return baseCollection;\n }\n\n return `${baseCollection}--${namespaceIdentityToken(trimmed || defaultNamespace)}`;\n}\n\ntype StorageRouterLike = {\n storageFor(namespace: string): Promise<{ dir: string }>;\n};\n\ntype NamespaceBackendRecord = {\n backend: SearchBackend;\n collection: string;\n memoryDir: string;\n available: boolean;\n collectionState: CollectionState;\n filtersNestedNamespaces: boolean;\n};\n\ntype CollectionState = \"present\" | \"missing\" | \"unknown\" | \"skipped\";\n\ntype NamespaceScopedSearchConfig = PluginConfig & {\n hostEmbeddingProviderScope?: string;\n};\n\nexport class NamespaceSearchRouter {\n private readonly cache = new Map<string, Promise<NamespaceBackendRecord>>();\n\n constructor(\n private readonly config: PluginConfig,\n private readonly storageRouter: StorageRouterLike,\n private readonly createBackend: (config: PluginConfig) => SearchBackend = createSearchBackend,\n ) {}\n\n async collectionForNamespace(namespace: string): Promise<string> {\n return (await this.backendRecordFor(namespace)).collection;\n }\n\n async searchAcrossNamespaces(options: {\n query: string;\n namespaces: string[];\n maxResults?: number;\n mode?: \"search\" | \"hybrid\" | \"bm25\" | \"vector\";\n searchOptions?: SearchQueryOptions;\n execution?: SearchExecutionOptions;\n }): Promise<QmdSearchResult[]> {\n const query = options.query.trim();\n if (!query) return [];\n const maxResults = Math.max(0, Math.floor(options.maxResults ?? this.config.qmdMaxResults));\n if (maxResults === 0) return [];\n\n const method = options.mode ?? \"search\";\n const namespaces = Array.from(new Set(options.namespaces.map((value) => value.trim()).filter(Boolean)));\n if (namespaces.length === 0) return [];\n\n const resultsByNamespace = await Promise.all(\n namespaces.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n if (!record.available || record.collectionState === \"missing\") {\n return { namespace, results: [] as QmdSearchResult[] };\n }\n const backendLimit = backendSearchLimit(record, maxResults);\n let results: QmdSearchResult[];\n switch (method) {\n case \"hybrid\":\n results = await record.backend.hybridSearch(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n case \"bm25\":\n results = await record.backend.bm25Search(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n case \"vector\":\n results = await record.backend.vectorSearch(\n query,\n record.collection,\n backendLimit,\n options.execution,\n );\n break;\n default:\n results = await record.backend.search(\n query,\n record.collection,\n backendLimit,\n options.searchOptions,\n options.execution,\n );\n break;\n }\n results = filterNamespaceSubtreeResults(record, results);\n return { namespace, results };\n }),\n );\n\n return mergeNamespaceSearchResults(resultsByNamespace, maxResults);\n }\n\n /**\n * Update all namespace backends.\n * Returns the number of backends for which an update was attempted\n * (i.e., available and collection present). Callers can treat 0 as a\n * signal that no backend was eligible — useful for success-verification in\n * startup-sync when namespacesEnabled is true.\n */\n async updateNamespaces(\n namespaces: string[],\n execution?: SearchExecutionOptions,\n ): Promise<number> {\n const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));\n const eligible = (await Promise.all(\n unique.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n return record.available && record.collectionState !== \"missing\"\n ? record\n : null;\n }),\n )).filter((record): record is NamespaceBackendRecord => record !== null);\n\n const globalRecord = eligible.find((record) => record.backend.updatesAllCollections?.() === true);\n const scopedRecords = globalRecord\n ? eligible.filter((record) => record.backend.updatesAllCollections?.() !== true)\n : eligible;\n\n await Promise.all([\n globalRecord ? globalRecord.backend.update(execution) : Promise.resolve(),\n ...scopedRecords.map((record) => record.backend.update(execution)),\n ]);\n\n return (globalRecord ? 1 : 0) + scopedRecords.length;\n }\n\n async embedNamespaces(namespaces: string[]): Promise<void> {\n const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));\n await Promise.all(\n unique.map(async (namespace) => {\n const record = await this.backendRecordFor(namespace);\n if (!record.available || record.collectionState === \"missing\") return;\n await record.backend.embed();\n }),\n );\n }\n\n async ensureNamespaceCollection(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<\"present\" | \"missing\" | \"unknown\" | \"skipped\"> {\n const record = await this.backendRecordFor(namespace, execution);\n return record.collectionState;\n }\n\n /** Clear cached backend records so the next access re-probes availability. */\n clearCache(): void {\n this.cache.clear();\n }\n\n /** Release any per-namespace backend handles held by cached records. */\n async dispose(): Promise<void> {\n const pendingRecords = Array.from(this.cache.values());\n this.cache.clear();\n const settled = await Promise.allSettled(pendingRecords);\n await Promise.allSettled(\n settled.flatMap((entry) => {\n if (entry.status !== \"fulfilled\") return [];\n const dispose = (entry.value.backend as { dispose?: () => void | Promise<void> }).dispose;\n return dispose ? [dispose.call(entry.value.backend)] : [];\n }),\n );\n }\n\n private async backendRecordFor(\n namespace: string,\n execution?: SearchExecutionOptions,\n ): Promise<NamespaceBackendRecord> {\n const key = namespace.trim() || this.config.defaultNamespace;\n const existing = this.cache.get(key);\n if (existing) return await existing;\n\n const pending = (async (): Promise<NamespaceBackendRecord> => {\n const storage = await this.storageRouter.storageFor(key);\n const useLegacyDefaultCollection =\n key === this.config.defaultNamespace && storage.dir === this.config.memoryDir;\n const filtersNestedNamespaces =\n this.config.namespacesEnabled === true && useLegacyDefaultCollection;\n const rootHostEmbeddingScope =\n (this.config as NamespaceScopedSearchConfig).hostEmbeddingProviderScope ??\n this.config.memoryDir;\n const scopedConfig: NamespaceScopedSearchConfig = {\n ...this.config,\n memoryDir: storage.dir,\n hostEmbeddingProviderScope: rootHostEmbeddingScope,\n qmdCollection: namespaceCollectionName(this.config.qmdCollection, key, {\n defaultNamespace: this.config.defaultNamespace,\n useLegacyDefaultCollection,\n }),\n };\n\n const backend = this.createBackend(scopedConfig);\n const available = await backend.probe().catch(() => false);\n const collectionState = available\n ? await this.collectionStateForBackend(backend, storage.dir, scopedConfig.qmdCollection, {\n skipAutoCreate: filtersNestedNamespaces,\n execution,\n })\n : \"unknown\";\n return {\n backend,\n collection: scopedConfig.qmdCollection,\n memoryDir: storage.dir,\n available,\n collectionState,\n filtersNestedNamespaces,\n };\n })();\n\n this.cache.set(key, pending);\n return await pending;\n }\n\n private async collectionStateForBackend(\n backend: SearchBackend,\n memoryDir: string,\n collection: string,\n options: {\n skipAutoCreate: boolean;\n execution?: SearchExecutionOptions;\n },\n ): Promise<CollectionState> {\n if (options.skipAutoCreate) {\n if (!backend.checkCollection) return \"unknown\";\n const collectionState = await backend\n .checkCollection(collection, options.execution)\n .catch(() => \"unknown\" as const);\n return collectionState === \"missing\" ? \"unknown\" : collectionState;\n }\n return await backend.ensureCollection(memoryDir, collection, options.execution).catch(() => \"unknown\" as const);\n }\n}\n\nfunction filterNamespaceSubtreeResults(\n record: NamespaceBackendRecord,\n results: QmdSearchResult[],\n): QmdSearchResult[] {\n if (!record.filtersNestedNamespaces) return results;\n return results.filter((result) =>\n !pathIsInsideNamespaceSubtree(record.memoryDir, record.collection, result.path)\n );\n}\n\nfunction backendSearchLimit(\n record: NamespaceBackendRecord,\n maxResults: number,\n): number {\n if (!record.filtersNestedNamespaces) return maxResults;\n return Math.max(\n maxResults,\n maxResults * NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR,\n NESTED_NAMESPACE_FILTER_OVERFETCH_MIN,\n );\n}\n\nfunction pathIsInsideNamespaceSubtree(\n memoryDir: string,\n collection: string,\n resultPath: string | undefined,\n): boolean {\n if (!resultPath) return false;\n const normalizedResultPath = normalizeQmdResultPath(resultPath, collection);\n const memoryRoot = path.resolve(memoryDir);\n const namespacesRoot = path.join(memoryRoot, \"namespaces\");\n const candidate = path.isAbsolute(normalizedResultPath)\n ? path.normalize(normalizedResultPath)\n : path.resolve(memoryRoot, normalizedResultPath);\n const relative = path.relative(namespacesRoot, candidate);\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nfunction normalizeQmdResultPath(resultPath: string, collection: string): string {\n let value = resultPath.trim();\n if (value.startsWith(\"qmd://\")) {\n try {\n const parsed = new URL(value);\n if (parsed.protocol === \"qmd:\" && parsed.hostname === collection) {\n value = decodeURIComponent(parsed.pathname.replace(/^\\/+/, \"\"));\n }\n } catch {\n const remainder = value.slice(\"qmd://\".length);\n const slashIndex = remainder.indexOf(\"/\");\n if (slashIndex !== -1) {\n value = remainder.slice(slashIndex + 1);\n }\n }\n }\n\n const collectionPrefix = `${collection}/`;\n if (value.startsWith(collectionPrefix)) {\n value = value.slice(collectionPrefix.length);\n }\n return value;\n}\n\nfunction mergeNamespaceSearchResults(\n lists: Array<{ namespace: string; results: QmdSearchResult[] }>,\n maxResults: number,\n): QmdSearchResult[] {\n const merged = new Map<string, QmdSearchResult>();\n\n for (const { namespace, results } of lists) {\n for (const result of results) {\n const key = `${namespace}\\0${result.path || result.docid}`;\n const existing = merged.get(key);\n if (!existing) {\n merged.set(key, result);\n continue;\n }\n if (result.score > existing.score) {\n merged.set(key, {\n ...result,\n snippet: existing.snippet || result.snippet || \"\",\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n}\n"],"mappings":"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","names":[]}
|