@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.
Files changed (45) hide show
  1. package/dist/{capabilities-B1wz2Zl0.d.ts → capabilities-Dt-7B4Wz.d.ts} +2 -2
  2. package/dist/{chunk-DD5X23E6.mjs → chunk-2STVUTNA.mjs} +1 -1
  3. package/dist/chunk-3JGWULDF.mjs +256 -0
  4. package/dist/{chunk-6TGZG2TV.mjs → chunk-653BNMAF.mjs} +190 -58
  5. package/dist/{chunk-YA52AC2V.mjs → chunk-FVLQ63B7.mjs} +203 -9
  6. package/dist/{chunk-5JLSTECC.mjs → chunk-GIVAGD3Q.mjs} +1 -1
  7. package/dist/{chunk-AV4IJKD2.mjs → chunk-HWQLTLNG.mjs} +18 -1
  8. package/dist/{chunk-4VU5TWCR.mjs → chunk-LMO5TJUS.mjs} +40 -35
  9. package/dist/{chunk-BEWOA4VV.mjs → chunk-M76V3OGJ.mjs} +508 -401
  10. package/dist/{chunk-YMZCMH5M.mjs → chunk-OI42GWLR.mjs} +17 -1
  11. package/dist/{chunk-2BDG2IWP.mjs → chunk-P4DSQ6M4.mjs} +16 -6
  12. package/dist/{chunk-6IT3MH5M.mjs → chunk-VNMTB3KC.mjs} +130 -216
  13. package/dist/{chunk-THYRHHO2.mjs → chunk-YJDQ7NLA.mjs} +1 -1
  14. package/dist/{client-xVrOCYTW.d.ts → client-COek5GXK.d.ts} +3 -3
  15. package/dist/client.d.ts +8 -8
  16. package/dist/{config-CEeSV_Xo.d.ts → config-Dfanyqqt.d.ts} +2 -2
  17. package/dist/{config-CSvCVtB7.d.ts → config-rBpbgTw8.d.ts} +2 -2
  18. package/dist/direct-executor-GYJEKNV5.mjs +12 -0
  19. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  20. package/dist/distribution/coordinator/etcd.mjs +1 -1
  21. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  22. package/dist/distribution/coordinator/in-memory.mjs +1 -1
  23. package/dist/distribution.d.ts +8 -8
  24. package/dist/distribution.mjs +109 -69
  25. package/dist/{engine-77ErLy8P.d.ts → engine-CXKfmK7I.d.ts} +12 -5
  26. package/dist/index.browser.mjs +1132 -740
  27. package/dist/index.d.ts +7 -7
  28. package/dist/index.mjs +10 -10
  29. package/dist/invalidation/filesystem.mjs +1 -1
  30. package/dist/{memory-CKF-9WLP.d.ts → memory-C7UMCMH3.d.ts} +35 -17
  31. package/dist/persistence/durability/checkpoint-worker.d.ts +2 -1
  32. package/dist/persistence/durability/checkpoint-worker.mjs +10 -10
  33. package/dist/react.d.ts +6 -6
  34. package/dist/{schema-d5Afu03A.d.ts → schema-Bgzr4g7b.d.ts} +62 -10
  35. package/dist/server.d.ts +10 -10
  36. package/dist/server.mjs +11 -7
  37. package/dist/{tasks-BEiNxDRs.d.ts → tasks-BZt_zes6.d.ts} +3 -1
  38. package/dist/{thread-FLP45EUD.mjs → thread-JEVAYETR.mjs} +6 -6
  39. package/dist/{types-DAK7mnUZ.d.ts → types-gOUqhs2F.d.ts} +1 -1
  40. package/dist/vector/search-worker.d.ts +1 -1
  41. package/dist/vector/search-worker.mjs +2 -2
  42. package/dist/workers/entry.mjs +3 -3
  43. package/package.json +7 -7
  44. package/dist/chunk-VTPLKNWI.mjs +0 -135
  45. 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-CSvCVtB7.js';
3
- import { h as TaskStore } from './tasks-BEiNxDRs.js';
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-6TGZG2TV.mjs';
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
- if (SCALAR_FIELD_TYPES.has(type)) continue;
82
- const vectorMatch = VECTOR_PATTERN.exec(type);
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 [fieldPath, fieldType] of Object.entries(flat)) {
134
- const match = VECTOR_PATTERN.exec(fieldType);
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 validateFieldValue(path, value, type) {
235
- switch (type) {
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 SORTABLE_TEXT_FIELD_TYPE:
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
- default: {
301
- const vectorMatch = VECTOR_PATTERN.exec(type);
302
- if (vectorMatch) {
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
- function validateDocumentFields(doc, schema, prefix) {
387
- for (const [field, type] of Object.entries(schema)) {
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
- if (isPlainObject(type)) {
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, type, path);
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, SORTABLE_TEXT_FIELD_TYPE, VECTOR_PATTERN, extractVectorFieldsFromSchema, flattenSchema, isGeopointOrVectorType, isQuantizationMode, isStorageMode, isTextFieldType, validateDocument, validateDocumentStrict, validateRequiredFields, validateSchema, validateVectorPromotion, validateVectorStorage };
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 };