@babav/knowledge-core-client 0.22.3 → 0.22.5

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.d.ts CHANGED
@@ -359,12 +359,41 @@ export interface StreamHandlers {
359
359
  /** Catch-all for any event (incl. unknown ones). */
360
360
  onEvent?: (event: string, data: unknown) => void;
361
361
  }
362
+ /** A live document status transition pushed over the ingestion-status SSE stream
363
+ * (documents.events / folders.events). Replaces polling GET /documents/count. */
364
+ export interface DocumentStatusEvent {
365
+ document_id: string;
366
+ corpus_id?: string | null;
367
+ folder_id?: string | null;
368
+ status: string;
369
+ filename?: string | null;
370
+ chunk_count?: number | null;
371
+ error?: string | null;
372
+ counts?: DocumentCount;
373
+ }
374
+ export interface DocumentEventsSnapshot {
375
+ documents: DocumentStatusEvent[];
376
+ counts: DocumentCount;
377
+ }
378
+ export interface DocumentEventHandlers {
379
+ /** Current state on connect (one query) — render the list from this. */
380
+ onSnapshot?: (s: DocumentEventsSnapshot) => void;
381
+ /** A single document's status changed (the moment it happened). Update that row + counts. */
382
+ onDocument?: (e: DocumentStatusEvent) => void;
383
+ /** Nothing is in-flight anymore — the stream is about to close. Authoritative final counts. */
384
+ onComplete?: (counts: DocumentCount) => void;
385
+ onError?: (err: unknown) => void;
386
+ }
362
387
  export declare class KnowledgeCoreClient extends HttpBase {
363
388
  /** @param opts.apiKey a TENANT key, supplied by the caller. */
364
389
  constructor(opts: ClientOptions);
365
390
  query(agentId: UUID, body: QueryRequest): Promise<QueryResponse>;
366
391
  /** Streaming query (SSE). Resolves when the stream ends. */
367
392
  queryStream(agentId: UUID, body: QueryRequest, handlers: StreamHandlers, signal?: AbortSignal): Promise<void>;
393
+ /** Shared SSE reader for the document-status streams (documents.events / folders.events).
394
+ * Resolves when the stream ends (server sends `complete` once nothing is in-flight, or the
395
+ * cap is hit). Abort via the signal to stop watching. Throws 501 if the env has no bus. */
396
+ _streamDocEvents(path: string, handlers: DocumentEventHandlers, signal?: AbortSignal): Promise<void>;
368
397
  retrieve(body: {
369
398
  query: string;
370
399
  corpus_ids: UUID[];
@@ -454,7 +483,7 @@ export declare class KnowledgeCoreClient extends HttpBase {
454
483
  folders: {
455
484
  create: (corpusId: UUID, name: string) => Promise<Folder>;
456
485
  rename: (folderId: UUID, name: string) => Promise<Folder>;
457
- delete: (folderId: UUID) => Promise<void>;
486
+ delete: (folderId: UUID, confirmName?: string) => Promise<void>;
458
487
  listDocuments: (folderId: UUID, q?: {
459
488
  limit?: number;
460
489
  cursor?: string;
@@ -466,6 +495,8 @@ export declare class KnowledgeCoreClient extends HttpBase {
466
495
  limit?: number;
467
496
  cursor?: string;
468
497
  }) => Promise<Page<Document>>;
498
+ /** Live ingestion-status stream for ONE folder (same contract as documents.events). */
499
+ events: (folderId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) => Promise<void>;
469
500
  };
470
501
  documents: {
471
502
  get: (id: UUID) => Promise<Document>;
@@ -478,6 +509,10 @@ export declare class KnowledgeCoreClient extends HttpBase {
478
509
  }) => Promise<Document>;
479
510
  contentUrl: (id: UUID, disposition?: "inline" | "attachment") => Promise<ContentUrl>;
480
511
  delete: (id: UUID) => Promise<void>;
512
+ /** Live ingestion-status stream for a corpus (SSE PUSH — replaces polling count). Snapshot on
513
+ * connect, then a delta per transition, then `complete` when nothing is in-flight. Abort via
514
+ * the signal. 501 (onError) if the environment has no doc-status bus — fall back to polling. */
515
+ events: (corpusId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) => Promise<void>;
481
516
  };
482
517
  conversations: {
483
518
  create: (b?: {
package/dist/index.js CHANGED
@@ -176,6 +176,38 @@ export class KnowledgeCoreClient extends HttpBase {
176
176
  if (buf.trim())
177
177
  dispatchSse(buf, handlers);
178
178
  }
179
+ /** Shared SSE reader for the document-status streams (documents.events / folders.events).
180
+ * Resolves when the stream ends (server sends `complete` once nothing is in-flight, or the
181
+ * cap is hit). Abort via the signal to stop watching. Throws 501 if the env has no bus. */
182
+ async _streamDocEvents(path, handlers, signal) {
183
+ const res = await this.raw("GET", path, { signal });
184
+ if (!res.ok || !res.body) {
185
+ const t = await res.text();
186
+ const err = new KnowledgeCoreError(res.status, safeJson(t), path);
187
+ if (handlers.onError) {
188
+ handlers.onError(err);
189
+ return;
190
+ }
191
+ throw err;
192
+ }
193
+ const reader = res.body.getReader();
194
+ const decoder = new TextDecoder();
195
+ let buf = "";
196
+ for (;;) {
197
+ const { value, done } = await reader.read();
198
+ if (done)
199
+ break;
200
+ buf += decoder.decode(value, { stream: true });
201
+ let idx;
202
+ while ((idx = buf.indexOf("\n\n")) !== -1) {
203
+ const frame = buf.slice(0, idx);
204
+ buf = buf.slice(idx + 2);
205
+ dispatchDocEvent(frame, handlers);
206
+ }
207
+ }
208
+ if (buf.trim())
209
+ dispatchDocEvent(buf, handlers);
210
+ }
179
211
  // --- retrieve (cross-corpus primitive, no generation) ---
180
212
  retrieve(body) {
181
213
  return this.request("POST", "/v1/retrieve", { json: body });
@@ -246,12 +278,17 @@ export class KnowledgeCoreClient extends HttpBase {
246
278
  folders = {
247
279
  create: (corpusId, name) => this.request("POST", `/v1/corpora/${corpusId}/folders`, { json: { name } }),
248
280
  rename: (folderId, name) => this.request("PATCH", `/v1/folders/${folderId}`, { json: { name } }),
249
- delete: (folderId) => this.request("DELETE", `/v1/folders/${folderId}`),
281
+ // Plain delete requires the folder to be EMPTY (else 409). Pass `confirmName` (the folder's
282
+ // name) to CASCADE — delete the folder AND every document in it (async, 202). The default
283
+ // folder cannot be deleted either way.
284
+ delete: (folderId, confirmName) => this.request("DELETE", `/v1/folders/${folderId}`, confirmName ? { query: { confirm: confirmName } } : undefined),
250
285
  listDocuments: (folderId, q) => this.request("GET", `/v1/folders/${folderId}/documents`, { query: q }),
251
286
  /** Count documents in the folder: { total, indexed, in_flight, failed }. */
252
287
  countDocuments: (folderId) => this.request("GET", `/v1/folders/${folderId}/documents/count`),
253
288
  /** Documents in the folder whose filename contains `q` (case-insensitive), paginated. */
254
289
  searchDocuments: (folderId, q, opts) => this.request("GET", `/v1/folders/${folderId}/documents/search`, { query: { q, ...opts } }),
290
+ /** Live ingestion-status stream for ONE folder (same contract as documents.events). */
291
+ events: (folderId, handlers, signal) => this._streamDocEvents(`/v1/folders/${folderId}/documents/events`, handlers, signal),
255
292
  };
256
293
  // --- documents ---
257
294
  documents = {
@@ -262,6 +299,10 @@ export class KnowledgeCoreClient extends HttpBase {
262
299
  update: (id, b) => this.request("PATCH", `/v1/documents/${id}`, { json: b }),
263
300
  contentUrl: (id, disposition = "inline") => this.request("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
264
301
  delete: (id) => this.request("DELETE", `/v1/documents/${id}`),
302
+ /** Live ingestion-status stream for a corpus (SSE PUSH — replaces polling count). Snapshot on
303
+ * connect, then a delta per transition, then `complete` when nothing is in-flight. Abort via
304
+ * the signal. 501 (onError) if the environment has no doc-status bus — fall back to polling. */
305
+ events: (corpusId, handlers, signal) => this._streamDocEvents(`/v1/corpora/${corpusId}/documents/events`, handlers, signal),
265
306
  };
266
307
  // --- conversations ---
267
308
  conversations = {
@@ -381,3 +422,27 @@ function dispatchSse(frame, h) {
381
422
  break;
382
423
  }
383
424
  }
425
+ function dispatchDocEvent(frame, h) {
426
+ let event = "message";
427
+ const dataLines = [];
428
+ for (const line of frame.split("\n")) {
429
+ if (line.startsWith("event:"))
430
+ event = line.slice(6).trim();
431
+ else if (line.startsWith("data:"))
432
+ dataLines.push(line.slice(5).trim());
433
+ }
434
+ if (dataLines.length === 0)
435
+ return; // heartbeat / comment frame
436
+ const data = safeJson(dataLines.join("\n"));
437
+ switch (event) {
438
+ case "snapshot":
439
+ h.onSnapshot?.(data);
440
+ break;
441
+ case "document":
442
+ h.onDocument?.(data);
443
+ break;
444
+ case "complete":
445
+ h.onComplete?.(data);
446
+ break;
447
+ }
448
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babav/knowledge-core-client",
3
- "version": "0.22.3",
3
+ "version": "0.22.5",
4
4
  "description": "TypeScript client for the Babav Knowledge Core API (Deno + Node 18+, zero deps). Includes the babav.visual grammar TYPES at the ./visual subpath (types only; all visual rendering is server-side).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -480,6 +480,32 @@ export interface StreamHandlers {
480
480
  onEvent?: (event: string, data: unknown) => void;
481
481
  }
482
482
 
483
+ /** A live document status transition pushed over the ingestion-status SSE stream
484
+ * (documents.events / folders.events). Replaces polling GET /documents/count. */
485
+ export interface DocumentStatusEvent {
486
+ document_id: string;
487
+ corpus_id?: string | null;
488
+ folder_id?: string | null;
489
+ status: string; // pending | ingesting | indexed | failed | deleting
490
+ filename?: string | null;
491
+ chunk_count?: number | null;
492
+ error?: string | null;
493
+ counts?: DocumentCount; // running aggregate, included on each `document` delta
494
+ }
495
+ export interface DocumentEventsSnapshot {
496
+ documents: DocumentStatusEvent[]; // current state of every doc in the corpus/folder
497
+ counts: DocumentCount;
498
+ }
499
+ export interface DocumentEventHandlers {
500
+ /** Current state on connect (one query) — render the list from this. */
501
+ onSnapshot?: (s: DocumentEventsSnapshot) => void;
502
+ /** A single document's status changed (the moment it happened). Update that row + counts. */
503
+ onDocument?: (e: DocumentStatusEvent) => void;
504
+ /** Nothing is in-flight anymore — the stream is about to close. Authoritative final counts. */
505
+ onComplete?: (counts: DocumentCount) => void;
506
+ onError?: (err: unknown) => void;
507
+ }
508
+
483
509
  // ---------------------------------------------------------------------------
484
510
  // Tenant client (data ops) — use a TENANT key
485
511
  // ---------------------------------------------------------------------------
@@ -518,6 +544,34 @@ export class KnowledgeCoreClient extends HttpBase {
518
544
  if (buf.trim()) dispatchSse(buf, handlers);
519
545
  }
520
546
 
547
+ /** Shared SSE reader for the document-status streams (documents.events / folders.events).
548
+ * Resolves when the stream ends (server sends `complete` once nothing is in-flight, or the
549
+ * cap is hit). Abort via the signal to stop watching. Throws 501 if the env has no bus. */
550
+ async _streamDocEvents(path: string, handlers: DocumentEventHandlers, signal?: AbortSignal): Promise<void> {
551
+ const res = await this.raw("GET", path, { signal });
552
+ if (!res.ok || !res.body) {
553
+ const t = await res.text();
554
+ const err = new KnowledgeCoreError(res.status, safeJson(t), path);
555
+ if (handlers.onError) { handlers.onError(err); return; }
556
+ throw err;
557
+ }
558
+ const reader = res.body.getReader();
559
+ const decoder = new TextDecoder();
560
+ let buf = "";
561
+ for (;;) {
562
+ const { value, done } = await reader.read();
563
+ if (done) break;
564
+ buf += decoder.decode(value, { stream: true });
565
+ let idx: number;
566
+ while ((idx = buf.indexOf("\n\n")) !== -1) {
567
+ const frame = buf.slice(0, idx);
568
+ buf = buf.slice(idx + 2);
569
+ dispatchDocEvent(frame, handlers);
570
+ }
571
+ }
572
+ if (buf.trim()) dispatchDocEvent(buf, handlers);
573
+ }
574
+
521
575
  // --- retrieve (cross-corpus primitive, no generation) ---
522
576
  retrieve(body: { query: string; corpus_ids: UUID[]; top_k_retrieved_chunks?: number; top_k_reranked_chunks?: number; rerank?: boolean; instruction?: string; filter?: MetadataFilter; }): Promise<{ retrieval_contents: RetrievalContent[] }> {
523
577
  return this.request("POST", "/v1/retrieve", { json: body });
@@ -594,7 +648,11 @@ export class KnowledgeCoreClient extends HttpBase {
594
648
  folders = {
595
649
  create: (corpusId: UUID, name: string) => this.request<Folder>("POST", `/v1/corpora/${corpusId}/folders`, { json: { name } }),
596
650
  rename: (folderId: UUID, name: string) => this.request<Folder>("PATCH", `/v1/folders/${folderId}`, { json: { name } }),
597
- delete: (folderId: UUID) => this.request<void>("DELETE", `/v1/folders/${folderId}`),
651
+ // Plain delete requires the folder to be EMPTY (else 409). Pass `confirmName` (the folder's
652
+ // name) to CASCADE — delete the folder AND every document in it (async, 202). The default
653
+ // folder cannot be deleted either way.
654
+ delete: (folderId: UUID, confirmName?: string) =>
655
+ this.request<void>("DELETE", `/v1/folders/${folderId}`, confirmName ? { query: { confirm: confirmName } } : undefined),
598
656
  listDocuments: (folderId: UUID, q?: { limit?: number; cursor?: string }) =>
599
657
  this.request<Page<Document>>("GET", `/v1/folders/${folderId}/documents`, { query: q }),
600
658
  /** Count documents in the folder: { total, indexed, in_flight, failed }. */
@@ -603,6 +661,9 @@ export class KnowledgeCoreClient extends HttpBase {
603
661
  /** Documents in the folder whose filename contains `q` (case-insensitive), paginated. */
604
662
  searchDocuments: (folderId: UUID, q: string, opts?: { limit?: number; cursor?: string }) =>
605
663
  this.request<Page<Document>>("GET", `/v1/folders/${folderId}/documents/search`, { query: { q, ...opts } }),
664
+ /** Live ingestion-status stream for ONE folder (same contract as documents.events). */
665
+ events: (folderId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) =>
666
+ this._streamDocEvents(`/v1/folders/${folderId}/documents/events`, handlers, signal),
606
667
  };
607
668
 
608
669
  // --- documents ---
@@ -616,6 +677,11 @@ export class KnowledgeCoreClient extends HttpBase {
616
677
  contentUrl: (id: UUID, disposition: "inline" | "attachment" = "inline") =>
617
678
  this.request<ContentUrl>("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
618
679
  delete: (id: UUID) => this.request<void>("DELETE", `/v1/documents/${id}`),
680
+ /** Live ingestion-status stream for a corpus (SSE PUSH — replaces polling count). Snapshot on
681
+ * connect, then a delta per transition, then `complete` when nothing is in-flight. Abort via
682
+ * the signal. 501 (onError) if the environment has no doc-status bus — fall back to polling. */
683
+ events: (corpusId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) =>
684
+ this._streamDocEvents(`/v1/corpora/${corpusId}/documents/events`, handlers, signal),
619
685
  };
620
686
 
621
687
  // --- conversations ---
@@ -729,3 +795,19 @@ function dispatchSse(frame: string, h: StreamHandlers): void {
729
795
  case "done": h.onDone?.(); break;
730
796
  }
731
797
  }
798
+
799
+ function dispatchDocEvent(frame: string, h: DocumentEventHandlers): void {
800
+ let event = "message";
801
+ const dataLines: string[] = [];
802
+ for (const line of frame.split("\n")) {
803
+ if (line.startsWith("event:")) event = line.slice(6).trim();
804
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
805
+ }
806
+ if (dataLines.length === 0) return; // heartbeat / comment frame
807
+ const data = safeJson(dataLines.join("\n"));
808
+ switch (event) {
809
+ case "snapshot": h.onSnapshot?.(data as DocumentEventsSnapshot); break;
810
+ case "document": h.onDocument?.(data as DocumentStatusEvent); break;
811
+ case "complete": h.onComplete?.(data as DocumentCount); break;
812
+ }
813
+ }