@delali/narsil 0.3.1-next.69 → 0.3.1-next.70
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/{capabilities-B1wz2Zl0.d.ts → capabilities-Dt-7B4Wz.d.ts} +2 -2
- package/dist/{chunk-DD5X23E6.mjs → chunk-2STVUTNA.mjs} +1 -1
- package/dist/chunk-3JGWULDF.mjs +256 -0
- package/dist/{chunk-6TGZG2TV.mjs → chunk-653BNMAF.mjs} +190 -58
- package/dist/{chunk-YA52AC2V.mjs → chunk-FVLQ63B7.mjs} +203 -9
- package/dist/{chunk-5JLSTECC.mjs → chunk-GIVAGD3Q.mjs} +1 -1
- package/dist/{chunk-AV4IJKD2.mjs → chunk-HWQLTLNG.mjs} +18 -1
- package/dist/{chunk-4VU5TWCR.mjs → chunk-LMO5TJUS.mjs} +40 -35
- package/dist/{chunk-BEWOA4VV.mjs → chunk-M76V3OGJ.mjs} +508 -401
- package/dist/{chunk-YMZCMH5M.mjs → chunk-OI42GWLR.mjs} +17 -1
- package/dist/{chunk-2BDG2IWP.mjs → chunk-P4DSQ6M4.mjs} +16 -6
- package/dist/{chunk-6IT3MH5M.mjs → chunk-VNMTB3KC.mjs} +130 -216
- package/dist/{chunk-THYRHHO2.mjs → chunk-YJDQ7NLA.mjs} +1 -1
- package/dist/{client-xVrOCYTW.d.ts → client-COek5GXK.d.ts} +3 -3
- package/dist/client.d.ts +8 -8
- package/dist/{config-CEeSV_Xo.d.ts → config-Dfanyqqt.d.ts} +2 -2
- package/dist/{config-CSvCVtB7.d.ts → config-rBpbgTw8.d.ts} +2 -2
- package/dist/direct-executor-GYJEKNV5.mjs +12 -0
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/etcd.mjs +1 -1
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.mjs +1 -1
- package/dist/distribution.d.ts +8 -8
- package/dist/distribution.mjs +109 -69
- package/dist/{engine-77ErLy8P.d.ts → engine-CXKfmK7I.d.ts} +12 -5
- package/dist/index.browser.mjs +1132 -740
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +10 -10
- package/dist/invalidation/filesystem.mjs +1 -1
- package/dist/{memory-CKF-9WLP.d.ts → memory-C7UMCMH3.d.ts} +35 -17
- package/dist/persistence/durability/checkpoint-worker.d.ts +2 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +10 -10
- package/dist/react.d.ts +6 -6
- package/dist/{schema-d5Afu03A.d.ts → schema-Bgzr4g7b.d.ts} +62 -10
- package/dist/server.d.ts +10 -10
- package/dist/server.mjs +11 -7
- package/dist/{tasks-BEiNxDRs.d.ts → tasks-BZt_zes6.d.ts} +3 -1
- package/dist/{thread-FLP45EUD.mjs → thread-JEVAYETR.mjs} +6 -6
- package/dist/{types-DAK7mnUZ.d.ts → types-gOUqhs2F.d.ts} +1 -1
- package/dist/vector/search-worker.d.ts +1 -1
- package/dist/vector/search-worker.mjs +2 -2
- package/dist/workers/entry.mjs +3 -3
- package/package.json +7 -7
- package/dist/chunk-VTPLKNWI.mjs +0 -135
- package/dist/direct-executor-3ZYXSGFX.mjs +0 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { E as EmbeddingAdapter } from './adapters-79OIa4xm.js';
|
|
2
|
-
import { C as ClusterNamespace } from './config-
|
|
3
|
-
import { h as TaskStore } from './tasks-
|
|
2
|
+
import { C as ClusterNamespace } from './config-rBpbgTw8.js';
|
|
3
|
+
import { h as TaskStore } from './tasks-BZt_zes6.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Context handed to {@link OnRequestHook} for every inbound request, captured
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_SORT_FIELDS } from './chunk-
|
|
1
|
+
import { MAX_SORT_FIELDS } from './chunk-653BNMAF.mjs';
|
|
2
2
|
import { nativeSearchCoreIsEnabled, loadNativeCore } from './chunk-YNVIUGF5.mjs';
|
|
3
3
|
import { NarsilError, ServerErrorCodes, ErrorCodes } from './chunk-USKFU7J5.mjs';
|
|
4
4
|
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { readSharedConfigFields, restoredEmbedding, restoredPartitionConfig, MAX_PARTITION_COUNT, MAX_REPLICATION_FACTOR } from './chunk-FVLQ63B7.mjs';
|
|
2
|
+
import { getLanguage } from './chunk-HWQLTLNG.mjs';
|
|
3
|
+
import { MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN } from './chunk-GIVAGD3Q.mjs';
|
|
4
|
+
import { compareCodePoints } from './chunk-OI42GWLR.mjs';
|
|
5
|
+
import { NarsilError, ErrorCodes } from './chunk-USKFU7J5.mjs';
|
|
6
|
+
import { encode, decode } from '@msgpack/msgpack';
|
|
7
|
+
|
|
8
|
+
// src/distribution/cluster/index-settings.ts
|
|
9
|
+
function partitionLimitsOf(partitions) {
|
|
10
|
+
if (partitions === void 0) {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
const { maxDocsPerPartition, watermark } = partitions;
|
|
14
|
+
if (maxDocsPerPartition === void 0 && watermark === void 0) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
...maxDocsPerPartition !== void 0 ? { maxDocsPerPartition } : {},
|
|
19
|
+
...watermark !== void 0 ? { watermark } : {}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function codeInSettings(option, registration) {
|
|
23
|
+
return new NarsilError(
|
|
24
|
+
ErrorCodes.CONFIG_INVALID,
|
|
25
|
+
`A node cannot serialise code into the cluster's index metadata, so register the index ${option} with ${registration} and pass its name`,
|
|
26
|
+
{ option }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
function settingsFieldsOf(source) {
|
|
30
|
+
const { tokenizer, stopWords, bm25, embedding } = source;
|
|
31
|
+
const partitionConfig = partitionLimitsOf(source.partitions);
|
|
32
|
+
if (tokenizer !== void 0 && typeof tokenizer !== "string") {
|
|
33
|
+
throw codeInSettings("tokenizer", "registerTokenizer");
|
|
34
|
+
}
|
|
35
|
+
if (typeof stopWords === "function") {
|
|
36
|
+
throw codeInSettings("stop words", "registerStopWords");
|
|
37
|
+
}
|
|
38
|
+
if (embedding?.adapter !== void 0 && typeof embedding.adapter !== "string") {
|
|
39
|
+
throw codeInSettings("embedding adapter", "registerEmbeddingAdapter");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...tokenizer !== void 0 ? { tokenizer } : {},
|
|
43
|
+
...typeof stopWords === "string" ? { stopWords } : {},
|
|
44
|
+
...stopWords instanceof Set ? { stopWordList: [...stopWords].sort(compareCodePoints) } : {},
|
|
45
|
+
...bm25 !== void 0 ? {
|
|
46
|
+
bm25: {
|
|
47
|
+
...bm25.k1 !== void 0 ? { k1: bm25.k1 } : {},
|
|
48
|
+
...bm25.b !== void 0 ? { b: bm25.b } : {}
|
|
49
|
+
}
|
|
50
|
+
} : {},
|
|
51
|
+
...source.surfaceForms !== void 0 ? { surfaceForms: source.surfaceForms } : {},
|
|
52
|
+
...source.defaultScoring !== void 0 ? { defaultScoring: source.defaultScoring } : {},
|
|
53
|
+
...source.trackPositions !== void 0 ? { trackPositions: source.trackPositions } : {},
|
|
54
|
+
...source.strict !== void 0 ? { strict: source.strict } : {},
|
|
55
|
+
...source.required !== void 0 ? { required: source.required } : {},
|
|
56
|
+
...source.vectorPromotion !== void 0 ? { vectorPromotion: source.vectorPromotion } : {},
|
|
57
|
+
...source.patternValueLimit !== void 0 ? { patternValueLimit: source.patternValueLimit } : {},
|
|
58
|
+
...partitionConfig !== void 0 ? { partitionConfig } : {},
|
|
59
|
+
...embedding !== void 0 ? {
|
|
60
|
+
embedding: {
|
|
61
|
+
fields: embedding.fields,
|
|
62
|
+
...typeof embedding.adapter === "string" ? { adapter: embedding.adapter } : {}
|
|
63
|
+
}
|
|
64
|
+
} : {}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function indexSettingsOf(config) {
|
|
68
|
+
return {
|
|
69
|
+
language: getLanguage(config.language ?? "english").name,
|
|
70
|
+
...settingsFieldsOf(config)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isRecord(value) {
|
|
74
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
function settingsRejection(indexName) {
|
|
77
|
+
return (message) => {
|
|
78
|
+
throw new NarsilError(
|
|
79
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
80
|
+
`Index metadata for '${indexName}' has invalid settings: ${message}`,
|
|
81
|
+
{ indexName }
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function decodeIndexSettings(raw, indexName) {
|
|
86
|
+
if (raw === void 0 || raw === null) {
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
const reject = settingsRejection(indexName);
|
|
90
|
+
if (!isRecord(raw)) {
|
|
91
|
+
return reject("settings must be an object");
|
|
92
|
+
}
|
|
93
|
+
if (typeof raw.language !== "string" || raw.language.length === 0) {
|
|
94
|
+
return reject("language must be a name");
|
|
95
|
+
}
|
|
96
|
+
const embedding = restoredEmbedding(raw.embedding, reject);
|
|
97
|
+
const partitions = restoredPartitionConfig(raw.partitionConfig, reject);
|
|
98
|
+
return {
|
|
99
|
+
language: raw.language,
|
|
100
|
+
...settingsFieldsOf({
|
|
101
|
+
...readSharedConfigFields(raw, reject),
|
|
102
|
+
...partitions !== void 0 ? { partitions } : {},
|
|
103
|
+
...embedding !== void 0 ? { embedding } : {}
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function withPartitionCount(config, partitionCount) {
|
|
108
|
+
return { ...config, partitions: { ...config.partitions, maxPartitions: partitionCount } };
|
|
109
|
+
}
|
|
110
|
+
function indexConfigFromSettings(schema, settings, indexName) {
|
|
111
|
+
if (settings === void 0) {
|
|
112
|
+
return { schema };
|
|
113
|
+
}
|
|
114
|
+
const { language, embedding, partitionConfig, ...fields } = settings;
|
|
115
|
+
return {
|
|
116
|
+
schema,
|
|
117
|
+
language,
|
|
118
|
+
...readSharedConfigFields(fields, settingsRejection(indexName)),
|
|
119
|
+
...partitionConfig !== void 0 ? { partitions: { ...partitionConfig } } : {},
|
|
120
|
+
...embedding !== void 0 ? { embedding } : {}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
var INDEX_CONFIG_PREFIX = "_narsil/index/";
|
|
124
|
+
var INDEX_CONFIG_SUFFIX = "/config";
|
|
125
|
+
function truncateForDisplay(value) {
|
|
126
|
+
const str = String(value);
|
|
127
|
+
if (str.length > 100) {
|
|
128
|
+
return `${str.slice(0, 100)}...`;
|
|
129
|
+
}
|
|
130
|
+
return str;
|
|
131
|
+
}
|
|
132
|
+
function validateIndexName(indexName) {
|
|
133
|
+
if (indexName.length === 0 || indexName.length > MAX_INDEX_NAME_LENGTH) {
|
|
134
|
+
throw new NarsilError(
|
|
135
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
136
|
+
`Index name must be between 1 and ${MAX_INDEX_NAME_LENGTH} characters`,
|
|
137
|
+
{ indexName: truncateForDisplay(indexName) }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (indexName.includes("\0") || indexName.includes("/") || indexName.includes("\\") || indexName.includes("..")) {
|
|
141
|
+
throw new NarsilError(
|
|
142
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
143
|
+
"Index name contains forbidden characters (/, \\, .., or null bytes)",
|
|
144
|
+
{ indexName: truncateForDisplay(indexName) }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (!INDEX_NAME_PATTERN.test(indexName)) {
|
|
148
|
+
throw new NarsilError(
|
|
149
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
150
|
+
"Index name must start with an alphanumeric character and contain only alphanumeric characters, hyphens, underscores, or dots",
|
|
151
|
+
{ indexName: truncateForDisplay(indexName) }
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function isValidInteger(value) {
|
|
156
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
157
|
+
}
|
|
158
|
+
function indexConfigKey(indexName) {
|
|
159
|
+
return `${INDEX_CONFIG_PREFIX}${indexName}${INDEX_CONFIG_SUFFIX}`;
|
|
160
|
+
}
|
|
161
|
+
function validateDecodedMetadata(decoded, indexName) {
|
|
162
|
+
if (!isRecord(decoded)) {
|
|
163
|
+
throw new NarsilError(
|
|
164
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
165
|
+
`Index metadata for '${indexName}' is not an object`,
|
|
166
|
+
{ indexName }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (typeof decoded.indexUuid !== "string" || decoded.indexUuid.length === 0 || decoded.indexUuid.length > 128) {
|
|
170
|
+
throw new NarsilError(
|
|
171
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
172
|
+
`Index metadata for '${indexName}' has invalid indexUuid`,
|
|
173
|
+
{ indexName, received: truncateForDisplay(decoded.indexUuid) }
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (typeof decoded.indexName !== "string") {
|
|
177
|
+
throw new NarsilError(
|
|
178
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
179
|
+
`Index metadata for '${indexName}' has invalid indexName`,
|
|
180
|
+
{ indexName, received: truncateForDisplay(decoded.indexName) }
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (!isValidInteger(decoded.partitionCount) || decoded.partitionCount <= 0 || decoded.partitionCount > MAX_PARTITION_COUNT) {
|
|
184
|
+
throw new NarsilError(
|
|
185
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
186
|
+
`Index metadata for '${indexName}' has invalid partitionCount (must be an integer between 1 and ${MAX_PARTITION_COUNT})`,
|
|
187
|
+
{ indexName, partitionCount: truncateForDisplay(decoded.partitionCount) }
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (!isValidInteger(decoded.replicationFactor) || decoded.replicationFactor < 0 || decoded.replicationFactor > MAX_REPLICATION_FACTOR) {
|
|
191
|
+
throw new NarsilError(
|
|
192
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
193
|
+
`Index metadata for '${indexName}' has invalid replicationFactor (must be an integer between 0 and ${MAX_REPLICATION_FACTOR})`,
|
|
194
|
+
{ indexName, replicationFactor: truncateForDisplay(decoded.replicationFactor) }
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (!isRecord(decoded.constraints)) {
|
|
198
|
+
throw new NarsilError(
|
|
199
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
200
|
+
`Index metadata for '${indexName}' has invalid constraints`,
|
|
201
|
+
{ indexName }
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const constraints = decoded.constraints;
|
|
205
|
+
if (constraints.maxShardsPerNode !== void 0 && constraints.maxShardsPerNode !== null && typeof constraints.maxShardsPerNode !== "number") {
|
|
206
|
+
throw new NarsilError(
|
|
207
|
+
ErrorCodes.CONTROLLER_METADATA_INVALID,
|
|
208
|
+
`Index metadata for '${indexName}' has invalid maxShardsPerNode (must be a number or null)`,
|
|
209
|
+
{ indexName, maxShardsPerNode: truncateForDisplay(constraints.maxShardsPerNode) }
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
indexUuid: decoded.indexUuid,
|
|
214
|
+
indexName: decoded.indexName,
|
|
215
|
+
partitionCount: decoded.partitionCount,
|
|
216
|
+
replicationFactor: decoded.replicationFactor,
|
|
217
|
+
constraints: {
|
|
218
|
+
zoneAwareness: constraints.zoneAwareness === true,
|
|
219
|
+
zoneAttribute: typeof constraints.zoneAttribute === "string" ? constraints.zoneAttribute : "zone",
|
|
220
|
+
maxShardsPerNode: typeof constraints.maxShardsPerNode === "number" ? constraints.maxShardsPerNode : null
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async function putIndexMetadata(coordinator, metadata) {
|
|
225
|
+
validateIndexName(metadata.indexName);
|
|
226
|
+
const key = indexConfigKey(metadata.indexName);
|
|
227
|
+
const encoded = encode(metadata, { ignoreUndefined: true });
|
|
228
|
+
const bytes = new Uint8Array(encoded);
|
|
229
|
+
if (await coordinator.compareAndSet(key, null, bytes)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
const current = await coordinator.get(key);
|
|
233
|
+
if (current === null || current.byteLength > 0) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
return coordinator.compareAndSet(key, current, bytes);
|
|
237
|
+
}
|
|
238
|
+
async function readStoredMetadata(coordinator, indexName) {
|
|
239
|
+
validateIndexName(indexName);
|
|
240
|
+
const raw = await coordinator.get(indexConfigKey(indexName));
|
|
241
|
+
if (raw === null || raw.byteLength === 0) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const decoded = decode(raw);
|
|
245
|
+
const record = validateDecodedMetadata(decoded, indexName);
|
|
246
|
+
return { record, rawSettings: isRecord(decoded) ? decoded.settings : void 0 };
|
|
247
|
+
}
|
|
248
|
+
async function getIndexMetadata(coordinator, indexName) {
|
|
249
|
+
return (await readStoredMetadata(coordinator, indexName))?.record ?? null;
|
|
250
|
+
}
|
|
251
|
+
async function getClusterIndexConfig(coordinator, indexName, schema) {
|
|
252
|
+
const stored = await readStoredMetadata(coordinator, indexName);
|
|
253
|
+
return indexConfigFromSettings(schema, decodeIndexSettings(stored?.rawSettings, indexName), indexName);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export { getClusterIndexConfig, getIndexMetadata, indexConfigKey, indexSettingsOf, putIndexMetadata, validateIndexName, withPartitionCount };
|
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
import { MAX_M } from './chunk-YNVIUGF5.mjs';
|
|
2
|
+
import { exceedsCodePoints } from './chunk-OI42GWLR.mjs';
|
|
2
3
|
import { NarsilError, ErrorCodes } from './chunk-USKFU7J5.mjs';
|
|
3
4
|
|
|
4
5
|
// src/schema/constants.ts
|
|
5
6
|
var MAX_NESTING_DEPTH = 4;
|
|
6
7
|
var MAX_DOCUMENT_NESTING_DEPTH = 32;
|
|
8
|
+
var DEFAULT_PATTERN_VALUE_LIMIT = 8192;
|
|
9
|
+
var MIN_PATTERN_VALUE_LIMIT = 1;
|
|
10
|
+
var MAX_PATTERN_VALUE_LIMIT = 65536;
|
|
7
11
|
|
|
8
12
|
// src/schema/validator/shared.ts
|
|
9
|
-
var SORTABLE_TEXT_FIELD_TYPE = "string:sortable";
|
|
10
|
-
var SCALAR_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
11
|
-
"string",
|
|
12
|
-
SORTABLE_TEXT_FIELD_TYPE,
|
|
13
|
-
"number",
|
|
14
|
-
"boolean",
|
|
15
|
-
"enum",
|
|
16
|
-
"geopoint",
|
|
17
|
-
"string[]",
|
|
18
|
-
"number[]",
|
|
19
|
-
"boolean[]",
|
|
20
|
-
"enum[]"
|
|
21
|
-
]);
|
|
22
|
-
function isTextFieldType(fieldType) {
|
|
23
|
-
return fieldType === "string" || fieldType === SORTABLE_TEXT_FIELD_TYPE;
|
|
24
|
-
}
|
|
25
13
|
var VECTOR_PATTERN = /^vector\[(\d+)]$/;
|
|
26
14
|
function isGeopointOrVectorType(fieldType) {
|
|
27
15
|
return fieldType === "geopoint" || fieldType !== void 0 && VECTOR_PATTERN.test(fieldType);
|
|
@@ -33,6 +21,114 @@ function isPlainObject(value) {
|
|
|
33
21
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
22
|
}
|
|
35
23
|
|
|
24
|
+
// src/schema/validator/field-type.ts
|
|
25
|
+
var FIELD_OPTION_ORDER = ["sortable", "pattern", "partial"];
|
|
26
|
+
var OPTIONS_BY_BASE = {
|
|
27
|
+
string: FIELD_OPTION_ORDER,
|
|
28
|
+
"string[]": FIELD_OPTION_ORDER,
|
|
29
|
+
verbatim: ["sortable"],
|
|
30
|
+
"verbatim[]": ["sortable"],
|
|
31
|
+
number: [],
|
|
32
|
+
"number[]": [],
|
|
33
|
+
boolean: [],
|
|
34
|
+
"boolean[]": [],
|
|
35
|
+
enum: [],
|
|
36
|
+
"enum[]": [],
|
|
37
|
+
geopoint: [],
|
|
38
|
+
vector: []
|
|
39
|
+
};
|
|
40
|
+
var TYPE_SEPARATOR = ":";
|
|
41
|
+
function isNamedBase(name) {
|
|
42
|
+
return name !== "vector" && Object.hasOwn(OPTIONS_BY_BASE, name);
|
|
43
|
+
}
|
|
44
|
+
function describeAllowedOptions(baseName, allowed) {
|
|
45
|
+
if (allowed.length === 0) return `the "${baseName}" type has no options`;
|
|
46
|
+
const quoted = allowed.map((option) => `"${option}"`);
|
|
47
|
+
if (quoted.length === 1) return `the only option of the "${baseName}" type is ${quoted[0]}`;
|
|
48
|
+
return `the options of the "${baseName}" type are ${quoted.slice(0, -1).join(", ")}, and ${quoted[quoted.length - 1]}`;
|
|
49
|
+
}
|
|
50
|
+
function invalidType(path, type, message) {
|
|
51
|
+
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, message, { field: path, type });
|
|
52
|
+
}
|
|
53
|
+
function parseBase(baseName, path, type) {
|
|
54
|
+
if (isNamedBase(baseName)) return { base: baseName, dimension: 0 };
|
|
55
|
+
const vectorMatch = VECTOR_PATTERN.exec(baseName);
|
|
56
|
+
if (vectorMatch === null) {
|
|
57
|
+
invalidType(path, type, `Field "${path}" has an unsupported type: "${type}"`);
|
|
58
|
+
}
|
|
59
|
+
const dimension = Number.parseInt(vectorMatch[1], 10);
|
|
60
|
+
if (dimension <= 0) {
|
|
61
|
+
throw new NarsilError(
|
|
62
|
+
ErrorCodes.SCHEMA_INVALID_VECTOR_DIMENSION,
|
|
63
|
+
`Vector field "${path}" must have a positive dimension, got ${dimension}`,
|
|
64
|
+
{ field: path, dimension }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return { base: "vector", dimension };
|
|
68
|
+
}
|
|
69
|
+
function parseFieldType(type, path) {
|
|
70
|
+
const separatorIndex = type.indexOf(TYPE_SEPARATOR);
|
|
71
|
+
const baseName = separatorIndex === -1 ? type : type.slice(0, separatorIndex);
|
|
72
|
+
const { base, dimension } = parseBase(baseName, path, type);
|
|
73
|
+
const allowed = OPTIONS_BY_BASE[base];
|
|
74
|
+
const options = /* @__PURE__ */ new Set();
|
|
75
|
+
if (separatorIndex !== -1) {
|
|
76
|
+
for (const option of type.slice(separatorIndex + 1).split(TYPE_SEPARATOR)) {
|
|
77
|
+
if (!allowed.includes(option)) {
|
|
78
|
+
invalidType(
|
|
79
|
+
path,
|
|
80
|
+
type,
|
|
81
|
+
`Field "${path}" has type "${type}" with the option "${option}", but ${describeAllowedOptions(baseName, allowed)}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (options.has(option)) {
|
|
85
|
+
invalidType(path, type, `Field "${path}" has the "${option}" option twice in type "${type}"`);
|
|
86
|
+
}
|
|
87
|
+
options.add(option);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const ordered = FIELD_OPTION_ORDER.filter((option) => options.has(option));
|
|
91
|
+
const verbatim = base === "verbatim" || base === "verbatim[]";
|
|
92
|
+
return {
|
|
93
|
+
type: [baseName, ...ordered].join(TYPE_SEPARATOR),
|
|
94
|
+
base,
|
|
95
|
+
sortable: options.has("sortable"),
|
|
96
|
+
pattern: verbatim || options.has("pattern"),
|
|
97
|
+
partial: options.has("partial"),
|
|
98
|
+
dimension
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function parsedTypeOf(type) {
|
|
102
|
+
if (type === void 0) return void 0;
|
|
103
|
+
try {
|
|
104
|
+
return parseFieldType(type, "");
|
|
105
|
+
} catch {
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function withSortableOption(parsed) {
|
|
110
|
+
if (parsed.sortable) return parsed.type;
|
|
111
|
+
const separatorIndex = parsed.type.indexOf(TYPE_SEPARATOR);
|
|
112
|
+
const baseName = separatorIndex === -1 ? parsed.type : parsed.type.slice(0, separatorIndex);
|
|
113
|
+
return parseFieldType(`${baseName}${TYPE_SEPARATOR}sortable${parsed.type.slice(baseName.length)}`, "").type;
|
|
114
|
+
}
|
|
115
|
+
function isListBase(base) {
|
|
116
|
+
return base.endsWith("[]");
|
|
117
|
+
}
|
|
118
|
+
function isWordIndexedBase(base) {
|
|
119
|
+
return base === "string" || base === "string[]";
|
|
120
|
+
}
|
|
121
|
+
function isSingleStringFieldType(fieldType) {
|
|
122
|
+
return parsedTypeOf(fieldType)?.base === "string";
|
|
123
|
+
}
|
|
124
|
+
function isWordIndexedFieldType(fieldType) {
|
|
125
|
+
const parsed = parsedTypeOf(fieldType);
|
|
126
|
+
return parsed !== void 0 && isWordIndexedBase(parsed.base);
|
|
127
|
+
}
|
|
128
|
+
function isStringValuedBase(base) {
|
|
129
|
+
return base === "string" || base === "string[]" || base === "verbatim" || base === "verbatim[]";
|
|
130
|
+
}
|
|
131
|
+
|
|
36
132
|
// src/schema/validator/schema.ts
|
|
37
133
|
function validateSchemaFields(schema, depth, prefix) {
|
|
38
134
|
if (depth > MAX_NESTING_DEPTH) {
|
|
@@ -42,6 +138,11 @@ function validateSchemaFields(schema, depth, prefix) {
|
|
|
42
138
|
{ path: prefix, maxDepth: MAX_NESTING_DEPTH }
|
|
43
139
|
);
|
|
44
140
|
}
|
|
141
|
+
let canonical = null;
|
|
142
|
+
const replace = (field, value) => {
|
|
143
|
+
if (canonical === null) canonical = { ...schema };
|
|
144
|
+
canonical[field] = value;
|
|
145
|
+
};
|
|
45
146
|
for (const [field, type] of Object.entries(schema)) {
|
|
46
147
|
const path = prefix ? `${prefix}.${field}` : field;
|
|
47
148
|
if (PROTOTYPE_POLLUTION_KEYS.has(field)) {
|
|
@@ -65,7 +166,8 @@ function validateSchemaFields(schema, depth, prefix) {
|
|
|
65
166
|
);
|
|
66
167
|
}
|
|
67
168
|
if (isPlainObject(type)) {
|
|
68
|
-
validateSchemaFields(type, depth + 1, path);
|
|
169
|
+
const nested = validateSchemaFields(type, depth + 1, path);
|
|
170
|
+
if (nested !== type) replace(field, nested);
|
|
69
171
|
continue;
|
|
70
172
|
}
|
|
71
173
|
if (typeof type !== "string") {
|
|
@@ -78,24 +180,10 @@ function validateSchemaFields(schema, depth, prefix) {
|
|
|
78
180
|
}
|
|
79
181
|
);
|
|
80
182
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (vectorMatch) {
|
|
84
|
-
const dimension = Number.parseInt(vectorMatch[1], 10);
|
|
85
|
-
if (dimension <= 0) {
|
|
86
|
-
throw new NarsilError(
|
|
87
|
-
ErrorCodes.SCHEMA_INVALID_VECTOR_DIMENSION,
|
|
88
|
-
`Vector field "${path}" must have a positive dimension, got ${dimension}`,
|
|
89
|
-
{ field: path, dimension }
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, `Field "${path}" has an unsupported type: "${type}"`, {
|
|
95
|
-
field: path,
|
|
96
|
-
type
|
|
97
|
-
});
|
|
183
|
+
const parsed = parseFieldType(type, path);
|
|
184
|
+
if (parsed.type !== type) replace(field, parsed.type);
|
|
98
185
|
}
|
|
186
|
+
return canonical ?? schema;
|
|
99
187
|
}
|
|
100
188
|
function validateSchema(schema) {
|
|
101
189
|
if (!isPlainObject(schema)) {
|
|
@@ -106,7 +194,19 @@ function validateSchema(schema) {
|
|
|
106
194
|
if (Object.keys(schema).length === 0) {
|
|
107
195
|
throw new NarsilError(ErrorCodes.SCHEMA_INVALID_TYPE, "Schema must define at least one field");
|
|
108
196
|
}
|
|
109
|
-
validateSchemaFields(schema, 1, "");
|
|
197
|
+
return validateSchemaFields(schema, 1, "");
|
|
198
|
+
}
|
|
199
|
+
function requireValidFieldTypes(flatSchema) {
|
|
200
|
+
for (const [path, type] of Object.entries(flatSchema)) {
|
|
201
|
+
if (typeof type !== "string") {
|
|
202
|
+
throw new NarsilError(
|
|
203
|
+
ErrorCodes.SCHEMA_INVALID_TYPE,
|
|
204
|
+
`Field "${path}" has an invalid type definition: ${String(type)}`,
|
|
205
|
+
{ field: path, type: String(type) }
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
parseFieldType(type, path);
|
|
209
|
+
}
|
|
110
210
|
}
|
|
111
211
|
function flattenRecursive(schema, prefix, result) {
|
|
112
212
|
for (const [field, type] of Object.entries(schema)) {
|
|
@@ -127,14 +227,22 @@ function flattenSchema(schema) {
|
|
|
127
227
|
flattenedSchemas.set(schema, Object.freeze(result));
|
|
128
228
|
return result;
|
|
129
229
|
}
|
|
230
|
+
var parsedSchemas = /* @__PURE__ */ new WeakMap();
|
|
231
|
+
function schemaFieldsOf(schema) {
|
|
232
|
+
const cached = parsedSchemas.get(schema);
|
|
233
|
+
if (cached !== void 0) return cached;
|
|
234
|
+
const fields = [];
|
|
235
|
+
for (const [path, type] of Object.entries(flattenSchema(schema))) {
|
|
236
|
+
fields.push({ ...parseFieldType(type, path), path });
|
|
237
|
+
}
|
|
238
|
+
const frozen = Object.freeze(fields);
|
|
239
|
+
parsedSchemas.set(schema, frozen);
|
|
240
|
+
return frozen;
|
|
241
|
+
}
|
|
130
242
|
function extractVectorFieldsFromSchema(schema) {
|
|
131
|
-
const flat = flattenSchema(schema);
|
|
132
243
|
const result = /* @__PURE__ */ new Map();
|
|
133
|
-
for (const
|
|
134
|
-
|
|
135
|
-
if (match) {
|
|
136
|
-
result.set(fieldPath, Number.parseInt(match[1], 10));
|
|
137
|
-
}
|
|
244
|
+
for (const field of schemaFieldsOf(schema)) {
|
|
245
|
+
if (field.base === "vector") result.set(field.path, field.dimension);
|
|
138
246
|
}
|
|
139
247
|
return result;
|
|
140
248
|
}
|
|
@@ -231,10 +339,19 @@ function validateTypedArray(path, value, elementType) {
|
|
|
231
339
|
}
|
|
232
340
|
}
|
|
233
341
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
342
|
+
function validatePatternValue(path, value, patternValueLimit, index) {
|
|
343
|
+
if (!exceedsCodePoints(value, patternValueLimit)) return;
|
|
344
|
+
const subject = index === void 0 ? `The value of field "${path}"` : `Element ${index} of field "${path}"`;
|
|
345
|
+
throw new NarsilError(
|
|
346
|
+
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
347
|
+
`${subject} is longer than ${patternValueLimit} code points, which is the pattern value limit of this index`,
|
|
348
|
+
{ field: path, limit: patternValueLimit, ...index === void 0 ? {} : { index } }
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
function validateFieldValue(path, value, type, patternValueLimit) {
|
|
352
|
+
switch (type.base) {
|
|
236
353
|
case "string":
|
|
237
|
-
case
|
|
354
|
+
case "verbatim":
|
|
238
355
|
if (typeof value !== "string") {
|
|
239
356
|
throw new NarsilError(
|
|
240
357
|
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
@@ -246,6 +363,7 @@ function validateFieldValue(path, value, type) {
|
|
|
246
363
|
}
|
|
247
364
|
);
|
|
248
365
|
}
|
|
366
|
+
if (type.pattern) validatePatternValue(path, value, patternValueLimit);
|
|
249
367
|
return;
|
|
250
368
|
case "number":
|
|
251
369
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
@@ -286,7 +404,12 @@ function validateFieldValue(path, value, type) {
|
|
|
286
404
|
validateGeopoint(path, value);
|
|
287
405
|
return;
|
|
288
406
|
case "string[]":
|
|
407
|
+
case "verbatim[]":
|
|
289
408
|
validateTypedArray(path, value, "string");
|
|
409
|
+
if (type.pattern) {
|
|
410
|
+
const elements = value;
|
|
411
|
+
for (let i = 0; i < elements.length; i++) validatePatternValue(path, elements[i], patternValueLimit, i);
|
|
412
|
+
}
|
|
290
413
|
return;
|
|
291
414
|
case "number[]":
|
|
292
415
|
validateTypedArray(path, value, "number");
|
|
@@ -297,12 +420,9 @@ function validateFieldValue(path, value, type) {
|
|
|
297
420
|
case "enum[]":
|
|
298
421
|
validateTypedArray(path, value, "string");
|
|
299
422
|
return;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
validateVector(path, value, Number.parseInt(vectorMatch[1], 10));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
423
|
+
case "vector":
|
|
424
|
+
validateVector(path, value, type.dimension);
|
|
425
|
+
return;
|
|
306
426
|
}
|
|
307
427
|
}
|
|
308
428
|
|
|
@@ -383,32 +503,44 @@ function vectorFieldPaths(schema) {
|
|
|
383
503
|
vectorPathCache.set(schema, paths);
|
|
384
504
|
return paths;
|
|
385
505
|
}
|
|
386
|
-
|
|
387
|
-
|
|
506
|
+
var parsedLevelCache = /* @__PURE__ */ new WeakMap();
|
|
507
|
+
function parsedLevel(schema, prefix) {
|
|
508
|
+
let entries = parsedLevelCache.get(schema);
|
|
509
|
+
if (entries !== void 0) return entries;
|
|
510
|
+
entries = Object.entries(schema).map(([field, type]) => {
|
|
511
|
+
if (isPlainObject(type)) return { field, nested: type, type: null };
|
|
388
512
|
const path = prefix ? `${prefix}.${field}` : field;
|
|
513
|
+
return { field, nested: null, type: parseFieldType(type, path) };
|
|
514
|
+
});
|
|
515
|
+
parsedLevelCache.set(schema, entries);
|
|
516
|
+
return entries;
|
|
517
|
+
}
|
|
518
|
+
function validateDocumentFields(doc, schema, prefix, patternValueLimit) {
|
|
519
|
+
for (const { field, nested, type } of parsedLevel(schema, prefix)) {
|
|
389
520
|
const value = doc[field];
|
|
390
521
|
if (value === void 0 || value === null) continue;
|
|
391
|
-
|
|
522
|
+
const path = prefix ? `${prefix}.${field}` : field;
|
|
523
|
+
if (nested !== null) {
|
|
392
524
|
if (!isPlainObject(value)) {
|
|
393
525
|
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, `Field "${path}" expected an object`, {
|
|
394
526
|
field: path,
|
|
395
527
|
received: Array.isArray(value) ? "array" : typeof value
|
|
396
528
|
});
|
|
397
529
|
}
|
|
398
|
-
validateDocumentFields(value,
|
|
530
|
+
validateDocumentFields(value, nested, path, patternValueLimit);
|
|
399
531
|
continue;
|
|
400
532
|
}
|
|
401
|
-
validateFieldValue(path, value, type);
|
|
533
|
+
if (type !== null) validateFieldValue(path, value, type, patternValueLimit);
|
|
402
534
|
}
|
|
403
535
|
}
|
|
404
|
-
function validateDocument(document, schema) {
|
|
536
|
+
function validateDocument(document, schema, patternValueLimit = DEFAULT_PATTERN_VALUE_LIMIT) {
|
|
405
537
|
if (!isPlainObject(document)) {
|
|
406
538
|
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document must be a plain object", {
|
|
407
539
|
received: typeof document
|
|
408
540
|
});
|
|
409
541
|
}
|
|
410
542
|
assertStorableDocument(document, vectorFieldPaths(schema));
|
|
411
|
-
validateDocumentFields(document, schema, "");
|
|
543
|
+
validateDocumentFields(document, schema, "", patternValueLimit);
|
|
412
544
|
}
|
|
413
545
|
function collectExtraFields(doc, schema, prefix, extras) {
|
|
414
546
|
for (const key of Object.keys(doc)) {
|
|
@@ -526,4 +658,4 @@ var DEFAULT_LINEAR_ALPHA = 0.5;
|
|
|
526
658
|
var MIN_LINEAR_ALPHA = 0;
|
|
527
659
|
var MAX_LINEAR_ALPHA = 1;
|
|
528
660
|
|
|
529
|
-
export { DEFAULT_FUZZY_PREFIX_LENGTH, DEFAULT_LINEAR_ALPHA, DEFAULT_MAX_PER_GROUP, DEFAULT_PAGE_SIZE, DEFAULT_RANK_CONSTANT, MAX_CURSOR_ANCHOR_CODE_POINTS, MAX_CURSOR_LENGTH, MAX_LINEAR_ALPHA, MAX_NESTING_DEPTH, MAX_PREFIX_LENGTH, MAX_SORT_FIELDS, MAX_SORT_FIELD_NAME_LENGTH, MAX_TOLERANCE, MIN_LINEAR_ALPHA, MIN_RANK_CONSTANT, PREFIX_MAX_EXPANSIONS, PROTOTYPE_POLLUTION_KEYS, RESULT_WINDOW,
|
|
661
|
+
export { DEFAULT_FUZZY_PREFIX_LENGTH, DEFAULT_LINEAR_ALPHA, DEFAULT_MAX_PER_GROUP, DEFAULT_PAGE_SIZE, DEFAULT_RANK_CONSTANT, MAX_CURSOR_ANCHOR_CODE_POINTS, MAX_CURSOR_LENGTH, MAX_LINEAR_ALPHA, MAX_NESTING_DEPTH, MAX_PATTERN_VALUE_LIMIT, MAX_PREFIX_LENGTH, MAX_SORT_FIELDS, MAX_SORT_FIELD_NAME_LENGTH, MAX_TOLERANCE, MIN_LINEAR_ALPHA, MIN_PATTERN_VALUE_LIMIT, MIN_RANK_CONSTANT, PREFIX_MAX_EXPANSIONS, PROTOTYPE_POLLUTION_KEYS, RESULT_WINDOW, VECTOR_PATTERN, extractVectorFieldsFromSchema, flattenSchema, isGeopointOrVectorType, isListBase, isQuantizationMode, isSingleStringFieldType, isStorageMode, isStringValuedBase, isWordIndexedBase, isWordIndexedFieldType, parsedTypeOf, requireValidFieldTypes, schemaFieldsOf, validateDocument, validateDocumentStrict, validateRequiredFields, validateSchema, validateVectorPromotion, validateVectorStorage, withSortableOption };
|