@nexo-labs/payload-typesense 1.3.0 → 1.4.3

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/index.mjs CHANGED
@@ -1,51 +1,25 @@
1
+ import Typesense from "typesense";
1
2
  import OpenAI from "openai";
2
3
  import { GoogleGenerativeAI, TaskType } from "@google/generative-ai";
3
- import Typesense from "typesense";
4
4
  import { z } from "zod";
5
5
  import { MarkdownTextSplitter, RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
6
+ import { convertLexicalToMarkdown, editorConfigFactory } from "@payloadcms/richtext-lexical";
6
7
 
7
- //#region src/core/di/container.ts
8
- var DIContainer = class {
9
- services = /* @__PURE__ */ new Map();
10
- factories = /* @__PURE__ */ new Map();
11
- register(token, factory) {
12
- this.factories.set(token, factory);
13
- }
14
- singleton(token, instance) {
15
- this.services.set(token, instance);
16
- }
17
- resolve(token) {
18
- if (this.services.has(token)) return this.services.get(token);
19
- if (this.factories.has(token)) {
20
- const factory = this.factories.get(token);
21
- if (factory) return factory();
22
- }
23
- throw new Error(`Service not found: ${token.toString()}`);
24
- }
25
- has(token) {
26
- return this.services.has(token) || this.factories.has(token);
27
- }
28
- /**
29
- * Clears all registered services and factories.
30
- * Useful for testing.
31
- */
32
- clear() {
33
- this.services.clear();
34
- this.factories.clear();
35
- }
8
+ //#region src/core/client/typesense-client.ts
9
+ const createTypesenseClient = (typesenseConfig) => {
10
+ return new Typesense.Client({
11
+ apiKey: typesenseConfig.apiKey,
12
+ connectionTimeoutSeconds: typesenseConfig.connectionTimeoutSeconds || 2,
13
+ nodes: typesenseConfig.nodes
14
+ });
36
15
  };
37
-
38
- //#endregion
39
- //#region src/core/di/tokens.ts
40
- const TOKENS = {
41
- CONFIG: Symbol.for("Config"),
42
- LOGGER: Symbol.for("Logger"),
43
- TYPESENSE_CLIENT: Symbol.for("TypesenseClient"),
44
- EMBEDDING_PROVIDER: Symbol.for("EmbeddingProvider"),
45
- EMBEDDING_SERVICE: Symbol.for("EmbeddingService"),
46
- SEARCH_SERVICE: Symbol.for("SearchService"),
47
- SYNC_SERVICE: Symbol.for("SyncService"),
48
- RAG_SERVICE: Symbol.for("RAGService")
16
+ const testTypesenseConnection = async (client) => {
17
+ try {
18
+ await client.health.retrieve();
19
+ return true;
20
+ } catch (_error) {
21
+ return false;
22
+ }
49
23
  };
50
24
 
51
25
  //#endregion
@@ -246,198 +220,6 @@ const ErrorCodes = {
246
220
  VALIDATION_ERROR: "ERR_9002"
247
221
  };
248
222
 
249
- //#endregion
250
- //#region src/features/embedding/providers/openai-provider.ts
251
- var OpenAIEmbeddingProvider = class {
252
- client;
253
- model;
254
- dimensions;
255
- constructor(config, logger$1) {
256
- this.logger = logger$1;
257
- if (!config.apiKey) throw new Error("OpenAI API key is required");
258
- this.client = new OpenAI({ apiKey: config.apiKey });
259
- this.model = config.model || DEFAULT_EMBEDDING_MODEL;
260
- this.dimensions = config.dimensions || DEFAULT_EMBEDDING_DIMENSIONS;
261
- }
262
- async generateEmbedding(text) {
263
- if (!text || text.trim().length < MIN_EMBEDDING_TEXT_LENGTH) return null;
264
- try {
265
- const response = await this.client.embeddings.create({
266
- model: this.model,
267
- input: text.trim(),
268
- dimensions: this.dimensions
269
- });
270
- const embedding = response.data[0]?.embedding;
271
- if (!embedding) return null;
272
- return {
273
- embedding,
274
- usage: {
275
- promptTokens: response.usage?.prompt_tokens || 0,
276
- totalTokens: response.usage?.total_tokens || 0
277
- }
278
- };
279
- } catch (error) {
280
- this.logger.error("OpenAI embedding generation failed", error, { model: this.model });
281
- return null;
282
- }
283
- }
284
- async generateBatchEmbeddings(texts) {
285
- const validTexts = texts.filter((t) => t && t.trim().length >= MIN_EMBEDDING_TEXT_LENGTH);
286
- if (validTexts.length === 0) return null;
287
- try {
288
- const response = await this.client.embeddings.create({
289
- model: this.model,
290
- input: validTexts.map((t) => t.trim()),
291
- dimensions: this.dimensions
292
- });
293
- return {
294
- embeddings: response.data.map((d) => d.embedding),
295
- usage: {
296
- promptTokens: response.usage?.prompt_tokens || 0,
297
- totalTokens: response.usage?.total_tokens || 0
298
- }
299
- };
300
- } catch (error) {
301
- this.logger.error("OpenAI batch embedding generation failed", error, {
302
- model: this.model,
303
- count: texts.length
304
- });
305
- return null;
306
- }
307
- }
308
- };
309
-
310
- //#endregion
311
- //#region src/features/embedding/providers/gemini-provider.ts
312
- var GeminiEmbeddingProvider = class {
313
- client;
314
- model;
315
- constructor(config, logger$1) {
316
- this.logger = logger$1;
317
- if (!config.apiKey) throw new Error("Gemini API key is required");
318
- this.client = new GoogleGenerativeAI(config.apiKey);
319
- this.model = config.model || DEFAULT_GEMINI_EMBEDDING_MODEL;
320
- }
321
- async generateEmbedding(text) {
322
- if (!text || text.trim().length < MIN_EMBEDDING_TEXT_LENGTH) return null;
323
- try {
324
- const embedding = (await this.client.getGenerativeModel({ model: this.model }).embedContent({
325
- content: {
326
- role: "user",
327
- parts: [{ text: text.trim() }]
328
- },
329
- taskType: TaskType.RETRIEVAL_DOCUMENT
330
- })).embedding.values;
331
- const estimatedTokens = Math.ceil(text.length / 4);
332
- return {
333
- embedding,
334
- usage: {
335
- promptTokens: estimatedTokens,
336
- totalTokens: estimatedTokens
337
- }
338
- };
339
- } catch (error) {
340
- this.logger.error("Gemini embedding generation failed", error, { model: this.model });
341
- return null;
342
- }
343
- }
344
- async generateBatchEmbeddings(texts) {
345
- const validTexts = texts.filter((t) => t && t.trim().length >= MIN_EMBEDDING_TEXT_LENGTH);
346
- if (validTexts.length === 0) return null;
347
- try {
348
- const model = this.client.getGenerativeModel({ model: this.model });
349
- const embeddings = [];
350
- let totalTokens = 0;
351
- for (const text of validTexts) {
352
- const result = await model.embedContent({
353
- content: {
354
- role: "user",
355
- parts: [{ text: text.trim() }]
356
- },
357
- taskType: TaskType.RETRIEVAL_DOCUMENT
358
- });
359
- embeddings.push(result.embedding.values);
360
- totalTokens += Math.ceil(text.length / 4);
361
- }
362
- return {
363
- embeddings,
364
- usage: {
365
- promptTokens: totalTokens,
366
- totalTokens
367
- }
368
- };
369
- } catch (error) {
370
- this.logger.error("Gemini batch embedding generation failed", error, {
371
- model: this.model,
372
- count: texts.length
373
- });
374
- return null;
375
- }
376
- }
377
- };
378
-
379
- //#endregion
380
- //#region src/features/embedding/embedding-service.ts
381
- var EmbeddingServiceImpl = class {
382
- constructor(provider, logger$1, config) {
383
- this.provider = provider;
384
- this.logger = logger$1;
385
- this.config = config;
386
- }
387
- async getEmbedding(text) {
388
- const result = await this.provider.generateEmbedding(text);
389
- if (!result) return null;
390
- return result.embedding;
391
- }
392
- async getEmbeddingsBatch(texts) {
393
- const result = await this.provider.generateBatchEmbeddings(texts);
394
- if (!result) return null;
395
- return result.embeddings;
396
- }
397
- getDimensions() {
398
- return this.config.dimensions || DEFAULT_EMBEDDING_DIMENSIONS;
399
- }
400
- };
401
-
402
- //#endregion
403
- //#region src/core/di/setup.ts
404
- const setupContainer = (config) => {
405
- const container = new DIContainer();
406
- container.singleton(TOKENS.CONFIG, config);
407
- const logger$1 = new Logger({
408
- enabled: true,
409
- prefix: "[payload-typesense]"
410
- });
411
- container.singleton(TOKENS.LOGGER, logger$1);
412
- const embeddingConfig = config.features.embedding;
413
- if (!embeddingConfig) throw new Error("Embedding configuration missing");
414
- let provider;
415
- if (embeddingConfig.type === "gemini") provider = new GeminiEmbeddingProvider(embeddingConfig, logger$1);
416
- else provider = new OpenAIEmbeddingProvider(embeddingConfig, logger$1);
417
- container.singleton(TOKENS.EMBEDDING_PROVIDER, provider);
418
- container.register(TOKENS.EMBEDDING_SERVICE, () => new EmbeddingServiceImpl(provider, logger$1, embeddingConfig));
419
- logger$1.debug("Embedding service registered", { provider: embeddingConfig });
420
- return container;
421
- };
422
-
423
- //#endregion
424
- //#region src/core/client/typesense-client.ts
425
- const createTypesenseClient = (typesenseConfig) => {
426
- return new Typesense.Client({
427
- apiKey: typesenseConfig.apiKey,
428
- connectionTimeoutSeconds: typesenseConfig.connectionTimeoutSeconds || 2,
429
- nodes: typesenseConfig.nodes
430
- });
431
- };
432
- const testTypesenseConnection = async (client) => {
433
- try {
434
- await client.health.retrieve();
435
- return true;
436
- } catch (_error) {
437
- return false;
438
- }
439
- };
440
-
441
223
  //#endregion
442
224
  //#region src/features/embedding/embeddings.ts
443
225
  let openaiClient = null;
@@ -1402,39 +1184,222 @@ async function createNewSession(payload, userId, conversationId, newUserMessage,
1402
1184
  }
1403
1185
 
1404
1186
  //#endregion
1405
- //#region src/features/rag/api/types.ts
1187
+ //#region src/features/rag/endpoints/chat/validators/request-validator.ts
1406
1188
  /**
1407
- * Helper to create a JSON response
1189
+ * JSON Response helper
1408
1190
  */
1409
- function jsonResponse(data, init) {
1191
+ const jsonResponse = (data, options) => {
1410
1192
  return new Response(JSON.stringify(data), {
1411
- ...init,
1412
- headers: {
1413
- "Content-Type": "application/json",
1414
- ...init?.headers
1415
- }
1193
+ headers: { "Content-Type": "application/json" },
1194
+ ...options
1416
1195
  });
1196
+ };
1197
+ /**
1198
+ * Validates chat request and extracts required data
1199
+ */
1200
+ async function validateChatRequest(request, config) {
1201
+ if (!await config.checkPermissions(request)) return {
1202
+ success: false,
1203
+ error: jsonResponse({ error: "No tienes permisos para acceder a esta sesión." }, { status: 403 })
1204
+ };
1205
+ if (!request.url || !request.user) return {
1206
+ success: false,
1207
+ error: jsonResponse({ error: "URL not found" }, { status: 400 })
1208
+ };
1209
+ const { id: userId, email } = request.user;
1210
+ const userEmail = email || "";
1211
+ const payload = await config.getPayload();
1212
+ const body = await request.json?.();
1213
+ if (!body) return {
1214
+ success: false,
1215
+ error: jsonResponse({ error: "Body not found" }, { status: 400 })
1216
+ };
1217
+ if (!body.message || typeof body.message !== "string" || body.message.trim() === "") return {
1218
+ success: false,
1219
+ error: jsonResponse({ error: "Se requiere un mensaje." }, { status: 400 })
1220
+ };
1221
+ return {
1222
+ success: true,
1223
+ userId,
1224
+ userEmail,
1225
+ payload,
1226
+ userMessage: body.message.trim(),
1227
+ body
1228
+ };
1417
1229
  }
1418
1230
 
1419
1231
  //#endregion
1420
- //#region src/features/rag/api/chat/handlers/embedding-handler.ts
1421
- /**
1422
- * Generates embedding and tracks usage
1423
- */
1424
- async function generateEmbeddingWithTracking(userMessage, config, spendingEntries) {
1425
- logger.debug("Generating embeddings for semantic search");
1426
- const embeddingConfig = config.embeddingConfig;
1427
- if (!embeddingConfig) throw new Error("Embedding configuration missing");
1428
- let provider;
1429
- const providerType = embeddingConfig.type;
1430
- const apiKey = embeddingConfig.apiKey;
1431
- const model = embeddingConfig.model;
1432
- const dimensions = embeddingConfig.dimensions;
1433
- const serviceLogger = new Logger({
1434
- enabled: true,
1435
- prefix: "[rag-embedding]"
1436
- });
1437
- if (providerType === "gemini") provider = new GeminiEmbeddingProvider({
1232
+ //#region src/features/embedding/services/embedding-service.ts
1233
+ var EmbeddingServiceImpl = class {
1234
+ constructor(provider, logger$1, config) {
1235
+ this.provider = provider;
1236
+ this.logger = logger$1;
1237
+ this.config = config;
1238
+ }
1239
+ async getEmbedding(text) {
1240
+ const result = await this.provider.generateEmbedding(text);
1241
+ if (!result) return null;
1242
+ return result.embedding;
1243
+ }
1244
+ async getEmbeddingsBatch(texts) {
1245
+ const result = await this.provider.generateBatchEmbeddings(texts);
1246
+ if (!result) return null;
1247
+ return result.embeddings;
1248
+ }
1249
+ getDimensions() {
1250
+ return this.config.dimensions || DEFAULT_EMBEDDING_DIMENSIONS;
1251
+ }
1252
+ };
1253
+
1254
+ //#endregion
1255
+ //#region src/features/embedding/providers/openai-provider.ts
1256
+ var OpenAIEmbeddingProvider = class {
1257
+ client;
1258
+ model;
1259
+ dimensions;
1260
+ constructor(config, logger$1) {
1261
+ this.logger = logger$1;
1262
+ if (!config.apiKey) throw new Error("OpenAI API key is required");
1263
+ this.client = new OpenAI({ apiKey: config.apiKey });
1264
+ this.model = config.model || DEFAULT_EMBEDDING_MODEL;
1265
+ this.dimensions = config.dimensions || DEFAULT_EMBEDDING_DIMENSIONS;
1266
+ }
1267
+ async generateEmbedding(text) {
1268
+ if (!text || text.trim().length < MIN_EMBEDDING_TEXT_LENGTH) return null;
1269
+ try {
1270
+ const response = await this.client.embeddings.create({
1271
+ model: this.model,
1272
+ input: text.trim(),
1273
+ dimensions: this.dimensions
1274
+ });
1275
+ const embedding = response.data[0]?.embedding;
1276
+ if (!embedding) return null;
1277
+ return {
1278
+ embedding,
1279
+ usage: {
1280
+ promptTokens: response.usage?.prompt_tokens || 0,
1281
+ totalTokens: response.usage?.total_tokens || 0
1282
+ }
1283
+ };
1284
+ } catch (error) {
1285
+ this.logger.error("OpenAI embedding generation failed", error, { model: this.model });
1286
+ return null;
1287
+ }
1288
+ }
1289
+ async generateBatchEmbeddings(texts) {
1290
+ const validTexts = texts.filter((t) => t && t.trim().length >= MIN_EMBEDDING_TEXT_LENGTH);
1291
+ if (validTexts.length === 0) return null;
1292
+ try {
1293
+ const response = await this.client.embeddings.create({
1294
+ model: this.model,
1295
+ input: validTexts.map((t) => t.trim()),
1296
+ dimensions: this.dimensions
1297
+ });
1298
+ return {
1299
+ embeddings: response.data.map((d) => d.embedding),
1300
+ usage: {
1301
+ promptTokens: response.usage?.prompt_tokens || 0,
1302
+ totalTokens: response.usage?.total_tokens || 0
1303
+ }
1304
+ };
1305
+ } catch (error) {
1306
+ this.logger.error("OpenAI batch embedding generation failed", error, {
1307
+ model: this.model,
1308
+ count: texts.length
1309
+ });
1310
+ return null;
1311
+ }
1312
+ }
1313
+ };
1314
+
1315
+ //#endregion
1316
+ //#region src/features/embedding/providers/gemini-provider.ts
1317
+ var GeminiEmbeddingProvider = class {
1318
+ client;
1319
+ model;
1320
+ constructor(config, logger$1) {
1321
+ this.logger = logger$1;
1322
+ if (!config.apiKey) throw new Error("Gemini API key is required");
1323
+ this.client = new GoogleGenerativeAI(config.apiKey);
1324
+ this.model = config.model || DEFAULT_GEMINI_EMBEDDING_MODEL;
1325
+ }
1326
+ async generateEmbedding(text) {
1327
+ if (!text || text.trim().length < MIN_EMBEDDING_TEXT_LENGTH) return null;
1328
+ try {
1329
+ const embedding = (await this.client.getGenerativeModel({ model: this.model }).embedContent({
1330
+ content: {
1331
+ role: "user",
1332
+ parts: [{ text: text.trim() }]
1333
+ },
1334
+ taskType: TaskType.RETRIEVAL_DOCUMENT
1335
+ })).embedding.values;
1336
+ const estimatedTokens = Math.ceil(text.length / 4);
1337
+ return {
1338
+ embedding,
1339
+ usage: {
1340
+ promptTokens: estimatedTokens,
1341
+ totalTokens: estimatedTokens
1342
+ }
1343
+ };
1344
+ } catch (error) {
1345
+ this.logger.error("Gemini embedding generation failed", error, { model: this.model });
1346
+ return null;
1347
+ }
1348
+ }
1349
+ async generateBatchEmbeddings(texts) {
1350
+ const validTexts = texts.filter((t) => t && t.trim().length >= MIN_EMBEDDING_TEXT_LENGTH);
1351
+ if (validTexts.length === 0) return null;
1352
+ try {
1353
+ const model = this.client.getGenerativeModel({ model: this.model });
1354
+ const embeddings = [];
1355
+ let totalTokens = 0;
1356
+ for (const text of validTexts) {
1357
+ const result = await model.embedContent({
1358
+ content: {
1359
+ role: "user",
1360
+ parts: [{ text: text.trim() }]
1361
+ },
1362
+ taskType: TaskType.RETRIEVAL_DOCUMENT
1363
+ });
1364
+ embeddings.push(result.embedding.values);
1365
+ totalTokens += Math.ceil(text.length / 4);
1366
+ }
1367
+ return {
1368
+ embeddings,
1369
+ usage: {
1370
+ promptTokens: totalTokens,
1371
+ totalTokens
1372
+ }
1373
+ };
1374
+ } catch (error) {
1375
+ this.logger.error("Gemini batch embedding generation failed", error, {
1376
+ model: this.model,
1377
+ count: texts.length
1378
+ });
1379
+ return null;
1380
+ }
1381
+ }
1382
+ };
1383
+
1384
+ //#endregion
1385
+ //#region src/features/rag/endpoints/chat/handlers/embedding-handler.ts
1386
+ /**
1387
+ * Generates embedding and tracks usage
1388
+ */
1389
+ async function generateEmbeddingWithTracking(userMessage, config, spendingEntries) {
1390
+ logger.debug("Generating embeddings for semantic search");
1391
+ const embeddingConfig = config.embeddingConfig;
1392
+ if (!embeddingConfig) throw new Error("Embedding configuration missing");
1393
+ let provider;
1394
+ const providerType = embeddingConfig.type;
1395
+ const apiKey = embeddingConfig.apiKey;
1396
+ const model = embeddingConfig.model;
1397
+ const dimensions = embeddingConfig.dimensions;
1398
+ const serviceLogger = new Logger({
1399
+ enabled: true,
1400
+ prefix: "[rag-embedding]"
1401
+ });
1402
+ if (providerType === "gemini") provider = new GeminiEmbeddingProvider({
1438
1403
  type: "gemini",
1439
1404
  apiKey,
1440
1405
  model,
@@ -1463,7 +1428,7 @@ async function generateEmbeddingWithTracking(userMessage, config, spendingEntrie
1463
1428
  }
1464
1429
 
1465
1430
  //#endregion
1466
- //#region src/features/rag/api/chat/handlers/session-handler.ts
1431
+ //#region src/features/rag/endpoints/chat/handlers/session-handler.ts
1467
1432
  /**
1468
1433
  * Saves chat session if function is provided
1469
1434
  */
@@ -1474,7 +1439,7 @@ async function saveChatSessionIfNeeded(config, payload, userId, conversationId,
1474
1439
  }
1475
1440
 
1476
1441
  //#endregion
1477
- //#region src/features/rag/api/chat/handlers/token-limit-handler.ts
1442
+ //#region src/features/rag/endpoints/chat/handlers/token-limit-handler.ts
1478
1443
  /**
1479
1444
  * Checks token limits before processing request
1480
1445
  */
@@ -1510,7 +1475,7 @@ async function checkTokenLimitsIfNeeded(config, payload, userId, userEmail, user
1510
1475
  }
1511
1476
 
1512
1477
  //#endregion
1513
- //#region src/features/rag/api/chat/handlers/usage-stats-handler.ts
1478
+ //#region src/features/rag/endpoints/chat/handlers/usage-stats-handler.ts
1514
1479
  /**
1515
1480
  * Calculates total usage from spending entries
1516
1481
  */
@@ -1546,43 +1511,7 @@ async function sendUsageStatsIfNeeded(config, payload, userId, totalTokens, tota
1546
1511
  }
1547
1512
 
1548
1513
  //#endregion
1549
- //#region src/features/rag/api/chat/validators/request-validator.ts
1550
- /**
1551
- * Validates chat request and extracts required data
1552
- */
1553
- async function validateChatRequest(request, config) {
1554
- if (!await config.checkPermissions(request)) return {
1555
- success: false,
1556
- error: jsonResponse({ error: "No tienes permisos para acceder a esta sesión." }, { status: 403 })
1557
- };
1558
- if (!request.url || !request.user) return {
1559
- success: false,
1560
- error: jsonResponse({ error: "URL not found" }, { status: 400 })
1561
- };
1562
- const { id: userId, email } = request.user;
1563
- const userEmail = email || "";
1564
- const payload = await config.getPayload();
1565
- const body = await request.json?.();
1566
- if (!body) return {
1567
- success: false,
1568
- error: jsonResponse({ error: "Body not found" }, { status: 400 })
1569
- };
1570
- if (!body.message || typeof body.message !== "string" || body.message.trim() === "") return {
1571
- success: false,
1572
- error: jsonResponse({ error: "Se requiere un mensaje." }, { status: 400 })
1573
- };
1574
- return {
1575
- success: true,
1576
- userId,
1577
- userEmail,
1578
- payload,
1579
- userMessage: body.message.trim(),
1580
- body
1581
- };
1582
- }
1583
-
1584
- //#endregion
1585
- //#region src/features/rag/api/chat/route.ts
1514
+ //#region src/features/rag/endpoints/chat/route.ts
1586
1515
  /**
1587
1516
  * Create a parameterizable POST handler for chat endpoint
1588
1517
  */
@@ -1869,7 +1798,7 @@ async function defaultHandleNonStreamingResponse(data, controller, encoder) {
1869
1798
  }
1870
1799
 
1871
1800
  //#endregion
1872
- //#region src/features/rag/api/chat/session/route.ts
1801
+ //#region src/features/rag/endpoints/chat/session/route.ts
1873
1802
  /**
1874
1803
  * Create a parameterizable GET handler for session endpoint
1875
1804
  *
@@ -1956,7 +1885,7 @@ function createSessionDELETEHandler(config) {
1956
1885
  }
1957
1886
 
1958
1887
  //#endregion
1959
- //#region src/features/rag/api/chunks/[id]/route.ts
1888
+ //#region src/features/rag/endpoints/chunks/[id]/route.ts
1960
1889
  /**
1961
1890
  * Create a parameterizable GET handler for chunks endpoint
1962
1891
  *
@@ -2001,7 +1930,7 @@ function createChunksGETHandler(config) {
2001
1930
  }
2002
1931
 
2003
1932
  //#endregion
2004
- //#region src/features/rag/api/chat/agents/route.ts
1933
+ //#region src/features/rag/endpoints/chat/agents/route.ts
2005
1934
  function createAgentsGETHandler(config) {
2006
1935
  return async function GET() {
2007
1936
  try {
@@ -2016,7 +1945,7 @@ function createAgentsGETHandler(config) {
2016
1945
  }
2017
1946
 
2018
1947
  //#endregion
2019
- //#region src/features/rag/create-rag-payload-handlers.ts
1948
+ //#region src/features/rag/endpoints.ts
2020
1949
  /**
2021
1950
  * Creates Payload handlers for RAG endpoints
2022
1951
  */
@@ -2083,7 +2012,7 @@ function createRAGPayloadHandlers(pluginOptions) {
2083
2012
  }
2084
2013
 
2085
2014
  //#endregion
2086
- //#region src/features/search/handlers/collections-handler.ts
2015
+ //#region src/features/search/endpoints/handlers/collections-handler.ts
2087
2016
  /**
2088
2017
  * Creates a handler for listing available search collections
2089
2018
  */
@@ -2095,18 +2024,7 @@ const createCollectionsHandler = (pluginOptions) => {
2095
2024
  const firstEnabledConfig = tableConfigs.find((config) => config.enabled);
2096
2025
  if (firstEnabledConfig) {
2097
2026
  let fields = [];
2098
- if (firstEnabledConfig.mode === "chunked") fields = [
2099
- ...firstEnabledConfig.fields || [],
2100
- {
2101
- name: "chunk_text",
2102
- index: true
2103
- },
2104
- {
2105
- name: "headers",
2106
- facet: true
2107
- }
2108
- ];
2109
- else fields = firstEnabledConfig.fields;
2027
+ fields = firstEnabledConfig.fields;
2110
2028
  const facetFields = fields.filter((f) => f.facet).map((f) => f.name);
2111
2029
  const searchFields = fields.filter((f) => f.index !== false).map((f) => f.name);
2112
2030
  collections.push({
@@ -2345,12 +2263,7 @@ const searchTraditionalCollection = async (typesenseClient, collectionName, conf
2345
2263
  if (options.searchFields) buildOptions.searchFields = options.searchFields;
2346
2264
  else if (config) {
2347
2265
  let fields = [];
2348
- if (config.mode === "chunked") fields = [...config.fields || [], {
2349
- name: "chunk_text",
2350
- index: true,
2351
- type: "string"
2352
- }];
2353
- else fields = config.fields;
2266
+ fields = config.fields;
2354
2267
  const searchFields = fields.filter((f) => f.index !== false && (f.type === "string" || f.type === "string[]")).map((f) => f.name);
2355
2268
  if (searchFields.length > 0) buildOptions.searchFields = searchFields;
2356
2269
  }
@@ -2374,7 +2287,7 @@ const searchTraditionalCollection = async (typesenseClient, collectionName, conf
2374
2287
  };
2375
2288
 
2376
2289
  //#endregion
2377
- //#region src/features/search/handlers/executors/traditional-multi-collection-search.ts
2290
+ //#region src/features/search/endpoints/handlers/executors/traditional-multi-collection-search.ts
2378
2291
  const performTraditionalMultiCollectionSearch = async (typesenseClient, enabledCollections, query, options) => {
2379
2292
  logger.info("Performing traditional multi-collection search", {
2380
2293
  query,
@@ -2390,12 +2303,7 @@ const performTraditionalMultiCollectionSearch = async (typesenseClient, enabledC
2390
2303
  ...searchFieldsOverride ? { searchFields: searchFieldsOverride } : (() => {
2391
2304
  if (!config) return {};
2392
2305
  let fields = [];
2393
- if (config.mode === "chunked") fields = [...config.fields || [], {
2394
- name: "chunk_text",
2395
- index: true,
2396
- type: "string"
2397
- }];
2398
- else fields = config.fields;
2306
+ fields = config.fields;
2399
2307
  const searchFields = fields.filter((f) => f.index !== false && (f.type === "string" || f.type === "string[]")).map((f) => f.name);
2400
2308
  return searchFields.length > 0 ? { searchFields } : {};
2401
2309
  })(),
@@ -2516,12 +2424,7 @@ const buildMultiCollectionVectorSearchParams = (searchVector, enabledCollections
2516
2424
  let searchFields;
2517
2425
  if (config) {
2518
2426
  let fields = [];
2519
- if (config.mode === "chunked") fields = [...config.fields || [], {
2520
- name: "chunk_text",
2521
- index: true,
2522
- type: "string"
2523
- }];
2524
- else fields = config.fields;
2427
+ fields = config.fields;
2525
2428
  const extracted = fields.filter((f) => f.index !== false && (f.type === "string" || f.type === "string[]")).map((f) => f.name);
2526
2429
  if (extracted.length > 0) searchFields = extracted;
2527
2430
  }
@@ -2615,7 +2518,7 @@ var SearchService = class {
2615
2518
  };
2616
2519
 
2617
2520
  //#endregion
2618
- //#region src/features/search/handlers/utils/document-transformer.ts
2521
+ //#region src/features/search/endpoints/handlers/utils/document-transformer.ts
2619
2522
  /**
2620
2523
  * Helper to resolve document type from collection name
2621
2524
  */
@@ -2644,7 +2547,7 @@ function transformToSimpleFormat(data) {
2644
2547
  }
2645
2548
 
2646
2549
  //#endregion
2647
- //#region src/features/search/handlers/utils/target-resolver.ts
2550
+ //#region src/features/search/endpoints/handlers/utils/target-resolver.ts
2648
2551
  var TargetCollectionResolver = class {
2649
2552
  allowedTableNames;
2650
2553
  constructor(pluginOptions) {
@@ -2690,7 +2593,7 @@ var TargetCollectionResolver = class {
2690
2593
  };
2691
2594
 
2692
2595
  //#endregion
2693
- //#region src/features/search/handlers/utils/config-mapper.ts
2596
+ //#region src/features/search/endpoints/handlers/utils/config-mapper.ts
2694
2597
  var SearchConfigMapper = class {
2695
2598
  constructor(pluginOptions) {
2696
2599
  this.pluginOptions = pluginOptions;
@@ -2781,7 +2684,7 @@ const extractCollectionName = (request) => {
2781
2684
  collectionNameStr = "";
2782
2685
  }
2783
2686
  } else {
2784
- const paramCollectionName = request.params?.collectionName;
2687
+ const paramCollectionName = request.routeParams?.collectionName;
2785
2688
  collectionName = String(paramCollectionName || "");
2786
2689
  collectionNameStr = collectionName;
2787
2690
  }
@@ -2829,7 +2732,7 @@ const extractSearchParams = (query) => {
2829
2732
  };
2830
2733
 
2831
2734
  //#endregion
2832
- //#region src/features/search/handlers/validators/search-request-validator.ts
2735
+ //#region src/features/search/endpoints/handlers/validators/search-request-validator.ts
2833
2736
  /**
2834
2737
  * Validates search request and returns parsed parameters
2835
2738
  */
@@ -2863,7 +2766,7 @@ function validateSearchRequest(request) {
2863
2766
  }
2864
2767
 
2865
2768
  //#endregion
2866
- //#region src/features/search/handlers/search-handler.ts
2769
+ //#region src/features/search/endpoints/handlers/search-handler.ts
2867
2770
  /**
2868
2771
  * Helper type guard to check if a result is a valid search response
2869
2772
  */
@@ -2911,7 +2814,7 @@ const createSearchHandler = (typesenseClient, pluginOptions) => {
2911
2814
  };
2912
2815
 
2913
2816
  //#endregion
2914
- //#region src/features/search/create-search-endpoints.ts
2817
+ //#region src/features/search/endpoints.ts
2915
2818
  const createSearchEndpoints = (typesenseClient, pluginOptions) => {
2916
2819
  return [
2917
2820
  {
@@ -2933,123 +2836,18 @@ const createSearchEndpoints = (typesenseClient, pluginOptions) => {
2933
2836
  };
2934
2837
 
2935
2838
  //#endregion
2936
- //#region src/shared/schema/collection-schemas.ts
2937
- /**
2938
- * Base fields that every collection should have
2939
- */
2940
- const getBaseFields = () => [
2941
- {
2942
- name: "id",
2943
- type: "string"
2944
- },
2945
- {
2946
- name: "slug",
2947
- type: "string"
2948
- },
2949
- {
2950
- name: "createdAt",
2951
- type: "int64"
2952
- },
2953
- {
2954
- name: "updatedAt",
2955
- type: "int64"
2956
- }
2957
- ];
2839
+ //#region src/shared/schema/field-mapper.ts
2958
2840
  /**
2959
- * Creates embedding field definition
2960
- * @param optional - Whether the embedding field is optional
2961
- * @param dimensions - Number of dimensions for the embedding vector (default: 1536)
2841
+ * Extracts a value from a document using dot notation path
2962
2842
  */
2963
- const getEmbeddingField = (optional = true, dimensions = DEFAULT_EMBEDDING_DIMENSIONS) => ({
2964
- name: "embedding",
2965
- type: "float[]",
2966
- num_dim: dimensions,
2967
- ...optional && { optional: true }
2968
- });
2843
+ const getValueByPath = (obj, path) => {
2844
+ if (!obj || typeof obj !== "object") return void 0;
2845
+ return path.split(".").reduce((acc, part) => {
2846
+ if (acc && typeof acc === "object" && part in acc) return acc[part];
2847
+ }, obj);
2848
+ };
2969
2849
  /**
2970
- * Maps FieldMapping to TypesenseFieldSchema
2971
- */
2972
- const mapFieldMappingsToSchema = (fields) => {
2973
- return fields.map((field) => ({
2974
- name: field.name,
2975
- type: field.type === "auto" ? "string" : field.type,
2976
- facet: field.facet,
2977
- index: field.index,
2978
- optional: field.optional
2979
- }));
2980
- };
2981
- /**
2982
- * Gets chunk-specific fields for chunk collections
2983
- */
2984
- const getChunkFields = () => [
2985
- {
2986
- name: "parent_doc_id",
2987
- type: "string"
2988
- },
2989
- {
2990
- name: "chunk_index",
2991
- type: "int32"
2992
- },
2993
- {
2994
- name: "chunk_text",
2995
- type: "string"
2996
- },
2997
- {
2998
- name: "is_chunk",
2999
- type: "bool"
3000
- },
3001
- {
3002
- name: "headers",
3003
- type: "string[]",
3004
- facet: true,
3005
- optional: true
3006
- }
3007
- ];
3008
- /**
3009
- * Creates a complete schema for a chunk collection
3010
- */
3011
- const getChunkCollectionSchema = (collectionSlug, tableConfig, embeddingDimensions = DEFAULT_EMBEDDING_DIMENSIONS) => {
3012
- const fields = tableConfig.fields ? mapFieldMappingsToSchema(tableConfig.fields) : [];
3013
- const userFieldNames = new Set([...fields.map((f) => f.name), ...getChunkFields().map((f) => f.name)]);
3014
- return {
3015
- name: collectionSlug,
3016
- fields: [
3017
- ...getBaseFields().filter((f) => !userFieldNames.has(f.name)),
3018
- ...getChunkFields(),
3019
- ...fields,
3020
- getEmbeddingField(false, embeddingDimensions)
3021
- ]
3022
- };
3023
- };
3024
- /**
3025
- * Creates a complete schema for a full document collection
3026
- */
3027
- const getFullDocumentCollectionSchema = (collectionSlug, tableConfig, embeddingDimensions = DEFAULT_EMBEDDING_DIMENSIONS) => {
3028
- const mappedFields = mapFieldMappingsToSchema(tableConfig.fields);
3029
- const userFieldNames = new Set(mappedFields.map((f) => f.name));
3030
- return {
3031
- name: collectionSlug,
3032
- fields: [
3033
- ...getBaseFields().filter((f) => !userFieldNames.has(f.name)),
3034
- ...mappedFields,
3035
- getEmbeddingField(true, embeddingDimensions)
3036
- ]
3037
- };
3038
- };
3039
-
3040
- //#endregion
3041
- //#region src/shared/schema/field-mapper.ts
3042
- /**
3043
- * Extracts a value from a document using dot notation path
3044
- */
3045
- const getValueByPath = (obj, path) => {
3046
- if (!obj || typeof obj !== "object") return void 0;
3047
- return path.split(".").reduce((acc, part) => {
3048
- if (acc && typeof acc === "object" && part in acc) return acc[part];
3049
- }, obj);
3050
- };
3051
- /**
3052
- * Maps a Payload document to a Typesense document based on field configuration
2850
+ * Maps a Payload document to a Typesense document based on field configuration
3053
2851
  */
3054
2852
  const mapPayloadDocumentToTypesense = async (doc, fields) => {
3055
2853
  const result = {};
@@ -3285,8 +3083,37 @@ const extractHeaderMetadata = (chunkText$1) => {
3285
3083
  };
3286
3084
 
3287
3085
  //#endregion
3288
- //#region src/features/sync/strategies/chunked-syncer.ts
3289
- var ChunkedSyncer = class {
3086
+ //#region src/features/sync/services/document-syncer.ts
3087
+ /**
3088
+ * Syncs a Payload document to Typesense
3089
+ * Uses Strategy pattern to handle both chunked and full document approaches
3090
+ */
3091
+ const syncDocumentToTypesense = async (typesenseClient, collectionSlug, doc, operation, tableConfig, embeddingService) => {
3092
+ try {
3093
+ const tableName = tableConfig.tableName || getTypesenseCollectionName(collectionSlug, tableConfig);
3094
+ logger.debug("Syncing document to Typesense", {
3095
+ documentId: doc.id,
3096
+ collection: collectionSlug,
3097
+ tableName,
3098
+ operation
3099
+ });
3100
+ await new DocumentSyncer(typesenseClient, collectionSlug, tableName, tableConfig, embeddingService).sync(doc, operation);
3101
+ logger.info("Document synced successfully to Typesense", {
3102
+ documentId: doc.id,
3103
+ collection: collectionSlug,
3104
+ operation
3105
+ });
3106
+ } catch (error) {
3107
+ const isValidationError = (error instanceof Error ? error.message : String(error)).toLowerCase().includes("validation");
3108
+ logger.error(`Failed to sync document to Typesense`, error, {
3109
+ documentId: doc.id,
3110
+ collection: collectionSlug,
3111
+ operation,
3112
+ isValidationError
3113
+ });
3114
+ }
3115
+ };
3116
+ var DocumentSyncer = class {
3290
3117
  constructor(client, collectionSlug, tableName, config, embeddingService) {
3291
3118
  this.client = client;
3292
3119
  this.collectionSlug = collectionSlug;
@@ -3295,7 +3122,28 @@ var ChunkedSyncer = class {
3295
3122
  this.embeddingService = embeddingService;
3296
3123
  }
3297
3124
  async sync(doc, operation) {
3298
- logger.debug(`Syncing document ${doc.id} to table ${this.tableName} (Chunked Mode)`);
3125
+ logger.debug(`Syncing document ${doc.id} to table ${this.tableName}`);
3126
+ if (this.config.embedding?.chunking) await this.syncChunked(doc, operation);
3127
+ else await this.syncDocument(doc, operation);
3128
+ }
3129
+ async syncDocument(doc, operation) {
3130
+ const typesenseDoc = await mapPayloadDocumentToTypesense(doc, this.config.fields);
3131
+ typesenseDoc.id = String(doc.id);
3132
+ typesenseDoc.slug = doc.slug || "";
3133
+ typesenseDoc.createdAt = new Date(doc.createdAt).getTime();
3134
+ typesenseDoc.updatedAt = new Date(doc.updatedAt).getTime();
3135
+ if (doc.publishedAt) typesenseDoc.publishedAt = new Date(doc.publishedAt).getTime();
3136
+ if (this.config.embedding?.fields && this.embeddingService) {
3137
+ const sourceText = await this.extractSourceText(doc);
3138
+ if (sourceText) {
3139
+ const embedding = await this.embeddingService.getEmbedding(sourceText);
3140
+ if (embedding) typesenseDoc.embedding = embedding;
3141
+ }
3142
+ }
3143
+ await this.client.collections(this.tableName).documents().upsert(typesenseDoc);
3144
+ logger.info(`Synced document ${doc.id} to ${this.tableName}`);
3145
+ }
3146
+ async syncChunked(doc, operation) {
3299
3147
  const sourceText = await this.extractSourceText(doc);
3300
3148
  if (!sourceText) {
3301
3149
  logger.warn(`No source text found for document ${doc.id}`);
@@ -3308,7 +3156,12 @@ var ChunkedSyncer = class {
3308
3156
  if (operation === "update") await this.client.collections(this.tableName).documents().delete({ filter_by: `parent_doc_id:${doc.id}` });
3309
3157
  for (const chunk of chunks) {
3310
3158
  const headers = buildHeaderHierarchy(chunk.metadata);
3311
- const formattedText = formatChunkWithHeaders(chunk.text, headers);
3159
+ let formattedText = formatChunkWithHeaders(chunk.text, headers);
3160
+ if (this.config.embedding?.chunking?.interceptResult) formattedText = this.config.embedding.chunking.interceptResult({
3161
+ ...chunk,
3162
+ headers,
3163
+ formattedText
3164
+ }, doc);
3312
3165
  let embedding = [];
3313
3166
  if (this.embeddingService) {
3314
3167
  const result = await this.embeddingService.getEmbedding(formattedText);
@@ -3330,68 +3183,13 @@ var ChunkedSyncer = class {
3330
3183
  }
3331
3184
  logger.info(`Synced ${chunks.length} chunks for document ${doc.id} to ${this.tableName}`);
3332
3185
  }
3333
- async extractSourceText(doc) {
3334
- const textParts = [];
3335
- for (const sourceField of this.config.sourceFields) {
3336
- const fieldName = typeof sourceField === "string" ? sourceField : sourceField.field;
3337
- const transform = typeof sourceField === "string" ? void 0 : sourceField.transform;
3338
- const val = doc[fieldName];
3339
- if (transform) {
3340
- let transformedVal = await transform(val);
3341
- textParts.push(String(transformedVal || ""));
3342
- } else if (typeof val === "object" && val !== null && "root" in val) {
3343
- let transformedVal = JSON.stringify(val);
3344
- textParts.push(String(transformedVal || ""));
3345
- }
3346
- }
3347
- return textParts.join("\n\n");
3348
- }
3349
- async generateChunks(text) {
3350
- const { strategy, size, overlap } = this.config.chunking;
3351
- const options = {
3352
- maxChunkSize: size,
3353
- overlap
3354
- };
3355
- if (strategy === "markdown") return await chunkMarkdown(text, options);
3356
- else return await chunkText(text, options);
3357
- }
3358
- };
3359
-
3360
- //#endregion
3361
- //#region src/features/sync/strategies/document-syncer.ts
3362
- var DocumentSyncer = class {
3363
- constructor(client, collectionSlug, tableName, config, embeddingService) {
3364
- this.client = client;
3365
- this.collectionSlug = collectionSlug;
3366
- this.tableName = tableName;
3367
- this.config = config;
3368
- this.embeddingService = embeddingService;
3369
- }
3370
- async sync(doc, operation) {
3371
- logger.debug(`Syncing document ${doc.id} to table ${this.tableName} (Document Mode)`);
3372
- const typesenseDoc = await mapPayloadDocumentToTypesense(doc, this.config.fields);
3373
- typesenseDoc.id = String(doc.id);
3374
- typesenseDoc.slug = doc.slug || "";
3375
- typesenseDoc.createdAt = new Date(doc.createdAt).getTime();
3376
- typesenseDoc.updatedAt = new Date(doc.updatedAt).getTime();
3377
- if (doc.publishedAt) typesenseDoc.publishedAt = new Date(doc.publishedAt).getTime();
3378
- if (this.config.sourceFields && this.embeddingService) {
3379
- const sourceText = await this.extractSourceText(doc);
3380
- if (sourceText) {
3381
- const embedding = await this.embeddingService.getEmbedding(sourceText);
3382
- if (embedding) typesenseDoc.embedding = embedding;
3383
- }
3384
- }
3385
- await this.client.collections(this.tableName).documents().upsert(typesenseDoc);
3386
- logger.info(`Synced document ${doc.id} to ${this.tableName}`);
3387
- }
3388
3186
  /**
3389
3187
  * Extract and transform source fields for embedding generation
3390
3188
  */
3391
3189
  async extractSourceText(doc) {
3392
- if (!this.config.sourceFields) return "";
3190
+ if (!this.config.embedding?.fields) return "";
3393
3191
  const textParts = [];
3394
- for (const sourceField of this.config.sourceFields) {
3192
+ for (const sourceField of this.config.embedding.fields) {
3395
3193
  let fieldName;
3396
3194
  let transform;
3397
3195
  if (typeof sourceField === "string") fieldName = sourceField;
@@ -3406,44 +3204,20 @@ var DocumentSyncer = class {
3406
3204
  }
3407
3205
  return textParts.join("\n\n");
3408
3206
  }
3409
- };
3410
-
3411
- //#endregion
3412
- //#region src/features/sync/document-sync.ts
3413
- /**
3414
- * Syncs a Payload document to Typesense
3415
- * Uses Strategy pattern to handle both chunked and full document approaches
3416
- */
3417
- const syncDocumentToTypesense = async (typesenseClient, collectionSlug, doc, operation, tableConfig, embeddingService) => {
3418
- try {
3419
- const tableName = tableConfig.tableName || getTypesenseCollectionName(collectionSlug, tableConfig);
3420
- logger.debug("Syncing document to Typesense", {
3421
- documentId: doc.id,
3422
- collection: collectionSlug,
3423
- tableName,
3424
- operation,
3425
- mode: tableConfig.mode
3426
- });
3427
- if (tableConfig.mode === "chunked") await new ChunkedSyncer(typesenseClient, collectionSlug, tableName, tableConfig, embeddingService).sync(doc, operation);
3428
- else await new DocumentSyncer(typesenseClient, collectionSlug, tableName, tableConfig, embeddingService).sync(doc, operation);
3429
- logger.info("Document synced successfully to Typesense", {
3430
- documentId: doc.id,
3431
- collection: collectionSlug,
3432
- operation
3433
- });
3434
- } catch (error) {
3435
- const isValidationError = (error instanceof Error ? error.message : String(error)).toLowerCase().includes("validation");
3436
- logger.error(`Failed to sync document to Typesense`, error, {
3437
- documentId: doc.id,
3438
- collection: collectionSlug,
3439
- operation,
3440
- isValidationError
3441
- });
3207
+ async generateChunks(text) {
3208
+ if (!this.config.embedding?.chunking) return [];
3209
+ const { strategy, size, overlap } = this.config.embedding.chunking;
3210
+ const options = {
3211
+ maxChunkSize: size,
3212
+ overlap
3213
+ };
3214
+ if (strategy === "markdown") return await chunkMarkdown(text, options);
3215
+ else return await chunkText(text, options);
3442
3216
  }
3443
3217
  };
3444
3218
 
3445
3219
  //#endregion
3446
- //#region src/features/sync/document-delete.ts
3220
+ //#region src/features/sync/services/document-delete.ts
3447
3221
  /**
3448
3222
  * Deletes a document from Typesense
3449
3223
  * Handles both direct document deletion and chunk deletion
@@ -3494,7 +3268,148 @@ const deleteDocumentFromTypesense = async (typesenseClient, collectionSlug, docI
3494
3268
  };
3495
3269
 
3496
3270
  //#endregion
3497
- //#region src/features/sync/schema-manager.ts
3271
+ //#region src/features/sync/hooks.ts
3272
+ /**
3273
+ * Applies sync hooks to Payload collections
3274
+ */
3275
+ const applySyncHooks = (config, pluginOptions, typesenseClient, embeddingService) => {
3276
+ if (!pluginOptions.features.sync?.enabled || pluginOptions.features.sync.autoSync === false || !pluginOptions.collections) return config;
3277
+ return (config || []).map((collection) => {
3278
+ const tableConfigs = pluginOptions.collections?.[collection.slug];
3279
+ if (tableConfigs && Array.isArray(tableConfigs) && tableConfigs.some((tableConfig) => tableConfig.enabled)) {
3280
+ logger.debug("Registering sync hooks for collection", {
3281
+ collection: collection.slug,
3282
+ tableCount: tableConfigs?.length || 0
3283
+ });
3284
+ return {
3285
+ ...collection,
3286
+ hooks: {
3287
+ ...collection.hooks,
3288
+ afterChange: [...collection.hooks?.afterChange || [], async ({ doc, operation, req: _req }) => {
3289
+ if (tableConfigs && Array.isArray(tableConfigs)) {
3290
+ for (const tableConfig of tableConfigs) if (tableConfig.enabled) await syncDocumentToTypesense(typesenseClient, collection.slug, doc, operation, tableConfig, embeddingService);
3291
+ }
3292
+ }],
3293
+ afterDelete: [...collection.hooks?.afterDelete || [], async ({ doc, req: _req }) => {
3294
+ if (tableConfigs && Array.isArray(tableConfigs)) {
3295
+ for (const tableConfig of tableConfigs) if (tableConfig.enabled) await deleteDocumentFromTypesense(typesenseClient, collection.slug, doc.id, tableConfig);
3296
+ }
3297
+ }]
3298
+ }
3299
+ };
3300
+ }
3301
+ return collection;
3302
+ });
3303
+ };
3304
+
3305
+ //#endregion
3306
+ //#region src/shared/schema/collection-schemas.ts
3307
+ /**
3308
+ * Base fields that every collection should have
3309
+ */
3310
+ const getBaseFields = () => [
3311
+ {
3312
+ name: "id",
3313
+ type: "string"
3314
+ },
3315
+ {
3316
+ name: "slug",
3317
+ type: "string"
3318
+ },
3319
+ {
3320
+ name: "createdAt",
3321
+ type: "int64"
3322
+ },
3323
+ {
3324
+ name: "updatedAt",
3325
+ type: "int64"
3326
+ }
3327
+ ];
3328
+ /**
3329
+ * Creates embedding field definition
3330
+ * @param optional - Whether the embedding field is optional
3331
+ * @param dimensions - Number of dimensions for the embedding vector (default: 1536)
3332
+ */
3333
+ const getEmbeddingField = (optional = true, dimensions = DEFAULT_EMBEDDING_DIMENSIONS) => ({
3334
+ name: "embedding",
3335
+ type: "float[]",
3336
+ num_dim: dimensions,
3337
+ ...optional && { optional: true }
3338
+ });
3339
+ /**
3340
+ * Maps FieldMapping to TypesenseFieldSchema
3341
+ */
3342
+ const mapFieldMappingsToSchema = (fields) => {
3343
+ return fields.map((field) => ({
3344
+ name: field.name,
3345
+ type: field.type === "auto" ? "string" : field.type,
3346
+ facet: field.facet,
3347
+ index: field.index,
3348
+ optional: field.optional
3349
+ }));
3350
+ };
3351
+ /**
3352
+ * Gets chunk-specific fields for chunk collections
3353
+ */
3354
+ const getChunkFields = () => [
3355
+ {
3356
+ name: "parent_doc_id",
3357
+ type: "string",
3358
+ facet: true
3359
+ },
3360
+ {
3361
+ name: "chunk_index",
3362
+ type: "int32"
3363
+ },
3364
+ {
3365
+ name: "chunk_text",
3366
+ type: "string"
3367
+ },
3368
+ {
3369
+ name: "is_chunk",
3370
+ type: "bool"
3371
+ },
3372
+ {
3373
+ name: "headers",
3374
+ type: "string[]",
3375
+ facet: true,
3376
+ optional: true
3377
+ }
3378
+ ];
3379
+ /**
3380
+ * Creates a complete schema for a chunk collection
3381
+ */
3382
+ const getChunkCollectionSchema = (collectionSlug, tableConfig, embeddingDimensions = DEFAULT_EMBEDDING_DIMENSIONS) => {
3383
+ const fields = tableConfig.fields ? mapFieldMappingsToSchema(tableConfig.fields) : [];
3384
+ const userFieldNames = new Set([...fields.map((f) => f.name), ...getChunkFields().map((f) => f.name)]);
3385
+ return {
3386
+ name: collectionSlug,
3387
+ fields: [
3388
+ ...getBaseFields().filter((f) => !userFieldNames.has(f.name)),
3389
+ ...getChunkFields(),
3390
+ ...fields,
3391
+ getEmbeddingField(false, embeddingDimensions)
3392
+ ]
3393
+ };
3394
+ };
3395
+ /**
3396
+ * Creates a complete schema for a full document collection
3397
+ */
3398
+ const getFullDocumentCollectionSchema = (collectionSlug, tableConfig, embeddingDimensions = DEFAULT_EMBEDDING_DIMENSIONS) => {
3399
+ const mappedFields = mapFieldMappingsToSchema(tableConfig.fields);
3400
+ const userFieldNames = new Set(mappedFields.map((f) => f.name));
3401
+ return {
3402
+ name: collectionSlug,
3403
+ fields: [
3404
+ ...getBaseFields().filter((f) => !userFieldNames.has(f.name)),
3405
+ ...mappedFields,
3406
+ getEmbeddingField(true, embeddingDimensions)
3407
+ ]
3408
+ };
3409
+ };
3410
+
3411
+ //#endregion
3412
+ //#region src/features/sync/services/schema-manager.ts
3498
3413
  var SchemaManager = class {
3499
3414
  constructor(client, config) {
3500
3415
  this.client = client;
@@ -3522,7 +3437,7 @@ var SchemaManager = class {
3522
3437
  async syncTable(collectionSlug, tableConfig, embeddingDimensions) {
3523
3438
  const tableName = getTypesenseCollectionName(collectionSlug, tableConfig);
3524
3439
  let targetSchema;
3525
- if (tableConfig.mode === "chunked") targetSchema = getChunkCollectionSchema(tableName, tableConfig, embeddingDimensions);
3440
+ if (tableConfig.embedding?.chunking) targetSchema = getChunkCollectionSchema(tableName, tableConfig, embeddingDimensions);
3526
3441
  else targetSchema = getFullDocumentCollectionSchema(tableName, tableConfig, embeddingDimensions);
3527
3442
  try {
3528
3443
  const collection = await this.client.collections(tableName).retrieve();
@@ -3557,7 +3472,7 @@ var SchemaManager = class {
3557
3472
  };
3558
3473
 
3559
3474
  //#endregion
3560
- //#region src/features/rag/agent-manager.ts
3475
+ //#region src/features/rag/services/agent-manager.ts
3561
3476
  var AgentManager = class {
3562
3477
  constructor(client, config) {
3563
3478
  this.client = client;
@@ -3657,10 +3572,17 @@ var AgentManager = class {
3657
3572
  * @returns Payload config modifier function
3658
3573
  */
3659
3574
  const typesenseSearch = (pluginOptions) => (config) => {
3660
- const container = setupContainer(pluginOptions);
3661
3575
  const typesenseClient = createTypesenseClient(pluginOptions.typesense);
3576
+ const logger$1 = new Logger({
3577
+ enabled: true,
3578
+ prefix: "[payload-typesense]"
3579
+ });
3662
3580
  let embeddingService;
3663
- if (container.has(TOKENS.EMBEDDING_SERVICE)) embeddingService = container.resolve(TOKENS.EMBEDDING_SERVICE);
3581
+ const embeddingConfig = pluginOptions.features.embedding;
3582
+ if (embeddingConfig) {
3583
+ embeddingService = new EmbeddingServiceImpl(embeddingConfig.type === "gemini" ? new GeminiEmbeddingProvider(embeddingConfig, logger$1) : new OpenAIEmbeddingProvider(embeddingConfig, logger$1), logger$1, embeddingConfig);
3584
+ logger$1.debug("Embedding service initialized", { provider: embeddingConfig.type });
3585
+ }
3664
3586
  const searchEndpoints = createSearchEndpoints(typesenseClient, pluginOptions);
3665
3587
  const ragEndpoints = pluginOptions.features.rag?.enabled ? createRAGPayloadHandlers(pluginOptions) : [];
3666
3588
  config.endpoints = [
@@ -3668,53 +3590,48 @@ const typesenseSearch = (pluginOptions) => (config) => {
3668
3590
  ...searchEndpoints,
3669
3591
  ...ragEndpoints
3670
3592
  ];
3671
- logger.debug("Search and RAG endpoints registered", {
3593
+ logger$1.debug("Search and RAG endpoints registered", {
3672
3594
  searchEndpointsCount: searchEndpoints.length,
3673
3595
  ragEndpointsCount: ragEndpoints.length
3674
3596
  });
3675
- if (pluginOptions.features.sync?.enabled && pluginOptions.features.sync.autoSync !== false && pluginOptions.collections) config.collections = (config.collections || []).map((collection) => {
3676
- const tableConfigs = pluginOptions.collections?.[collection.slug];
3677
- if (tableConfigs && Array.isArray(tableConfigs) && tableConfigs.some((tableConfig) => tableConfig.enabled)) {
3678
- logger.debug("Registering sync hooks for collection", {
3679
- collection: collection.slug,
3680
- tableCount: tableConfigs?.length || 0
3681
- });
3682
- return {
3683
- ...collection,
3684
- hooks: {
3685
- ...collection.hooks,
3686
- afterChange: [...collection.hooks?.afterChange || [], async ({ doc, operation, req: _req }) => {
3687
- if (tableConfigs && Array.isArray(tableConfigs)) {
3688
- for (const tableConfig of tableConfigs) if (tableConfig.enabled) await syncDocumentToTypesense(typesenseClient, collection.slug, doc, operation, tableConfig, embeddingService);
3689
- }
3690
- }],
3691
- afterDelete: [...collection.hooks?.afterDelete || [], async ({ doc, req: _req }) => {
3692
- if (tableConfigs && Array.isArray(tableConfigs)) {
3693
- for (const tableConfig of tableConfigs) if (tableConfig.enabled) await deleteDocumentFromTypesense(typesenseClient, collection.slug, doc.id, tableConfig);
3694
- }
3695
- }]
3696
- }
3697
- };
3698
- }
3699
- return collection;
3700
- });
3597
+ if (config.collections) config.collections = applySyncHooks(config.collections, pluginOptions, typesenseClient, embeddingService);
3701
3598
  const incomingOnInit = config.onInit;
3702
3599
  config.onInit = async (payload) => {
3703
3600
  if (incomingOnInit) await incomingOnInit(payload);
3704
3601
  try {
3705
- logger.info("Initializing Typesense collections...");
3602
+ logger$1.info("Initializing Typesense collections...");
3706
3603
  await new SchemaManager(typesenseClient, pluginOptions).syncCollections();
3707
3604
  if (pluginOptions.features.rag?.enabled) {
3708
- logger.info("Initializing RAG agents...");
3605
+ logger$1.info("Initializing RAG agents...");
3709
3606
  await new AgentManager(typesenseClient, pluginOptions).syncAgents();
3710
3607
  }
3711
3608
  } catch (error) {
3712
- logger.error("Error initializing Typesense resources", error);
3609
+ logger$1.error("Error initializing Typesense resources", error);
3713
3610
  }
3714
3611
  };
3715
3612
  return config;
3716
3613
  };
3717
3614
 
3718
3615
  //#endregion
3719
- export { CHUNK_HEADER_SEPARATOR, DEFAULT_CACHE_TTL_MS, DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, DEFAULT_HYBRID_SEARCH_ALPHA, DEFAULT_RAG_CONTEXT_LIMIT, DEFAULT_RAG_LLM_MODEL, DEFAULT_RAG_MAX_TOKENS, DEFAULT_SEARCH_LIMIT, DEFAULT_SESSION_TTL_SEC, ErrorCodes, buildContextText, buildConversationalUrl, buildHybridSearchParams, buildMultiSearchRequestBody, buildMultiSearchRequests, closeSession, configureLogger, createLogger, createSSEForwardStream, createTypesenseClient, ensureConversationCollection, executeRAGSearch, extractContentOnly, extractHeaderMetadata, extractSourcesFromResults, fetchChunkById, formatChunkWithHeaders, formatSSEEvent, generateEmbedding, generateEmbeddingWithUsage, generateEmbeddingsBatchWithUsage, getActiveSession, getDefaultRAGConfig, getSessionByConversationId, jsonResponse, logger, mergeRAGConfigWithDefaults, parseChunkText, parseConversationEvent, processConversationStream, saveChatSession, sendSSEEvent, testTypesenseConnection, typesenseSearch };
3616
+ //#region src/core/utils/transforms.ts
3617
+ /**
3618
+ * Transforms Lexical editor state to Markdown
3619
+ * @param value - The serialized editor state
3620
+ * @param config - Optional Payload config. If provided, it will be used to generate the editor config.
3621
+ */
3622
+ const transformLexicalToMarkdown = async (value, config) => {
3623
+ if (!value) return "";
3624
+ try {
3625
+ return await convertLexicalToMarkdown({
3626
+ data: value,
3627
+ editorConfig: await editorConfigFactory.default({ config })
3628
+ });
3629
+ } catch (error) {
3630
+ console.error("Error transforming lexical to markdown", error);
3631
+ return "";
3632
+ }
3633
+ };
3634
+
3635
+ //#endregion
3636
+ export { CHUNK_HEADER_SEPARATOR, DEFAULT_CACHE_TTL_MS, DEFAULT_CHUNK_OVERLAP, DEFAULT_CHUNK_SIZE, DEFAULT_EMBEDDING_DIMENSIONS, DEFAULT_EMBEDDING_MODEL, DEFAULT_HYBRID_SEARCH_ALPHA, DEFAULT_RAG_CONTEXT_LIMIT, DEFAULT_RAG_LLM_MODEL, DEFAULT_RAG_MAX_TOKENS, DEFAULT_SEARCH_LIMIT, DEFAULT_SESSION_TTL_SEC, ErrorCodes, buildContextText, buildConversationalUrl, buildHybridSearchParams, buildMultiSearchRequestBody, buildMultiSearchRequests, closeSession, configureLogger, createLogger, createSSEForwardStream, createTypesenseClient, deleteDocumentFromTypesense, ensureConversationCollection, executeRAGSearch, extractContentOnly, extractHeaderMetadata, extractSourcesFromResults, fetchChunkById, formatChunkWithHeaders, formatSSEEvent, generateEmbedding, generateEmbeddingWithUsage, generateEmbeddingsBatchWithUsage, getActiveSession, getDefaultRAGConfig, getSessionByConversationId, jsonResponse, logger, mergeRAGConfigWithDefaults, parseChunkText, parseConversationEvent, processConversationStream, saveChatSession, sendSSEEvent, testTypesenseConnection, transformLexicalToMarkdown, typesenseSearch };
3720
3637
  //# sourceMappingURL=index.mjs.map