@delali/narsil 0.1.8 → 0.1.10
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 +777 -130
- package/dist/{chunk-GQEHCZG7.mjs → chunk-CU6BVZEU.mjs} +643 -447
- package/dist/{chunk-FS7XLIUS.mjs → chunk-Q4OVNESD.mjs} +52 -4
- package/dist/{config-cQLlYR3U.d.ts → config-DX--2Owg.d.ts} +3 -1
- package/dist/{direct-executor-FX56DYD3.mjs → direct-executor-6FOKJKLD.mjs} +1 -1
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution.d.ts +4 -4
- package/dist/distribution.mjs +2 -2
- package/dist/embeddings/openai.mjs +5 -0
- package/dist/index.browser.mjs +476 -244
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{narsil-ChTdkwPu.d.ts → narsil-B3jKyXCr.d.ts} +6 -2
- package/dist/persistence/durability/checkpoint-worker.d.ts +7 -0
- package/dist/persistence/durability/checkpoint-worker.mjs +2 -2
- package/dist/{schema-BVaw7nqz.d.ts → schema-C3VhNvfw.d.ts} +3 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.mjs +11 -12
- package/dist/{types-Bt7c4hDF.d.ts → types-CTVVm4GG.d.ts} +1 -1
- package/dist/workers/entry.mjs +1 -1
- package/package.json +11 -2
package/dist/index.browser.mjs
CHANGED
|
@@ -1049,6 +1049,38 @@ function extractVectorFieldsFromSchema(schema) {
|
|
|
1049
1049
|
}
|
|
1050
1050
|
return result;
|
|
1051
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
// src/schema/validator/vector-promotion.ts
|
|
1054
|
+
function fail(message, details) {
|
|
1055
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, message, details);
|
|
1056
|
+
}
|
|
1057
|
+
function isPositiveInteger(value) {
|
|
1058
|
+
return Number.isInteger(value) && value >= 1;
|
|
1059
|
+
}
|
|
1060
|
+
function validateVectorPromotion(config) {
|
|
1061
|
+
if (config === void 0) return;
|
|
1062
|
+
if (config.threshold !== void 0 && !isPositiveInteger(config.threshold)) {
|
|
1063
|
+
fail("vectorPromotion.threshold must be a positive integer", { threshold: config.threshold });
|
|
1064
|
+
}
|
|
1065
|
+
const quantization = config.quantization;
|
|
1066
|
+
if (quantization !== void 0 && quantization !== "sq8" && quantization !== "none") {
|
|
1067
|
+
fail("vectorPromotion.quantization must be 'sq8' or 'none'", { quantization });
|
|
1068
|
+
}
|
|
1069
|
+
const hnsw = config.hnswConfig;
|
|
1070
|
+
if (hnsw === void 0) return;
|
|
1071
|
+
if (hnsw.m !== void 0 && !isPositiveInteger(hnsw.m)) {
|
|
1072
|
+
fail("vectorPromotion.hnswConfig.m must be a positive integer", { m: hnsw.m });
|
|
1073
|
+
}
|
|
1074
|
+
if (hnsw.efConstruction !== void 0 && !isPositiveInteger(hnsw.efConstruction)) {
|
|
1075
|
+
fail("vectorPromotion.hnswConfig.efConstruction must be a positive integer", {
|
|
1076
|
+
efConstruction: hnsw.efConstruction
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
const metric = hnsw.metric;
|
|
1080
|
+
if (metric !== void 0 && metric !== "cosine" && metric !== "dotProduct" && metric !== "euclidean") {
|
|
1081
|
+
fail("vectorPromotion.hnswConfig.metric must be 'cosine', 'dotProduct', or 'euclidean'", { metric });
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1052
1084
|
function encodeRawPayloadV2(wire) {
|
|
1053
1085
|
return encode(wire);
|
|
1054
1086
|
}
|
|
@@ -3992,6 +4024,123 @@ function createPluginRegistry() {
|
|
|
3992
4024
|
};
|
|
3993
4025
|
}
|
|
3994
4026
|
|
|
4027
|
+
// src/schema/embedding-validator.ts
|
|
4028
|
+
var VECTOR_DIMENSION_PATTERN = /^vector\[(\d+)]$/;
|
|
4029
|
+
function validateEmbeddingConfig(embeddingConfig, schema, instanceAdapter) {
|
|
4030
|
+
const fieldEntries = Object.keys(embeddingConfig.fields);
|
|
4031
|
+
if (fieldEntries.length === 0) {
|
|
4032
|
+
throw new NarsilError(
|
|
4033
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4034
|
+
"Embedding config must define at least one field mapping"
|
|
4035
|
+
);
|
|
4036
|
+
}
|
|
4037
|
+
if (typeof embeddingConfig.adapter === "string") {
|
|
4038
|
+
throw new NarsilError(
|
|
4039
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4040
|
+
`Embedding adapter "${embeddingConfig.adapter}" must be resolved against the engine registry before validation`,
|
|
4041
|
+
{ adapter: embeddingConfig.adapter }
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4044
|
+
const resolvedAdapter = embeddingConfig.adapter ?? instanceAdapter;
|
|
4045
|
+
if (!resolvedAdapter) {
|
|
4046
|
+
throw new NarsilError(
|
|
4047
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4048
|
+
"Embedding config requires an adapter (set on the index or the instance)"
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
if (typeof resolvedAdapter.dimensions !== "number" || !Number.isInteger(resolvedAdapter.dimensions) || resolvedAdapter.dimensions <= 0) {
|
|
4052
|
+
throw new NarsilError(
|
|
4053
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4054
|
+
`Adapter dimensions must be a positive integer, got ${resolvedAdapter.dimensions}`
|
|
4055
|
+
);
|
|
4056
|
+
}
|
|
4057
|
+
const flatFields = flattenSchema(schema);
|
|
4058
|
+
for (const [targetField, sourceFields] of Object.entries(embeddingConfig.fields)) {
|
|
4059
|
+
const targetType = flatFields[targetField];
|
|
4060
|
+
if (targetType === void 0) {
|
|
4061
|
+
throw new NarsilError(
|
|
4062
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4063
|
+
`Embedding target field "${targetField}" does not exist in the schema`,
|
|
4064
|
+
{ field: targetField }
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
const vectorMatch = VECTOR_DIMENSION_PATTERN.exec(targetType);
|
|
4068
|
+
if (!vectorMatch) {
|
|
4069
|
+
throw new NarsilError(
|
|
4070
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4071
|
+
`Embedding target field "${targetField}" must be a vector type, got "${targetType}"`,
|
|
4072
|
+
{ field: targetField, type: targetType }
|
|
4073
|
+
);
|
|
4074
|
+
}
|
|
4075
|
+
const schemaDimension = Number.parseInt(vectorMatch[1], 10);
|
|
4076
|
+
if (schemaDimension !== resolvedAdapter.dimensions) {
|
|
4077
|
+
throw new NarsilError(
|
|
4078
|
+
ErrorCodes.EMBEDDING_DIMENSION_MISMATCH,
|
|
4079
|
+
`Embedding target field "${targetField}" has dimension ${schemaDimension} but the adapter produces ${resolvedAdapter.dimensions}-dimensional vectors`,
|
|
4080
|
+
{ field: targetField, expected: schemaDimension, actual: resolvedAdapter.dimensions }
|
|
4081
|
+
);
|
|
4082
|
+
}
|
|
4083
|
+
const sources = Array.isArray(sourceFields) ? sourceFields : [sourceFields];
|
|
4084
|
+
if (sources.length === 0) {
|
|
4085
|
+
throw new NarsilError(
|
|
4086
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4087
|
+
`Embedding target field "${targetField}" must have at least one source field`,
|
|
4088
|
+
{ field: targetField }
|
|
4089
|
+
);
|
|
4090
|
+
}
|
|
4091
|
+
for (const source of sources) {
|
|
4092
|
+
const sourceType = flatFields[source];
|
|
4093
|
+
if (sourceType === void 0) {
|
|
4094
|
+
throw new NarsilError(
|
|
4095
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4096
|
+
`Embedding source field "${source}" does not exist in the schema`,
|
|
4097
|
+
{ field: source, targetField }
|
|
4098
|
+
);
|
|
4099
|
+
}
|
|
4100
|
+
if (sourceType !== "string") {
|
|
4101
|
+
throw new NarsilError(
|
|
4102
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4103
|
+
`Embedding source field "${source}" must be a string type, got "${sourceType}"`,
|
|
4104
|
+
{ field: source, type: sourceType, targetField }
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
return resolvedAdapter;
|
|
4110
|
+
}
|
|
4111
|
+
function validateRegisteredAdapter(name, adapter) {
|
|
4112
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
4113
|
+
throw new NarsilError(ErrorCodes.EMBEDDING_CONFIG_INVALID, "Embedding adapter names must be non-empty strings");
|
|
4114
|
+
}
|
|
4115
|
+
if (typeof adapter?.embed !== "function") {
|
|
4116
|
+
throw new NarsilError(
|
|
4117
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4118
|
+
`Embedding adapter "${name}" must provide an embed function`,
|
|
4119
|
+
{ adapter: name }
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
if (typeof adapter.dimensions !== "number" || !Number.isInteger(adapter.dimensions) || adapter.dimensions <= 0) {
|
|
4123
|
+
throw new NarsilError(
|
|
4124
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4125
|
+
`Embedding adapter "${name}" dimensions must be a positive integer, got ${adapter.dimensions}`,
|
|
4126
|
+
{ adapter: name, dimensions: adapter.dimensions }
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
function validateRequiredFieldsInSchema(required, schema) {
|
|
4131
|
+
if (required.length === 0) return;
|
|
4132
|
+
const flatFields = flattenSchema(schema);
|
|
4133
|
+
for (const field of required) {
|
|
4134
|
+
if (flatFields[field] === void 0) {
|
|
4135
|
+
throw new NarsilError(
|
|
4136
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
4137
|
+
`Required field "${field}" does not exist in the schema`,
|
|
4138
|
+
{ field }
|
|
4139
|
+
);
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
|
|
3995
4144
|
// src/search/facets.ts
|
|
3996
4145
|
function mergeFacets(partitionFacets) {
|
|
3997
4146
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -6027,6 +6176,14 @@ function createHNSWIndex(dimension, store, config, quantizer) {
|
|
|
6027
6176
|
};
|
|
6028
6177
|
}
|
|
6029
6178
|
|
|
6179
|
+
// src/workers/node-worker.browser.ts
|
|
6180
|
+
async function spawnNodeWorker(_entryPoint) {
|
|
6181
|
+
return null;
|
|
6182
|
+
}
|
|
6183
|
+
async function isNodeMainThread() {
|
|
6184
|
+
return true;
|
|
6185
|
+
}
|
|
6186
|
+
|
|
6030
6187
|
// src/runtime/detect.ts
|
|
6031
6188
|
function detectRuntimeName() {
|
|
6032
6189
|
if ("Bun" in globalThis) {
|
|
@@ -6076,9 +6233,7 @@ async function spawnWorker() {
|
|
|
6076
6233
|
const entryPoint = resolveWorkerEntryPoint();
|
|
6077
6234
|
if (runtime.supportsWorkerThreads) {
|
|
6078
6235
|
try {
|
|
6079
|
-
|
|
6080
|
-
const instance = new workerThreads.Worker(new URL(entryPoint));
|
|
6081
|
-
return instance;
|
|
6236
|
+
return await spawnNodeWorker(new URL(entryPoint));
|
|
6082
6237
|
} catch {
|
|
6083
6238
|
return null;
|
|
6084
6239
|
}
|
|
@@ -6966,21 +7121,21 @@ function createExecutionPromoter(config) {
|
|
|
6966
7121
|
return { check, markPromoted, isPromoted };
|
|
6967
7122
|
}
|
|
6968
7123
|
|
|
6969
|
-
// src/persistence/durability/
|
|
6970
|
-
|
|
6971
|
-
|
|
7124
|
+
// src/persistence/durability/node-fs.browser.ts
|
|
7125
|
+
function filesystemUnavailable() {
|
|
7126
|
+
throw new NarsilError(
|
|
7127
|
+
ErrorCodes.CONFIG_INVALID,
|
|
7128
|
+
"Filesystem durability requires a Node.js runtime; use the memory or IndexedDB persistence adapters in the browser"
|
|
7129
|
+
);
|
|
7130
|
+
}
|
|
6972
7131
|
async function getFs() {
|
|
6973
|
-
|
|
6974
|
-
cachedFs = await import('fs/promises');
|
|
6975
|
-
}
|
|
6976
|
-
return cachedFs;
|
|
7132
|
+
return filesystemUnavailable();
|
|
6977
7133
|
}
|
|
6978
7134
|
async function getPath() {
|
|
6979
|
-
|
|
6980
|
-
cachedPath = await import('path');
|
|
6981
|
-
}
|
|
6982
|
-
return cachedPath;
|
|
7135
|
+
return filesystemUnavailable();
|
|
6983
7136
|
}
|
|
7137
|
+
|
|
7138
|
+
// src/persistence/durability/durable-filesystem.ts
|
|
6984
7139
|
function validateRelativeKey(key) {
|
|
6985
7140
|
if (key.length === 0) {
|
|
6986
7141
|
throw new NarsilError(ErrorCodes.PERSISTENCE_SAVE_FAILED, "Invalid key: empty");
|
|
@@ -7293,12 +7448,7 @@ var mainThreadResolved = false;
|
|
|
7293
7448
|
var onMainThread = true;
|
|
7294
7449
|
async function isOnMainThread() {
|
|
7295
7450
|
if (!mainThreadResolved) {
|
|
7296
|
-
|
|
7297
|
-
const workerThreads = await import('worker_threads');
|
|
7298
|
-
onMainThread = workerThreads.isMainThread;
|
|
7299
|
-
} catch {
|
|
7300
|
-
onMainThread = true;
|
|
7301
|
-
}
|
|
7451
|
+
onMainThread = await isNodeMainThread();
|
|
7302
7452
|
mainThreadResolved = true;
|
|
7303
7453
|
}
|
|
7304
7454
|
return onMainThread;
|
|
@@ -7313,8 +7463,7 @@ function resolveWorkerEntryPoint2() {
|
|
|
7313
7463
|
}
|
|
7314
7464
|
async function spawnWorker2() {
|
|
7315
7465
|
try {
|
|
7316
|
-
|
|
7317
|
-
return new workerThreads.Worker(new URL(resolveWorkerEntryPoint2()));
|
|
7466
|
+
return await spawnNodeWorker(new URL(resolveWorkerEntryPoint2()));
|
|
7318
7467
|
} catch {
|
|
7319
7468
|
return null;
|
|
7320
7469
|
}
|
|
@@ -7580,6 +7729,9 @@ function metadataToWire(meta) {
|
|
|
7580
7729
|
if (meta.vectorFields) {
|
|
7581
7730
|
wire.vector_fields = meta.vectorFields;
|
|
7582
7731
|
}
|
|
7732
|
+
if (meta.embedding) {
|
|
7733
|
+
wire.embedding = meta.embedding.adapter !== void 0 ? { adapter: meta.embedding.adapter, fields: meta.embedding.fields } : { fields: meta.embedding.fields };
|
|
7734
|
+
}
|
|
7583
7735
|
return wire;
|
|
7584
7736
|
}
|
|
7585
7737
|
function wireToMetadata(raw) {
|
|
@@ -7595,6 +7747,9 @@ function wireToMetadata(raw) {
|
|
|
7595
7747
|
if (raw.vector_fields) {
|
|
7596
7748
|
meta.vectorFields = raw.vector_fields;
|
|
7597
7749
|
}
|
|
7750
|
+
if (raw.embedding && typeof raw.embedding === "object" && typeof raw.embedding.fields === "object") {
|
|
7751
|
+
meta.embedding = typeof raw.embedding.adapter === "string" ? { adapter: raw.embedding.adapter, fields: raw.embedding.fields } : { fields: raw.embedding.fields };
|
|
7752
|
+
}
|
|
7598
7753
|
return meta;
|
|
7599
7754
|
}
|
|
7600
7755
|
function serializeMetadata(meta) {
|
|
@@ -7987,8 +8142,10 @@ function resolveWorkerEntryPoint3() {
|
|
|
7987
8142
|
}
|
|
7988
8143
|
async function spawnWorker3() {
|
|
7989
8144
|
try {
|
|
7990
|
-
const
|
|
7991
|
-
|
|
8145
|
+
const worker = await spawnNodeWorker(new URL(resolveWorkerEntryPoint3()));
|
|
8146
|
+
if (worker === null) {
|
|
8147
|
+
return null;
|
|
8148
|
+
}
|
|
7992
8149
|
if (typeof worker.unref === "function") {
|
|
7993
8150
|
worker.unref();
|
|
7994
8151
|
}
|
|
@@ -8947,11 +9104,15 @@ function unflattenSchema(flat) {
|
|
|
8947
9104
|
return root;
|
|
8948
9105
|
}
|
|
8949
9106
|
function reconstructSchemaFromMetadata(metadata) {
|
|
8950
|
-
|
|
9107
|
+
const config = {
|
|
8951
9108
|
schema: unflattenSchema(metadata.schema),
|
|
8952
9109
|
language: metadata.language,
|
|
8953
9110
|
bm25: { k1: metadata.bm25Params.k1, b: metadata.bm25Params.b }
|
|
8954
9111
|
};
|
|
9112
|
+
if (metadata.embedding) {
|
|
9113
|
+
config.embedding = metadata.embedding.adapter !== void 0 ? { adapter: metadata.embedding.adapter, fields: metadata.embedding.fields } : { fields: metadata.embedding.fields };
|
|
9114
|
+
}
|
|
9115
|
+
return config;
|
|
8955
9116
|
}
|
|
8956
9117
|
|
|
8957
9118
|
// src/persistence/durability/segment/build-segment.ts
|
|
@@ -10399,7 +10560,7 @@ function buildMetadata(indexName, hooks) {
|
|
|
10399
10560
|
if (config === void 0 || manager === void 0) {
|
|
10400
10561
|
return void 0;
|
|
10401
10562
|
}
|
|
10402
|
-
|
|
10563
|
+
const metadata = {
|
|
10403
10564
|
indexName,
|
|
10404
10565
|
schema: config.schema,
|
|
10405
10566
|
language: config.language,
|
|
@@ -10408,6 +10569,10 @@ function buildMetadata(indexName, hooks) {
|
|
|
10408
10569
|
createdAt: Date.now(),
|
|
10409
10570
|
engineVersion: VERSION
|
|
10410
10571
|
};
|
|
10572
|
+
if (config.embedding) {
|
|
10573
|
+
metadata.embedding = config.embedding;
|
|
10574
|
+
}
|
|
10575
|
+
return metadata;
|
|
10411
10576
|
}
|
|
10412
10577
|
function createDurabilityIntegration(tier, hooks) {
|
|
10413
10578
|
const managerHooks = {
|
|
@@ -10928,11 +11093,16 @@ function createDurabilityFromConfig(config, wiring) {
|
|
|
10928
11093
|
if (entry === void 0) {
|
|
10929
11094
|
return void 0;
|
|
10930
11095
|
}
|
|
11096
|
+
const embedding = entry.config.embedding ? {
|
|
11097
|
+
fields: entry.config.embedding.fields,
|
|
11098
|
+
...entry.embeddingAdapterName !== null ? { adapter: entry.embeddingAdapterName } : {}
|
|
11099
|
+
} : void 0;
|
|
10931
11100
|
return {
|
|
10932
11101
|
schema: flattenSchema(entry.config.schema),
|
|
10933
11102
|
language: entry.language.name,
|
|
10934
11103
|
k1: entry.config.bm25?.k1 ?? 1.2,
|
|
10935
|
-
b: entry.config.bm25?.b ?? 0.75
|
|
11104
|
+
b: entry.config.bm25?.b ?? 0.75,
|
|
11105
|
+
...embedding !== void 0 ? { embedding } : {}
|
|
10936
11106
|
};
|
|
10937
11107
|
},
|
|
10938
11108
|
createIndexFromMetadata: wiring.createIndexFromMetadata,
|
|
@@ -10951,6 +11121,13 @@ function createEngineCore(config) {
|
|
|
10951
11121
|
}
|
|
10952
11122
|
const idGenerator = config?.idGenerator ?? generateId;
|
|
10953
11123
|
const indexRegistry = /* @__PURE__ */ new Map();
|
|
11124
|
+
const embeddingAdapters = /* @__PURE__ */ new Map();
|
|
11125
|
+
if (config?.embeddingAdapters) {
|
|
11126
|
+
for (const [name, adapter] of Object.entries(config.embeddingAdapters)) {
|
|
11127
|
+
validateRegisteredAdapter(name, adapter);
|
|
11128
|
+
embeddingAdapters.set(name, adapter);
|
|
11129
|
+
}
|
|
11130
|
+
}
|
|
10954
11131
|
const eventHandlers = /* @__PURE__ */ new Map();
|
|
10955
11132
|
const shutdownState = { isShutdown: false };
|
|
10956
11133
|
const abortController = new AbortController();
|
|
@@ -11000,11 +11177,35 @@ function createEngineCore(config) {
|
|
|
11000
11177
|
}
|
|
11001
11178
|
const indexConfig = reconstructSchemaFromMetadata(metadata);
|
|
11002
11179
|
const language = getLanguage(indexConfig.language ?? "english");
|
|
11180
|
+
const adapterName = metadata.embedding?.adapter ?? null;
|
|
11181
|
+
let embeddingAdapter = null;
|
|
11182
|
+
if (metadata.embedding) {
|
|
11183
|
+
const candidate = adapterName !== null ? embeddingAdapters.get(adapterName) ?? null : config?.embedding ?? null;
|
|
11184
|
+
if (candidate) {
|
|
11185
|
+
try {
|
|
11186
|
+
validateEmbeddingConfig(
|
|
11187
|
+
{ fields: metadata.embedding.fields, adapter: candidate },
|
|
11188
|
+
indexConfig.schema,
|
|
11189
|
+
void 0
|
|
11190
|
+
);
|
|
11191
|
+
} catch (err) {
|
|
11192
|
+
if (err instanceof NarsilError) {
|
|
11193
|
+
throw new NarsilError(err.code, `Recovery of index "${metadata.indexName}" failed: ${err.message}`, {
|
|
11194
|
+
indexName: metadata.indexName,
|
|
11195
|
+
adapter: adapterName ?? void 0
|
|
11196
|
+
});
|
|
11197
|
+
}
|
|
11198
|
+
throw err;
|
|
11199
|
+
}
|
|
11200
|
+
embeddingAdapter = candidate;
|
|
11201
|
+
}
|
|
11202
|
+
}
|
|
11003
11203
|
executor.createIndex(metadata.indexName, indexConfig, language);
|
|
11004
11204
|
indexRegistry.set(metadata.indexName, {
|
|
11005
11205
|
config: indexConfig,
|
|
11006
11206
|
language,
|
|
11007
|
-
embeddingAdapter
|
|
11207
|
+
embeddingAdapter,
|
|
11208
|
+
embeddingAdapterName: adapterName,
|
|
11008
11209
|
vectorFieldPaths: getVectorFieldPaths(indexConfig.schema)
|
|
11009
11210
|
});
|
|
11010
11211
|
}
|
|
@@ -11047,6 +11248,7 @@ function createEngineCore(config) {
|
|
|
11047
11248
|
durability,
|
|
11048
11249
|
idGenerator,
|
|
11049
11250
|
indexRegistry,
|
|
11251
|
+
embeddingAdapters,
|
|
11050
11252
|
eventHandlers,
|
|
11051
11253
|
shutdownState,
|
|
11052
11254
|
abortController,
|
|
@@ -11064,6 +11266,160 @@ function createEngineCore(config) {
|
|
|
11064
11266
|
rebalanceCtx
|
|
11065
11267
|
};
|
|
11066
11268
|
}
|
|
11269
|
+
var MAX_INDEX_NAME_LENGTH = 255;
|
|
11270
|
+
|
|
11271
|
+
// src/engine/validation.ts
|
|
11272
|
+
var INDEX_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
11273
|
+
var MAX_DOC_ID_LENGTH = 512;
|
|
11274
|
+
var BATCH_CHUNK_SIZE = 1e3;
|
|
11275
|
+
var MAX_LIMIT = 1e4;
|
|
11276
|
+
var MAX_OFFSET = 1e5;
|
|
11277
|
+
var DEFAULT_LIMIT = 10;
|
|
11278
|
+
var DEFAULT_OFFSET = 0;
|
|
11279
|
+
function now() {
|
|
11280
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
11281
|
+
return performance.now();
|
|
11282
|
+
}
|
|
11283
|
+
return Date.now();
|
|
11284
|
+
}
|
|
11285
|
+
function validateIndexName(name) {
|
|
11286
|
+
if (!name || name.length === 0) {
|
|
11287
|
+
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, "Index name must not be empty", { indexName: name });
|
|
11288
|
+
}
|
|
11289
|
+
if (name.length > MAX_INDEX_NAME_LENGTH) {
|
|
11290
|
+
throw new NarsilError(
|
|
11291
|
+
ErrorCodes.INDEX_NOT_FOUND,
|
|
11292
|
+
`Index name must not exceed ${MAX_INDEX_NAME_LENGTH} characters`,
|
|
11293
|
+
{
|
|
11294
|
+
indexName: name,
|
|
11295
|
+
length: name.length
|
|
11296
|
+
}
|
|
11297
|
+
);
|
|
11298
|
+
}
|
|
11299
|
+
if (!INDEX_NAME_PATTERN.test(name)) {
|
|
11300
|
+
throw new NarsilError(
|
|
11301
|
+
ErrorCodes.INDEX_NOT_FOUND,
|
|
11302
|
+
`Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
|
|
11303
|
+
{ indexName: name }
|
|
11304
|
+
);
|
|
11305
|
+
}
|
|
11306
|
+
if (name.includes("..")) {
|
|
11307
|
+
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index name "${name}" must not contain ".."`, { indexName: name });
|
|
11308
|
+
}
|
|
11309
|
+
}
|
|
11310
|
+
function validateDocId(docId) {
|
|
11311
|
+
if (!docId || docId.length === 0) {
|
|
11312
|
+
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not be empty", { docId });
|
|
11313
|
+
}
|
|
11314
|
+
if (docId.length > MAX_DOC_ID_LENGTH) {
|
|
11315
|
+
throw new NarsilError(
|
|
11316
|
+
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
11317
|
+
`Document ID must not exceed ${MAX_DOC_ID_LENGTH} characters`,
|
|
11318
|
+
{
|
|
11319
|
+
docId,
|
|
11320
|
+
length: docId.length
|
|
11321
|
+
}
|
|
11322
|
+
);
|
|
11323
|
+
}
|
|
11324
|
+
if (docId.includes("\0")) {
|
|
11325
|
+
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not contain null bytes", { docId });
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11328
|
+
function clampLimit(limit) {
|
|
11329
|
+
if (limit === void 0) return DEFAULT_LIMIT;
|
|
11330
|
+
return Math.max(0, Math.min(limit, MAX_LIMIT));
|
|
11331
|
+
}
|
|
11332
|
+
function clampOffset(offset) {
|
|
11333
|
+
if (offset === void 0) return DEFAULT_OFFSET;
|
|
11334
|
+
return Math.max(0, Math.min(offset, MAX_OFFSET));
|
|
11335
|
+
}
|
|
11336
|
+
|
|
11337
|
+
// src/engine/index-lifecycle.ts
|
|
11338
|
+
async function createEngineIndex(core, config, name, indexConfig) {
|
|
11339
|
+
core.guardShutdown();
|
|
11340
|
+
validateIndexName(name);
|
|
11341
|
+
if (core.indexRegistry.has(name)) {
|
|
11342
|
+
throw new NarsilError(ErrorCodes.INDEX_ALREADY_EXISTS, `Index "${name}" already exists`, { indexName: name });
|
|
11343
|
+
}
|
|
11344
|
+
validateSchema(indexConfig.schema);
|
|
11345
|
+
validateVectorPromotion(indexConfig.vectorPromotion);
|
|
11346
|
+
let resolvedEmbeddingAdapter = null;
|
|
11347
|
+
let embeddingAdapterName = null;
|
|
11348
|
+
if (indexConfig.embedding) {
|
|
11349
|
+
let configuredAdapter = indexConfig.embedding.adapter;
|
|
11350
|
+
if (typeof configuredAdapter === "string") {
|
|
11351
|
+
embeddingAdapterName = configuredAdapter;
|
|
11352
|
+
const registered = core.embeddingAdapters.get(configuredAdapter);
|
|
11353
|
+
if (!registered) {
|
|
11354
|
+
throw new NarsilError(
|
|
11355
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
11356
|
+
`Embedding adapter "${configuredAdapter}" is not registered on this engine`,
|
|
11357
|
+
{ adapter: configuredAdapter, available: [...core.embeddingAdapters.keys()] }
|
|
11358
|
+
);
|
|
11359
|
+
}
|
|
11360
|
+
configuredAdapter = registered;
|
|
11361
|
+
}
|
|
11362
|
+
resolvedEmbeddingAdapter = validateEmbeddingConfig(
|
|
11363
|
+
{ fields: indexConfig.embedding.fields, adapter: configuredAdapter },
|
|
11364
|
+
indexConfig.schema,
|
|
11365
|
+
config?.embedding
|
|
11366
|
+
);
|
|
11367
|
+
}
|
|
11368
|
+
if (indexConfig.required && indexConfig.required.length > 0) {
|
|
11369
|
+
validateRequiredFieldsInSchema(indexConfig.required, indexConfig.schema);
|
|
11370
|
+
}
|
|
11371
|
+
const language = getLanguage(indexConfig.language ?? "english");
|
|
11372
|
+
core.executor.createIndex(name, indexConfig, language);
|
|
11373
|
+
core.indexRegistry.set(name, {
|
|
11374
|
+
config: indexConfig,
|
|
11375
|
+
language,
|
|
11376
|
+
embeddingAdapter: resolvedEmbeddingAdapter,
|
|
11377
|
+
embeddingAdapterName,
|
|
11378
|
+
vectorFieldPaths: getVectorFieldPaths(indexConfig.schema)
|
|
11379
|
+
});
|
|
11380
|
+
if (core.durability) {
|
|
11381
|
+
await core.durability.manager.persistMetadata(name);
|
|
11382
|
+
}
|
|
11383
|
+
await core.pluginRegistry.runHook("onIndexCreate", { indexName: name, config: indexConfig });
|
|
11384
|
+
}
|
|
11385
|
+
async function dropEngineIndex(core, name) {
|
|
11386
|
+
core.guardShutdown();
|
|
11387
|
+
const entry = core.requireIndex(name);
|
|
11388
|
+
core.executor.dropIndex(name);
|
|
11389
|
+
core.indexRegistry.delete(name);
|
|
11390
|
+
if (core.durability) {
|
|
11391
|
+
await core.durability.manager.removeIndex(name);
|
|
11392
|
+
}
|
|
11393
|
+
await core.pluginRegistry.runHook("onIndexDrop", { indexName: name, config: entry.config });
|
|
11394
|
+
}
|
|
11395
|
+
function registerEngineEmbeddingAdapter(core, name, adapter) {
|
|
11396
|
+
core.guardShutdown();
|
|
11397
|
+
validateRegisteredAdapter(name, adapter);
|
|
11398
|
+
const affected = [];
|
|
11399
|
+
for (const [indexName, entry] of core.indexRegistry) {
|
|
11400
|
+
if (entry.embeddingAdapterName !== name || !entry.config.embedding) continue;
|
|
11401
|
+
try {
|
|
11402
|
+
validateEmbeddingConfig({ fields: entry.config.embedding.fields, adapter }, entry.config.schema, void 0);
|
|
11403
|
+
} catch (err) {
|
|
11404
|
+
if (err instanceof NarsilError) {
|
|
11405
|
+
throw new NarsilError(
|
|
11406
|
+
err.code,
|
|
11407
|
+
`Cannot bind embedding adapter "${name}" to index "${indexName}": ${err.message}`,
|
|
11408
|
+
{
|
|
11409
|
+
indexName,
|
|
11410
|
+
adapter: name
|
|
11411
|
+
}
|
|
11412
|
+
);
|
|
11413
|
+
}
|
|
11414
|
+
throw err;
|
|
11415
|
+
}
|
|
11416
|
+
affected.push(entry);
|
|
11417
|
+
}
|
|
11418
|
+
core.embeddingAdapters.set(name, adapter);
|
|
11419
|
+
for (const entry of affected) {
|
|
11420
|
+
entry.embeddingAdapter = adapter;
|
|
11421
|
+
}
|
|
11422
|
+
}
|
|
11067
11423
|
|
|
11068
11424
|
// src/engine/lifecycle.ts
|
|
11069
11425
|
async function shutdownEngine(core) {
|
|
@@ -11098,6 +11454,18 @@ async function shutdownEngine(core) {
|
|
|
11098
11454
|
}
|
|
11099
11455
|
|
|
11100
11456
|
// src/engine/embed.ts
|
|
11457
|
+
function assertDocumentCarriesMappedVectors(document, embeddingConfig, adapterName) {
|
|
11458
|
+
for (const targetField of Object.keys(embeddingConfig.fields)) {
|
|
11459
|
+
const existing = resolveFieldValue(document, targetField);
|
|
11460
|
+
if (existing === void 0 || existing === null) {
|
|
11461
|
+
throw new NarsilError(
|
|
11462
|
+
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
11463
|
+
adapterName ? `The index embeds "${targetField}" via adapter "${adapterName}", which is not registered on this engine. Register the adapter or supply the vector on the document.` : `The index embeds "${targetField}" automatically but no embedding adapter is bound. Bind an adapter or supply the vector on the document.`,
|
|
11464
|
+
adapterName ? { field: targetField, adapter: adapterName } : { field: targetField }
|
|
11465
|
+
);
|
|
11466
|
+
}
|
|
11467
|
+
}
|
|
11468
|
+
}
|
|
11101
11469
|
function validateVector2(vector, targetField, expectedDimensions) {
|
|
11102
11470
|
if (!(vector instanceof Float32Array)) {
|
|
11103
11471
|
throw new NarsilError(
|
|
@@ -11299,73 +11667,6 @@ async function embedBatchDocumentFields(documents, embeddingConfig, adapter, sig
|
|
|
11299
11667
|
}
|
|
11300
11668
|
return { embedded, failed };
|
|
11301
11669
|
}
|
|
11302
|
-
var MAX_INDEX_NAME_LENGTH = 255;
|
|
11303
|
-
|
|
11304
|
-
// src/engine/validation.ts
|
|
11305
|
-
var INDEX_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
11306
|
-
var MAX_DOC_ID_LENGTH = 512;
|
|
11307
|
-
var BATCH_CHUNK_SIZE = 1e3;
|
|
11308
|
-
var MAX_LIMIT = 1e4;
|
|
11309
|
-
var MAX_OFFSET = 1e5;
|
|
11310
|
-
var DEFAULT_LIMIT = 10;
|
|
11311
|
-
var DEFAULT_OFFSET = 0;
|
|
11312
|
-
function now() {
|
|
11313
|
-
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
11314
|
-
return performance.now();
|
|
11315
|
-
}
|
|
11316
|
-
return Date.now();
|
|
11317
|
-
}
|
|
11318
|
-
function validateIndexName(name) {
|
|
11319
|
-
if (!name || name.length === 0) {
|
|
11320
|
-
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, "Index name must not be empty", { indexName: name });
|
|
11321
|
-
}
|
|
11322
|
-
if (name.length > MAX_INDEX_NAME_LENGTH) {
|
|
11323
|
-
throw new NarsilError(
|
|
11324
|
-
ErrorCodes.INDEX_NOT_FOUND,
|
|
11325
|
-
`Index name must not exceed ${MAX_INDEX_NAME_LENGTH} characters`,
|
|
11326
|
-
{
|
|
11327
|
-
indexName: name,
|
|
11328
|
-
length: name.length
|
|
11329
|
-
}
|
|
11330
|
-
);
|
|
11331
|
-
}
|
|
11332
|
-
if (!INDEX_NAME_PATTERN.test(name)) {
|
|
11333
|
-
throw new NarsilError(
|
|
11334
|
-
ErrorCodes.INDEX_NOT_FOUND,
|
|
11335
|
-
`Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
|
|
11336
|
-
{ indexName: name }
|
|
11337
|
-
);
|
|
11338
|
-
}
|
|
11339
|
-
if (name.includes("..")) {
|
|
11340
|
-
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index name "${name}" must not contain ".."`, { indexName: name });
|
|
11341
|
-
}
|
|
11342
|
-
}
|
|
11343
|
-
function validateDocId(docId) {
|
|
11344
|
-
if (!docId || docId.length === 0) {
|
|
11345
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not be empty", { docId });
|
|
11346
|
-
}
|
|
11347
|
-
if (docId.length > MAX_DOC_ID_LENGTH) {
|
|
11348
|
-
throw new NarsilError(
|
|
11349
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
11350
|
-
`Document ID must not exceed ${MAX_DOC_ID_LENGTH} characters`,
|
|
11351
|
-
{
|
|
11352
|
-
docId,
|
|
11353
|
-
length: docId.length
|
|
11354
|
-
}
|
|
11355
|
-
);
|
|
11356
|
-
}
|
|
11357
|
-
if (docId.includes("\0")) {
|
|
11358
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not contain null bytes", { docId });
|
|
11359
|
-
}
|
|
11360
|
-
}
|
|
11361
|
-
function clampLimit(limit) {
|
|
11362
|
-
if (limit === void 0) return DEFAULT_LIMIT;
|
|
11363
|
-
return Math.max(0, Math.min(limit, MAX_LIMIT));
|
|
11364
|
-
}
|
|
11365
|
-
function clampOffset(offset) {
|
|
11366
|
-
if (offset === void 0) return DEFAULT_OFFSET;
|
|
11367
|
-
return Math.max(0, Math.min(offset, MAX_OFFSET));
|
|
11368
|
-
}
|
|
11369
11670
|
|
|
11370
11671
|
// src/engine/mutations/durable-rollback.ts
|
|
11371
11672
|
function rollbackFailed(docId, originalError, rollbackError) {
|
|
@@ -11428,13 +11729,21 @@ async function insertDocument(ctx, indexName, document, docId, options) {
|
|
|
11428
11729
|
if (entry.config.required && entry.config.required.length > 0) {
|
|
11429
11730
|
validateRequiredFields(document, entry.config.required);
|
|
11430
11731
|
}
|
|
11431
|
-
if (entry.
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11732
|
+
if (entry.config.embedding) {
|
|
11733
|
+
if (entry.embeddingAdapter) {
|
|
11734
|
+
await embedDocumentFields(
|
|
11735
|
+
document,
|
|
11736
|
+
entry.config.embedding,
|
|
11737
|
+
entry.embeddingAdapter,
|
|
11738
|
+
ctx.abortController.signal
|
|
11739
|
+
);
|
|
11740
|
+
} else {
|
|
11741
|
+
assertDocumentCarriesMappedVectors(
|
|
11742
|
+
document,
|
|
11743
|
+
entry.config.embedding,
|
|
11744
|
+
entry.embeddingAdapterName
|
|
11745
|
+
);
|
|
11746
|
+
}
|
|
11438
11747
|
}
|
|
11439
11748
|
const insertManager = ctx.requireManager(indexName);
|
|
11440
11749
|
const insertVecIndexes = insertManager.getVectorIndexes();
|
|
@@ -11514,6 +11823,7 @@ async function insertDocumentBatch(ctx, indexName, documents, options) {
|
|
|
11514
11823
|
const hasAfterHook = ctx.pluginRegistry.hasHooks("afterInsert");
|
|
11515
11824
|
const hasRequired = entry.config.required && entry.config.required.length > 0;
|
|
11516
11825
|
const hasEmbedding = entry.embeddingAdapter && entry.config.embedding;
|
|
11826
|
+
const embeddingUnbound = !entry.embeddingAdapter && entry.config.embedding;
|
|
11517
11827
|
const batchManager = ctx.requireManager(indexName);
|
|
11518
11828
|
const batchVecIndexes = batchManager.getVectorIndexes();
|
|
11519
11829
|
const batchVectorFieldPaths = batchVecIndexes.size > 0 ? entry.vectorFieldPaths : /* @__PURE__ */ new Set();
|
|
@@ -11529,7 +11839,7 @@ async function insertDocumentBatch(ctx, indexName, documents, options) {
|
|
|
11529
11839
|
} catch (err) {
|
|
11530
11840
|
chunkFailedIndexes.add(i);
|
|
11531
11841
|
failed.push({
|
|
11532
|
-
docId: "",
|
|
11842
|
+
docId: providedDocId(documents[i]) ?? "",
|
|
11533
11843
|
error: err instanceof NarsilError ? err : new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, String(err))
|
|
11534
11844
|
});
|
|
11535
11845
|
}
|
|
@@ -11554,16 +11864,33 @@ async function insertDocumentBatch(ctx, indexName, documents, options) {
|
|
|
11554
11864
|
for (const [sliceIndex, error] of embedResult.failed) {
|
|
11555
11865
|
const originalIdx = embeddableOriginalIndexes[sliceIndex];
|
|
11556
11866
|
chunkFailedIndexes.add(originalIdx);
|
|
11557
|
-
failed.push({ docId: "", error });
|
|
11867
|
+
failed.push({ docId: providedDocId(documents[originalIdx]) ?? "", error });
|
|
11558
11868
|
}
|
|
11559
11869
|
} catch (err) {
|
|
11560
11870
|
const embeddingError = err instanceof NarsilError ? err : new NarsilError(ErrorCodes.EMBEDDING_FAILED, String(err));
|
|
11561
11871
|
for (const originalIdx of embeddableOriginalIndexes) {
|
|
11562
11872
|
chunkFailedIndexes.add(originalIdx);
|
|
11563
|
-
failed.push({ docId: "", error: embeddingError });
|
|
11873
|
+
failed.push({ docId: providedDocId(documents[originalIdx]) ?? "", error: embeddingError });
|
|
11564
11874
|
}
|
|
11565
11875
|
}
|
|
11566
11876
|
}
|
|
11877
|
+
} else if (embeddingUnbound) {
|
|
11878
|
+
for (let i = chunkStart; i < chunkEnd; i++) {
|
|
11879
|
+
if (chunkFailedIndexes.has(i)) continue;
|
|
11880
|
+
try {
|
|
11881
|
+
assertDocumentCarriesMappedVectors(
|
|
11882
|
+
documents[i],
|
|
11883
|
+
entry.config.embedding,
|
|
11884
|
+
entry.embeddingAdapterName
|
|
11885
|
+
);
|
|
11886
|
+
} catch (err) {
|
|
11887
|
+
chunkFailedIndexes.add(i);
|
|
11888
|
+
failed.push({
|
|
11889
|
+
docId: providedDocId(documents[i]) ?? "",
|
|
11890
|
+
error: err instanceof NarsilError ? err : new NarsilError(ErrorCodes.EMBEDDING_FAILED, String(err))
|
|
11891
|
+
});
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11567
11894
|
}
|
|
11568
11895
|
for (let i = chunkStart; i < chunkEnd; i++) {
|
|
11569
11896
|
if (ctx.abortController.signal.aborted) break;
|
|
@@ -11757,13 +12084,21 @@ async function updateDocument(ctx, indexName, docId, document) {
|
|
|
11757
12084
|
if (ctx.bufferIfRebalancing(indexName, { action: "update", docId, document, indexName })) {
|
|
11758
12085
|
return;
|
|
11759
12086
|
}
|
|
11760
|
-
if (entry.
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
12087
|
+
if (entry.config.embedding) {
|
|
12088
|
+
if (entry.embeddingAdapter) {
|
|
12089
|
+
await embedDocumentFields(
|
|
12090
|
+
document,
|
|
12091
|
+
entry.config.embedding,
|
|
12092
|
+
entry.embeddingAdapter,
|
|
12093
|
+
ctx.abortController.signal
|
|
12094
|
+
);
|
|
12095
|
+
} else {
|
|
12096
|
+
assertDocumentCarriesMappedVectors(
|
|
12097
|
+
document,
|
|
12098
|
+
entry.config.embedding,
|
|
12099
|
+
entry.embeddingAdapterName
|
|
12100
|
+
);
|
|
12101
|
+
}
|
|
11767
12102
|
}
|
|
11768
12103
|
const updateManager = ctx.requireManager(indexName);
|
|
11769
12104
|
const oldDocument = updateManager.get(docId);
|
|
@@ -12813,7 +13148,7 @@ async function executeRebalance(manager, indexName, targetPartitionCount, ctx) {
|
|
|
12813
13148
|
}
|
|
12814
13149
|
|
|
12815
13150
|
// src/engine/resolve-vector-text.ts
|
|
12816
|
-
async function resolveVectorText(params, embeddingAdapter, signal) {
|
|
13151
|
+
async function resolveVectorText(params, embeddingAdapter, signal, embeddingAdapterName) {
|
|
12817
13152
|
if (!params.vector) return params;
|
|
12818
13153
|
if (params.vector.text === void 0 && params.vector.value === void 0) return params;
|
|
12819
13154
|
if (params.vector.value !== void 0 && params.vector.text === void 0) return params;
|
|
@@ -12823,7 +13158,8 @@ async function resolveVectorText(params, embeddingAdapter, signal) {
|
|
|
12823
13158
|
if (!embeddingAdapter) {
|
|
12824
13159
|
throw new NarsilError(
|
|
12825
13160
|
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
12826
|
-
"Vector query with 'text' requires an embedding adapter on the index or instance"
|
|
13161
|
+
embeddingAdapterName ? `Vector query with 'text' needs embedding adapter "${embeddingAdapterName}", which is not registered on this engine. Register it with registerEmbeddingAdapter or supply a raw vector.` : "Vector query with 'text' requires an embedding adapter on the index or instance",
|
|
13162
|
+
embeddingAdapterName ? { adapter: embeddingAdapterName } : void 0
|
|
12827
13163
|
);
|
|
12828
13164
|
}
|
|
12829
13165
|
if (typeof params.vector.text !== "string") {
|
|
@@ -12905,7 +13241,13 @@ async function restoreFromSnapshot(indexName, data, executor, indexRegistry, get
|
|
|
12905
13241
|
const indexConfig = { schema, language: envelope.language };
|
|
12906
13242
|
executor.createIndex(indexName, indexConfig, language);
|
|
12907
13243
|
const vectorFieldPaths = getVectorFieldPaths2(schema);
|
|
12908
|
-
indexRegistry.set(indexName, {
|
|
13244
|
+
indexRegistry.set(indexName, {
|
|
13245
|
+
config: indexConfig,
|
|
13246
|
+
language,
|
|
13247
|
+
embeddingAdapter: null,
|
|
13248
|
+
embeddingAdapterName: null,
|
|
13249
|
+
vectorFieldPaths
|
|
13250
|
+
});
|
|
12909
13251
|
try {
|
|
12910
13252
|
const manager = requireManager(indexName);
|
|
12911
13253
|
while (manager.partitionCount < envelope.partitions.length) {
|
|
@@ -13036,97 +13378,6 @@ function readProcessMemory() {
|
|
|
13036
13378
|
}
|
|
13037
13379
|
}
|
|
13038
13380
|
|
|
13039
|
-
// src/schema/embedding-validator.ts
|
|
13040
|
-
var VECTOR_DIMENSION_PATTERN = /^vector\[(\d+)]$/;
|
|
13041
|
-
function validateEmbeddingConfig(embeddingConfig, schema, instanceAdapter) {
|
|
13042
|
-
const fieldEntries = Object.keys(embeddingConfig.fields);
|
|
13043
|
-
if (fieldEntries.length === 0) {
|
|
13044
|
-
throw new NarsilError(
|
|
13045
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13046
|
-
"Embedding config must define at least one field mapping"
|
|
13047
|
-
);
|
|
13048
|
-
}
|
|
13049
|
-
const resolvedAdapter = embeddingConfig.adapter ?? instanceAdapter;
|
|
13050
|
-
if (!resolvedAdapter) {
|
|
13051
|
-
throw new NarsilError(
|
|
13052
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13053
|
-
"Embedding config requires an adapter (set on the index or the instance)"
|
|
13054
|
-
);
|
|
13055
|
-
}
|
|
13056
|
-
if (typeof resolvedAdapter.dimensions !== "number" || !Number.isInteger(resolvedAdapter.dimensions) || resolvedAdapter.dimensions <= 0) {
|
|
13057
|
-
throw new NarsilError(
|
|
13058
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13059
|
-
`Adapter dimensions must be a positive integer, got ${resolvedAdapter.dimensions}`
|
|
13060
|
-
);
|
|
13061
|
-
}
|
|
13062
|
-
const flatFields = flattenSchema(schema);
|
|
13063
|
-
for (const [targetField, sourceFields] of Object.entries(embeddingConfig.fields)) {
|
|
13064
|
-
const targetType = flatFields[targetField];
|
|
13065
|
-
if (targetType === void 0) {
|
|
13066
|
-
throw new NarsilError(
|
|
13067
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13068
|
-
`Embedding target field "${targetField}" does not exist in the schema`,
|
|
13069
|
-
{ field: targetField }
|
|
13070
|
-
);
|
|
13071
|
-
}
|
|
13072
|
-
const vectorMatch = VECTOR_DIMENSION_PATTERN.exec(targetType);
|
|
13073
|
-
if (!vectorMatch) {
|
|
13074
|
-
throw new NarsilError(
|
|
13075
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13076
|
-
`Embedding target field "${targetField}" must be a vector type, got "${targetType}"`,
|
|
13077
|
-
{ field: targetField, type: targetType }
|
|
13078
|
-
);
|
|
13079
|
-
}
|
|
13080
|
-
const schemaDimension = Number.parseInt(vectorMatch[1], 10);
|
|
13081
|
-
if (schemaDimension !== resolvedAdapter.dimensions) {
|
|
13082
|
-
throw new NarsilError(
|
|
13083
|
-
ErrorCodes.EMBEDDING_DIMENSION_MISMATCH,
|
|
13084
|
-
`Embedding target field "${targetField}" has dimension ${schemaDimension} but the adapter produces ${resolvedAdapter.dimensions}-dimensional vectors`,
|
|
13085
|
-
{ field: targetField, expected: schemaDimension, actual: resolvedAdapter.dimensions }
|
|
13086
|
-
);
|
|
13087
|
-
}
|
|
13088
|
-
const sources = Array.isArray(sourceFields) ? sourceFields : [sourceFields];
|
|
13089
|
-
if (sources.length === 0) {
|
|
13090
|
-
throw new NarsilError(
|
|
13091
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13092
|
-
`Embedding target field "${targetField}" must have at least one source field`,
|
|
13093
|
-
{ field: targetField }
|
|
13094
|
-
);
|
|
13095
|
-
}
|
|
13096
|
-
for (const source of sources) {
|
|
13097
|
-
const sourceType = flatFields[source];
|
|
13098
|
-
if (sourceType === void 0) {
|
|
13099
|
-
throw new NarsilError(
|
|
13100
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13101
|
-
`Embedding source field "${source}" does not exist in the schema`,
|
|
13102
|
-
{ field: source, targetField }
|
|
13103
|
-
);
|
|
13104
|
-
}
|
|
13105
|
-
if (sourceType !== "string") {
|
|
13106
|
-
throw new NarsilError(
|
|
13107
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13108
|
-
`Embedding source field "${source}" must be a string type, got "${sourceType}"`,
|
|
13109
|
-
{ field: source, type: sourceType, targetField }
|
|
13110
|
-
);
|
|
13111
|
-
}
|
|
13112
|
-
}
|
|
13113
|
-
}
|
|
13114
|
-
return resolvedAdapter;
|
|
13115
|
-
}
|
|
13116
|
-
function validateRequiredFieldsInSchema(required, schema) {
|
|
13117
|
-
if (required.length === 0) return;
|
|
13118
|
-
const flatFields = flattenSchema(schema);
|
|
13119
|
-
for (const field of required) {
|
|
13120
|
-
if (flatFields[field] === void 0) {
|
|
13121
|
-
throw new NarsilError(
|
|
13122
|
-
ErrorCodes.EMBEDDING_CONFIG_INVALID,
|
|
13123
|
-
`Required field "${field}" does not exist in the schema`,
|
|
13124
|
-
{ field }
|
|
13125
|
-
);
|
|
13126
|
-
}
|
|
13127
|
-
}
|
|
13128
|
-
}
|
|
13129
|
-
|
|
13130
13381
|
// src/narsil.ts
|
|
13131
13382
|
async function createNarsil(config) {
|
|
13132
13383
|
const core = createEngineCore(config);
|
|
@@ -13153,43 +13404,14 @@ function createNarsilFromCore(core, config) {
|
|
|
13153
13404
|
rebalanceCtx
|
|
13154
13405
|
} = core;
|
|
13155
13406
|
const narsil = {
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
validateIndexName(name);
|
|
13159
|
-
if (indexRegistry.has(name)) {
|
|
13160
|
-
throw new NarsilError(ErrorCodes.INDEX_ALREADY_EXISTS, `Index "${name}" already exists`, { indexName: name });
|
|
13161
|
-
}
|
|
13162
|
-
validateSchema(indexConfig.schema);
|
|
13163
|
-
let resolvedEmbeddingAdapter = null;
|
|
13164
|
-
if (indexConfig.embedding) {
|
|
13165
|
-
resolvedEmbeddingAdapter = validateEmbeddingConfig(indexConfig.embedding, indexConfig.schema, config?.embedding);
|
|
13166
|
-
}
|
|
13167
|
-
if (indexConfig.required && indexConfig.required.length > 0) {
|
|
13168
|
-
validateRequiredFieldsInSchema(indexConfig.required, indexConfig.schema);
|
|
13169
|
-
}
|
|
13170
|
-
const language = getLanguage(indexConfig.language ?? "english");
|
|
13171
|
-
executor.createIndex(name, indexConfig, language);
|
|
13172
|
-
const vectorFieldPaths = getVectorFieldPaths(indexConfig.schema);
|
|
13173
|
-
indexRegistry.set(name, {
|
|
13174
|
-
config: indexConfig,
|
|
13175
|
-
language,
|
|
13176
|
-
embeddingAdapter: resolvedEmbeddingAdapter,
|
|
13177
|
-
vectorFieldPaths
|
|
13178
|
-
});
|
|
13179
|
-
if (durability) {
|
|
13180
|
-
await durability.manager.persistMetadata(name);
|
|
13181
|
-
}
|
|
13182
|
-
await pluginRegistry.runHook("onIndexCreate", { indexName: name, config: indexConfig });
|
|
13407
|
+
createIndex(name, indexConfig) {
|
|
13408
|
+
return createEngineIndex(core, config, name, indexConfig);
|
|
13183
13409
|
},
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
if (durability) {
|
|
13190
|
-
await durability.manager.removeIndex(name);
|
|
13191
|
-
}
|
|
13192
|
-
await pluginRegistry.runHook("onIndexDrop", { indexName: name, config: entry.config });
|
|
13410
|
+
registerEmbeddingAdapter(name, adapter) {
|
|
13411
|
+
registerEngineEmbeddingAdapter(core, name, adapter);
|
|
13412
|
+
},
|
|
13413
|
+
dropIndex(name) {
|
|
13414
|
+
return dropEngineIndex(core, name);
|
|
13193
13415
|
},
|
|
13194
13416
|
listIndexes() {
|
|
13195
13417
|
const infos = [];
|
|
@@ -13268,7 +13490,12 @@ function createNarsilFromCore(core, config) {
|
|
|
13268
13490
|
guardShutdown();
|
|
13269
13491
|
const entry = requireIndex(indexName);
|
|
13270
13492
|
const manager = requireManager(indexName);
|
|
13271
|
-
const resolvedParams = await resolveVectorText(
|
|
13493
|
+
const resolvedParams = await resolveVectorText(
|
|
13494
|
+
params,
|
|
13495
|
+
entry.embeddingAdapter,
|
|
13496
|
+
abortController.signal,
|
|
13497
|
+
entry.embeddingAdapterName
|
|
13498
|
+
);
|
|
13272
13499
|
await pluginRegistry.runHook("beforeSearch", { indexName, params: resolvedParams });
|
|
13273
13500
|
const workerSearch = orchestrator.isPromoted() ? orchestrator.searchViaWorker.bind(orchestrator) : void 0;
|
|
13274
13501
|
const result = await executeQuery(resolvedParams, {
|
|
@@ -13293,7 +13520,12 @@ function createNarsilFromCore(core, config) {
|
|
|
13293
13520
|
guardShutdown();
|
|
13294
13521
|
const entry = requireIndex(indexName);
|
|
13295
13522
|
const manager = requireManager(indexName);
|
|
13296
|
-
const resolvedParams = await resolveVectorText(
|
|
13523
|
+
const resolvedParams = await resolveVectorText(
|
|
13524
|
+
params,
|
|
13525
|
+
entry.embeddingAdapter,
|
|
13526
|
+
abortController.signal,
|
|
13527
|
+
entry.embeddingAdapterName
|
|
13528
|
+
);
|
|
13297
13529
|
const workerSearch = orchestrator.isPromoted() ? orchestrator.searchViaWorker.bind(orchestrator) : void 0;
|
|
13298
13530
|
return executePreflight(resolvedParams, {
|
|
13299
13531
|
manager,
|