@opengeni/documents 0.2.7 → 0.2.9

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.js CHANGED
@@ -56,8 +56,6 @@ var RecursiveTextChunker = class {
56
56
  throw new Error("document chunk overlap must be smaller than chunk size");
57
57
  }
58
58
  }
59
- maxChars;
60
- overlapChars;
61
59
  chunk(parsed, file) {
62
60
  return chunkText(parsed.text, this.maxChars, this.overlapChars).map((text, index) => ({
63
61
  text,
@@ -127,8 +125,6 @@ var DeterministicEmbeddingProvider = class {
127
125
  this.dimensions = dimensions;
128
126
  this.model = model;
129
127
  }
130
- dimensions;
131
- model;
132
128
  async embedMany(texts) {
133
129
  return texts.map((text) => deterministicEmbedding(text, this.dimensions));
134
130
  }
@@ -190,16 +186,20 @@ function azureOpenAIDefaultQuery(settings, baseURL) {
190
186
  return { "api-version": settings.azureOpenaiApiVersion };
191
187
  }
192
188
  async function createDocumentBase(db, input) {
193
- return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
194
- const [row] = await scopedDb.insert(schema.documentBases).values({
195
- accountId: input.accountId,
196
- workspaceId: input.workspaceId,
197
- name: input.name.trim(),
198
- description: input.description?.trim() || null
199
- }).returning();
200
- if (!row) throw new Error("Failed to create document base");
201
- return mapDocumentBase(row);
202
- });
189
+ return await withRlsContext(
190
+ db,
191
+ { accountId: input.accountId, workspaceId: input.workspaceId },
192
+ async (scopedDb) => {
193
+ const [row] = await scopedDb.insert(schema.documentBases).values({
194
+ accountId: input.accountId,
195
+ workspaceId: input.workspaceId,
196
+ name: input.name.trim(),
197
+ description: input.description?.trim() || null
198
+ }).returning();
199
+ if (!row) throw new Error("Failed to create document base");
200
+ return mapDocumentBase(row);
201
+ }
202
+ );
203
203
  }
204
204
  async function listDocumentBases(db, workspaceId) {
205
205
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
@@ -209,77 +209,112 @@ async function listDocumentBases(db, workspaceId) {
209
209
  }
210
210
  async function getDocumentBase(db, workspaceId, baseId) {
211
211
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
212
- const [row] = await scopedDb.select().from(schema.documentBases).where(and(eq(schema.documentBases.workspaceId, workspaceId), eq(schema.documentBases.id, baseId))).limit(1);
212
+ const [row] = await scopedDb.select().from(schema.documentBases).where(
213
+ and(eq(schema.documentBases.workspaceId, workspaceId), eq(schema.documentBases.id, baseId))
214
+ ).limit(1);
213
215
  return row ? mapDocumentBase(row) : null;
214
216
  });
215
217
  }
216
218
  async function addDocumentToBase(db, input) {
217
- return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
218
- const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);
219
- if (!base) throw new Error(`Document base not found: ${input.baseId}`);
220
- const file = await requireReadyFile(scopedDb, input.workspaceId, input.fileId);
221
- const now = /* @__PURE__ */ new Date();
222
- const [existing] = await scopedDb.select().from(schema.documents).where(and(eq(schema.documents.workspaceId, input.workspaceId), eq(schema.documents.baseId, input.baseId), eq(schema.documents.fileId, input.fileId))).limit(1);
223
- if (existing) {
224
- const [updated] = await scopedDb.update(schema.documents).set({
225
- title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? existing.title,
226
- ...input.sourceKind !== void 0 ? { sourceKind: input.sourceKind } : {},
227
- sourceUri: cleanString(input.sourceUri) ?? existing.sourceUri,
228
- sourceExternalId: cleanString(input.sourceExternalId) ?? existing.sourceExternalId,
229
- sourceTitle: cleanString(input.sourceTitle) ?? existing.sourceTitle,
230
- sourceAuthor: cleanString(input.sourceAuthor) ?? existing.sourceAuthor,
231
- sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt) ?? existing.sourceCreatedAt,
232
- sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt) ?? existing.sourceUpdatedAt,
233
- sourceVersion: cleanString(input.sourceVersion) ?? existing.sourceVersion,
234
- ...input.aclTags !== void 0 ? { aclTags: cleanStringArray(input.aclTags) } : {},
219
+ return await withRlsContext(
220
+ db,
221
+ { accountId: input.accountId, workspaceId: input.workspaceId },
222
+ async (scopedDb) => {
223
+ const base = await getDocumentBase(scopedDb, input.workspaceId, input.baseId);
224
+ if (!base) throw new Error(`Document base not found: ${input.baseId}`);
225
+ const file = await requireReadyFile(scopedDb, input.workspaceId, input.fileId);
226
+ const now = /* @__PURE__ */ new Date();
227
+ const [existing] = await scopedDb.select().from(schema.documents).where(
228
+ and(
229
+ eq(schema.documents.workspaceId, input.workspaceId),
230
+ eq(schema.documents.baseId, input.baseId),
231
+ eq(schema.documents.fileId, input.fileId)
232
+ )
233
+ ).limit(1);
234
+ if (existing) {
235
+ const [updated] = await scopedDb.update(schema.documents).set({
236
+ title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? existing.title,
237
+ ...input.sourceKind !== void 0 ? { sourceKind: input.sourceKind } : {},
238
+ sourceUri: cleanString(input.sourceUri) ?? existing.sourceUri,
239
+ sourceExternalId: cleanString(input.sourceExternalId) ?? existing.sourceExternalId,
240
+ sourceTitle: cleanString(input.sourceTitle) ?? existing.sourceTitle,
241
+ sourceAuthor: cleanString(input.sourceAuthor) ?? existing.sourceAuthor,
242
+ sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt) ?? existing.sourceCreatedAt,
243
+ sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt) ?? existing.sourceUpdatedAt,
244
+ sourceVersion: cleanString(input.sourceVersion) ?? existing.sourceVersion,
245
+ ...input.aclTags !== void 0 ? { aclTags: cleanStringArray(input.aclTags) } : {},
246
+ updatedAt: now
247
+ }).where(
248
+ and(
249
+ eq(schema.documents.workspaceId, input.workspaceId),
250
+ eq(schema.documents.id, existing.id)
251
+ )
252
+ ).returning();
253
+ return mapDocument(updated ?? existing);
254
+ }
255
+ const [row] = await scopedDb.insert(schema.documents).values({
256
+ accountId: input.accountId,
257
+ workspaceId: input.workspaceId,
258
+ baseId: input.baseId,
259
+ fileId: input.fileId,
260
+ status: "queued",
261
+ title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? file.filename,
262
+ parser: DEFAULT_DOCUMENT_PARSER,
263
+ sourceKind: input.sourceKind ?? "manual_upload",
264
+ sourceUri: cleanString(input.sourceUri) ?? null,
265
+ sourceExternalId: cleanString(input.sourceExternalId) ?? null,
266
+ sourceTitle: cleanString(input.sourceTitle) ?? null,
267
+ sourceAuthor: cleanString(input.sourceAuthor) ?? null,
268
+ sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt),
269
+ sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt),
270
+ sourceVersion: cleanString(input.sourceVersion) ?? null,
271
+ aclTags: cleanStringArray(input.aclTags),
235
272
  updatedAt: now
236
- }).where(and(eq(schema.documents.workspaceId, input.workspaceId), eq(schema.documents.id, existing.id))).returning();
237
- return mapDocument(updated ?? existing);
273
+ }).returning();
274
+ if (!row) throw new Error("Failed to create document");
275
+ return mapDocument(row);
238
276
  }
239
- const [row] = await scopedDb.insert(schema.documents).values({
240
- accountId: input.accountId,
241
- workspaceId: input.workspaceId,
242
- baseId: input.baseId,
243
- fileId: input.fileId,
244
- status: "queued",
245
- title: cleanString(input.title) ?? cleanString(input.sourceTitle) ?? file.filename,
246
- parser: DEFAULT_DOCUMENT_PARSER,
247
- sourceKind: input.sourceKind ?? "manual_upload",
248
- sourceUri: cleanString(input.sourceUri) ?? null,
249
- sourceExternalId: cleanString(input.sourceExternalId) ?? null,
250
- sourceTitle: cleanString(input.sourceTitle) ?? null,
251
- sourceAuthor: cleanString(input.sourceAuthor) ?? null,
252
- sourceCreatedAt: parseOptionalDate(input.sourceCreatedAt),
253
- sourceUpdatedAt: parseOptionalDate(input.sourceUpdatedAt),
254
- sourceVersion: cleanString(input.sourceVersion) ?? null,
255
- aclTags: cleanStringArray(input.aclTags),
256
- updatedAt: now
257
- }).returning();
258
- if (!row) throw new Error("Failed to create document");
259
- return mapDocument(row);
260
- });
277
+ );
261
278
  }
262
279
  async function deleteDocumentFromBase(db, input) {
263
- await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
264
- const [document] = await scopedDb.select().from(schema.documents).where(and(eq(schema.documents.workspaceId, input.workspaceId), eq(schema.documents.id, input.documentId))).limit(1);
265
- if (!document) {
266
- throw new Error(`Document not found: ${input.documentId}`);
267
- }
268
- if (document.baseId !== input.baseId) {
269
- throw new Error(`Document not found: ${input.documentId}`);
280
+ await withRlsContext(
281
+ db,
282
+ { accountId: input.accountId, workspaceId: input.workspaceId },
283
+ async (scopedDb) => {
284
+ const [document] = await scopedDb.select().from(schema.documents).where(
285
+ and(
286
+ eq(schema.documents.workspaceId, input.workspaceId),
287
+ eq(schema.documents.id, input.documentId)
288
+ )
289
+ ).limit(1);
290
+ if (!document) {
291
+ throw new Error(`Document not found: ${input.documentId}`);
292
+ }
293
+ if (document.baseId !== input.baseId) {
294
+ throw new Error(`Document not found: ${input.documentId}`);
295
+ }
296
+ await scopedDb.delete(schema.documents).where(
297
+ and(
298
+ eq(schema.documents.workspaceId, input.workspaceId),
299
+ eq(schema.documents.id, input.documentId)
300
+ )
301
+ );
270
302
  }
271
- await scopedDb.delete(schema.documents).where(and(eq(schema.documents.workspaceId, input.workspaceId), eq(schema.documents.id, input.documentId)));
272
- });
303
+ );
273
304
  }
274
305
  async function listDocuments(db, workspaceId, baseId) {
275
306
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
276
- const rows = await scopedDb.select().from(schema.documents).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.baseId, baseId))).orderBy(asc(schema.documents.createdAt));
307
+ const rows = await scopedDb.select().from(schema.documents).where(
308
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.baseId, baseId))
309
+ ).orderBy(asc(schema.documents.createdAt));
277
310
  return rows.map(mapDocument);
278
311
  });
279
312
  }
280
313
  async function getDocument(db, workspaceId, documentId) {
281
314
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
282
- const [row] = await scopedDb.select().from(schema.documents).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))).limit(1);
315
+ const [row] = await scopedDb.select().from(schema.documents).where(
316
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
317
+ ).limit(1);
283
318
  return row ? mapDocument(row) : null;
284
319
  });
285
320
  }
@@ -289,7 +324,9 @@ async function queueDocumentForReindex(db, workspaceId, documentId) {
289
324
  status: "queued",
290
325
  error: null,
291
326
  updatedAt: /* @__PURE__ */ new Date()
292
- }).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))).returning();
327
+ }).where(
328
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
329
+ ).returning();
293
330
  if (!row) throw new Error(`Document not found: ${documentId}`);
294
331
  return mapDocument(row);
295
332
  });
@@ -298,7 +335,9 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
298
335
  const [document] = await withWorkspaceRls(
299
336
  db,
300
337
  workspaceId,
301
- async (scopedDb) => await scopedDb.select().from(schema.documents).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))).limit(1)
338
+ async (scopedDb) => await scopedDb.select().from(schema.documents).where(
339
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
340
+ ).limit(1)
302
341
  );
303
342
  if (!document) throw new Error(`Document not found: ${documentId}`);
304
343
  const file = await requireReadyFile(db, workspaceId, document.fileId);
@@ -308,7 +347,9 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
308
347
  parser: services.parser.name,
309
348
  error: null,
310
349
  updatedAt: /* @__PURE__ */ new Date()
311
- }).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)));
350
+ }).where(
351
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
352
+ );
312
353
  });
313
354
  try {
314
355
  const bytes = await objectStorage.getFileBytes(file);
@@ -322,44 +363,66 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
322
363
  });
323
364
  const embeddings = await services.embedder.embedMany(chunks.map((chunk) => chunk.text));
324
365
  if (embeddings.length !== chunks.length) {
325
- throw new Error(`Embedding provider returned ${embeddings.length} embeddings for ${chunks.length} chunks`);
366
+ throw new Error(
367
+ `Embedding provider returned ${embeddings.length} embeddings for ${chunks.length} chunks`
368
+ );
326
369
  }
327
- await withWorkspaceRls(db, workspaceId, async (scopedDb) => await scopedDb.transaction(async (tx) => {
328
- await tx.delete(schema.documentChunks).where(and(eq(schema.documentChunks.workspaceId, workspaceId), eq(schema.documentChunks.documentId, documentId)));
329
- if (chunks.length > 0) {
330
- await tx.insert(schema.documentChunks).values(chunks.map((chunk, index) => ({
331
- accountId: document.accountId,
332
- workspaceId: document.workspaceId,
333
- documentId,
334
- baseId: document.baseId,
335
- fileId: file.id,
336
- chunkIndex: index,
337
- text: chunk.text,
338
- metadata: {
339
- ...chunk.metadata,
340
- documentTitle: document.title,
341
- sourceKind: document.sourceKind,
342
- sourceUri: document.sourceUri,
343
- sourceExternalId: document.sourceExternalId,
344
- sourceTitle: document.sourceTitle,
345
- sourceAuthor: document.sourceAuthor,
346
- sourceCreatedAt: document.sourceCreatedAt?.toISOString() ?? null,
347
- sourceUpdatedAt: document.sourceUpdatedAt?.toISOString() ?? null,
348
- sourceVersion: document.sourceVersion,
349
- aclTags: document.aclTags
350
- },
351
- embedding: validateEmbedding(embeddings[index] ?? [], services.embedder.dimensions, services.embedder.model),
352
- embeddingModel: services.embedder.model
353
- })));
354
- }
355
- await tx.update(schema.documents).set({
356
- status: "ready",
357
- parser: services.parser.name,
358
- chunkCount: chunks.length,
359
- error: null,
360
- updatedAt: /* @__PURE__ */ new Date()
361
- }).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId)));
362
- }));
370
+ await withWorkspaceRls(
371
+ db,
372
+ workspaceId,
373
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
374
+ await tx.delete(schema.documentChunks).where(
375
+ and(
376
+ eq(schema.documentChunks.workspaceId, workspaceId),
377
+ eq(schema.documentChunks.documentId, documentId)
378
+ )
379
+ );
380
+ if (chunks.length > 0) {
381
+ await tx.insert(schema.documentChunks).values(
382
+ chunks.map((chunk, index) => ({
383
+ accountId: document.accountId,
384
+ workspaceId: document.workspaceId,
385
+ documentId,
386
+ baseId: document.baseId,
387
+ fileId: file.id,
388
+ chunkIndex: index,
389
+ text: chunk.text,
390
+ metadata: {
391
+ ...chunk.metadata,
392
+ documentTitle: document.title,
393
+ sourceKind: document.sourceKind,
394
+ sourceUri: document.sourceUri,
395
+ sourceExternalId: document.sourceExternalId,
396
+ sourceTitle: document.sourceTitle,
397
+ sourceAuthor: document.sourceAuthor,
398
+ sourceCreatedAt: document.sourceCreatedAt?.toISOString() ?? null,
399
+ sourceUpdatedAt: document.sourceUpdatedAt?.toISOString() ?? null,
400
+ sourceVersion: document.sourceVersion,
401
+ aclTags: document.aclTags
402
+ },
403
+ embedding: validateEmbedding(
404
+ embeddings[index] ?? [],
405
+ services.embedder.dimensions,
406
+ services.embedder.model
407
+ ),
408
+ embeddingModel: services.embedder.model
409
+ }))
410
+ );
411
+ }
412
+ await tx.update(schema.documents).set({
413
+ status: "ready",
414
+ parser: services.parser.name,
415
+ chunkCount: chunks.length,
416
+ error: null,
417
+ updatedAt: /* @__PURE__ */ new Date()
418
+ }).where(
419
+ and(
420
+ eq(schema.documents.workspaceId, workspaceId),
421
+ eq(schema.documents.id, documentId)
422
+ )
423
+ );
424
+ })
425
+ );
363
426
  } catch (error) {
364
427
  const [failed] = await withWorkspaceRls(
365
428
  db,
@@ -368,7 +431,9 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
368
431
  status: "failed",
369
432
  error: error instanceof Error ? error.message : String(error),
370
433
  updatedAt: /* @__PURE__ */ new Date()
371
- }).where(and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))).returning()
434
+ }).where(
435
+ and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
436
+ ).returning()
372
437
  );
373
438
  if (!failed) throw error;
374
439
  return mapDocument(failed);
@@ -389,10 +454,13 @@ async function searchDocuments(db, input, services = createDocumentServices()) {
389
454
  if (mode === "vector") {
390
455
  throw error;
391
456
  }
392
- console.warn("document hybrid search vector component failed; falling back to keyword search", {
393
- workspaceId: input.workspaceId,
394
- error: error instanceof Error ? error.message : String(error)
395
- });
457
+ console.warn(
458
+ "document hybrid search vector component failed; falling back to keyword search",
459
+ {
460
+ workspaceId: input.workspaceId,
461
+ error: error instanceof Error ? error.message : String(error)
462
+ }
463
+ );
396
464
  }
397
465
  }
398
466
  if (mode === "keyword" || mode === "hybrid") {
@@ -458,10 +526,12 @@ async function keywordSearchDocuments(db, input, limit) {
458
526
  sourceVersion: schema.documents.sourceVersion,
459
527
  aclTags: schema.documents.aclTags,
460
528
  rank
461
- }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(and(
462
- ...documentSearchConditions(input),
463
- sql`to_tsvector('simple', ${schema.documentChunks.text}) @@ plainto_tsquery('simple', ${input.query})`
464
- )).orderBy(desc(rank)).limit(limit)
529
+ }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
530
+ and(
531
+ ...documentSearchConditions(input),
532
+ sql`to_tsvector('simple', ${schema.documentChunks.text}) @@ plainto_tsquery('simple', ${input.query})`
533
+ )
534
+ ).orderBy(desc(rank)).limit(limit)
465
535
  );
466
536
  return rows.map((row) => ({
467
537
  ...mapSearchRowBase(row, input.workspaceId),
@@ -491,7 +561,13 @@ async function getDocumentChunk(db, workspaceId, chunkId) {
491
561
  sourceUpdatedAt: schema.documents.sourceUpdatedAt,
492
562
  sourceVersion: schema.documents.sourceVersion,
493
563
  aclTags: schema.documents.aclTags
494
- }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(and(eq(schema.documentChunks.workspaceId, workspaceId), eq(schema.documentChunks.id, chunkId), eq(schema.documents.status, "ready"))).limit(1)
564
+ }).from(schema.documentChunks).innerJoin(schema.documents, eq(schema.documentChunks.documentId, schema.documents.id)).where(
565
+ and(
566
+ eq(schema.documentChunks.workspaceId, workspaceId),
567
+ eq(schema.documentChunks.id, chunkId),
568
+ eq(schema.documents.status, "ready")
569
+ )
570
+ ).limit(1)
495
571
  );
496
572
  if (!row) return null;
497
573
  return {
@@ -576,7 +652,9 @@ function combinedSearchScore(mode, vectorScore, keywordScore, matchType) {
576
652
  const keyword = keywordScore ?? 0;
577
653
  if (mode === "vector") return roundScore(vector);
578
654
  if (mode === "keyword") return roundScore(keyword);
579
- return roundScore(Math.min(1, 0.65 * vector + 0.35 * keyword + (matchType === "hybrid" ? 0.1 : 0)));
655
+ return roundScore(
656
+ Math.min(1, 0.65 * vector + 0.35 * keyword + (matchType === "hybrid" ? 0.1 : 0))
657
+ );
580
658
  }
581
659
  function normalizeKeywordScore(rank) {
582
660
  if (!Number.isFinite(rank) || rank <= 0) {
@@ -637,7 +715,9 @@ async function requireReadyFile(db, workspaceId, fileId) {
637
715
  }
638
716
  function validateEmbedding(values, dimensions, model) {
639
717
  if (values.length !== dimensions) {
640
- throw new Error(`Embedding model ${model} returned ${values.length} dimensions; expected ${dimensions}`);
718
+ throw new Error(
719
+ `Embedding model ${model} returned ${values.length} dimensions; expected ${dimensions}`
720
+ );
641
721
  }
642
722
  if (values.some((value) => !Number.isFinite(value))) {
643
723
  throw new Error(`Embedding model ${model} returned non-finite values`);