@semiont/api-client 0.4.13 → 0.4.15

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
@@ -1,24 +1,19 @@
1
- import ky from 'ky';
2
- import { Subscription, BehaviorSubject, merge, firstValueFrom } from 'rxjs';
3
- import { resourceId, annotationId } from '@semiont/core';
1
+ import ky, { HTTPError } from 'ky';
2
+ import { BehaviorSubject, Observable, merge } from 'rxjs';
3
+ import { map, distinctUntilChanged, filter, takeUntil } from 'rxjs/operators';
4
+ import { annotationId, resourceId, email, googleCredential, refreshToken } from '@semiont/core';
4
5
  export { getFragmentSelector, getSvgSelector, getTextPositionSelector, validateSvgMarkup } from '@semiont/core';
5
- import { map, distinctUntilChanged, filter, take, timeout } from 'rxjs/operators';
6
6
 
7
7
  // src/client.ts
8
8
 
9
9
  // src/sse/stream.ts
10
- var DOMAIN_EVENT_TO_UI_CHANNEL = {
11
- "annotation.added": "mark:added",
12
- "annotation.removed": "mark:removed",
13
- "annotation.body.updated": "mark:body-updated",
14
- "resource.archived": "mark:archived",
15
- "resource.unarchived": "mark:unarchived",
16
- "entitytag.added": "mark:entity-tag-added",
17
- "entitytag.removed": "mark:entity-tag-removed"
18
- };
19
10
  function createSSEStream(url, fetchOptions, config, logger) {
20
- const abortController = new AbortController();
11
+ let abortController = new AbortController();
21
12
  let closed = false;
13
+ let lastEventId = null;
14
+ const RECONNECT_INITIAL_MS = 1e3;
15
+ const RECONNECT_MAX_MS = 3e4;
16
+ let reconnectDelayMs = RECONNECT_INITIAL_MS;
22
17
  const connect = async () => {
23
18
  try {
24
19
  logger?.debug("SSE Stream Request", {
@@ -27,13 +22,17 @@ function createSSEStream(url, fetchOptions, config, logger) {
27
22
  method: fetchOptions.method || "GET",
28
23
  timestamp: Date.now()
29
24
  });
25
+ const headers = {
26
+ ...fetchOptions.headers,
27
+ "Accept": "text/event-stream"
28
+ };
29
+ if (lastEventId !== null) {
30
+ headers["Last-Event-ID"] = lastEventId;
31
+ }
30
32
  const response = await fetch(url, {
31
33
  ...fetchOptions,
32
34
  signal: abortController.signal,
33
- headers: {
34
- ...fetchOptions.headers,
35
- "Accept": "text/event-stream"
36
- }
35
+ headers
37
36
  });
38
37
  if (!response.ok) {
39
38
  const errorData = await response.json().catch(() => ({}));
@@ -117,10 +116,13 @@ function createSSEStream(url, fetchOptions, config, logger) {
117
116
  }
118
117
  }
119
118
  };
120
- const handleEvent = (eventType, data, _id) => {
119
+ const handleEvent = (eventType, data, id) => {
121
120
  if (data.startsWith(":")) {
122
121
  return;
123
122
  }
123
+ if (id) {
124
+ lastEventId = id;
125
+ }
124
126
  try {
125
127
  const parsed = JSON.parse(data);
126
128
  logger?.debug("SSE Event Received", {
@@ -129,13 +131,8 @@ function createSSEStream(url, fetchOptions, config, logger) {
129
131
  event: eventType || "message",
130
132
  hasData: !!data
131
133
  });
132
- if (typeof parsed === "object" && parsed !== null && "type" in parsed) {
134
+ if (typeof parsed === "object" && parsed !== null && "metadata" in parsed) {
133
135
  config.eventBus.get(eventType).next(parsed);
134
- const uiChannel = DOMAIN_EVENT_TO_UI_CHANNEL[parsed.type];
135
- if (uiChannel) {
136
- config.eventBus.get(uiChannel).next(parsed);
137
- }
138
- config.eventBus.get("make-meaning:event").next(parsed);
139
136
  return;
140
137
  }
141
138
  config.eventBus.get(eventType).next(parsed);
@@ -150,9 +147,33 @@ function createSSEStream(url, fetchOptions, config, logger) {
150
147
  logger?.error("SSE Failed to parse event data", { error, eventType, data });
151
148
  }
152
149
  };
153
- connect();
150
+ const runConnect = async () => {
151
+ if (!config.reconnect) {
152
+ return connect();
153
+ }
154
+ while (!closed) {
155
+ abortController = new AbortController();
156
+ try {
157
+ await connect();
158
+ if (closed) return;
159
+ logger?.info("SSE Stream ended cleanly; reconnecting", { url });
160
+ } catch (error) {
161
+ if (closed) return;
162
+ if (error instanceof Error && error.name === "AbortError") return;
163
+ logger?.warn("SSE Stream errored; reconnecting", {
164
+ url,
165
+ error: error instanceof Error ? error.message : String(error),
166
+ delayMs: reconnectDelayMs
167
+ });
168
+ }
169
+ await new Promise((resolve) => setTimeout(resolve, reconnectDelayMs));
170
+ reconnectDelayMs = Math.min(reconnectDelayMs * 2, RECONNECT_MAX_MS);
171
+ }
172
+ };
173
+ void runConnect();
154
174
  return {
155
175
  close() {
176
+ closed = true;
156
177
  abortController.abort();
157
178
  }
158
179
  };
@@ -179,459 +200,6 @@ var SSEClient = class {
179
200
  }
180
201
  return headers;
181
202
  }
182
- /**
183
- * Detect annotations in a resource (streaming)
184
- *
185
- * Streams entity detection progress via Server-Sent Events.
186
- *
187
- * @param resourceId - Resource URI or ID
188
- * @param request - Detection configuration (entity types to detect)
189
- * @param options - Request options (auth token)
190
- * @returns SSE stream controller with progress/complete/error callbacks
191
- *
192
- * @example
193
- * ```typescript
194
- * const stream = sseClient.markReferences(
195
- * 'http://localhost:4000/resources/doc-123',
196
- * { entityTypes: ['Person', 'Organization'] },
197
- * { auth: 'your-token' }
198
- * );
199
- *
200
- * stream.onProgress((progress) => {
201
- * console.log(`Scanning: ${progress.currentEntityType}`);
202
- * console.log(`Progress: ${progress.processedEntityTypes}/${progress.totalEntityTypes}`);
203
- * });
204
- *
205
- * stream.onComplete((result) => {
206
- * console.log(`Detection complete! Found ${result.foundCount} entities`);
207
- * });
208
- *
209
- * stream.onError((error) => {
210
- * console.error('Detection failed:', error.message);
211
- * });
212
- *
213
- * // Cleanup when done
214
- * stream.close();
215
- * ```
216
- */
217
- markReferences(resourceId, request, options) {
218
- const url = `${this.baseUrl}/resources/${resourceId}/annotate-references-stream`;
219
- return createSSEStream(
220
- url,
221
- {
222
- method: "POST",
223
- headers: this.getHeaders(options.auth),
224
- body: JSON.stringify(request)
225
- },
226
- {
227
- progressEvents: ["mark:progress"],
228
- completeEvent: "mark:assist-finished",
229
- errorEvent: "mark:assist-failed",
230
- eventBus: options.eventBus,
231
- eventPrefix: void 0
232
- },
233
- this.logger
234
- );
235
- }
236
- /**
237
- * Generate resource from annotation (streaming)
238
- *
239
- * Streams resource generation progress via Server-Sent Events.
240
- *
241
- * @param resourceId - Source resource URI or ID
242
- * @param annotationId - Annotation URI or ID to use as generation source
243
- * @param request - Generation options (title, prompt, language)
244
- * @param options - Request options (auth token)
245
- * @returns SSE stream controller with progress/complete/error callbacks
246
- *
247
- * @example
248
- * ```typescript
249
- * const stream = sseClient.yieldResource(
250
- * 'http://localhost:4000/resources/doc-123',
251
- * 'http://localhost:4000/annotations/ann-456',
252
- * { language: 'es', title: 'Spanish Summary' },
253
- * { auth: 'your-token' }
254
- * );
255
- *
256
- * stream.onProgress((progress) => {
257
- * console.log(`${progress.status}: ${progress.percentage}%`);
258
- * console.log(progress.message);
259
- * });
260
- *
261
- * stream.onComplete((result) => {
262
- * console.log(`Yielded resource: ${result.resourceId}`);
263
- * });
264
- *
265
- * stream.onError((error) => {
266
- * console.error('Yield failed:', error.message);
267
- * });
268
- *
269
- * // Cleanup when done
270
- * stream.close();
271
- * ```
272
- */
273
- yieldResource(resourceId, annotationId, request, options) {
274
- const url = `${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/yield-resource-stream`;
275
- return createSSEStream(
276
- url,
277
- {
278
- method: "POST",
279
- headers: this.getHeaders(options.auth),
280
- body: JSON.stringify(request)
281
- },
282
- {
283
- progressEvents: ["yield:progress"],
284
- completeEvent: "yield:finished",
285
- errorEvent: "yield:failed",
286
- eventBus: options.eventBus,
287
- eventPrefix: void 0
288
- },
289
- this.logger
290
- );
291
- }
292
- /**
293
- * Detect highlights in a resource (streaming)
294
- *
295
- * Streams highlight annotation progress via Server-Sent Events.
296
- *
297
- * @param resourceId - Resource URI or ID
298
- * @param request - Detection configuration (optional instructions)
299
- * @param options - Request options (auth token)
300
- * @returns SSE stream controller with progress/complete/error callbacks
301
- *
302
- * @example
303
- * ```typescript
304
- * const stream = sseClient.markHighlights(
305
- * 'http://localhost:4000/resources/doc-123',
306
- * { instructions: 'Focus on key technical points' },
307
- * { auth: 'your-token' }
308
- * );
309
- *
310
- * stream.onProgress((progress) => {
311
- * console.log(`${progress.status}: ${progress.percentage}%`);
312
- * console.log(progress.message);
313
- * });
314
- *
315
- * stream.onComplete((result) => {
316
- * console.log(`Detection complete! Created ${result.createdCount} highlights`);
317
- * });
318
- *
319
- * stream.onError((error) => {
320
- * console.error('Detection failed:', error.message);
321
- * });
322
- *
323
- * // Cleanup when done
324
- * stream.close();
325
- * ```
326
- */
327
- markHighlights(resourceId, request = {}, options) {
328
- const url = `${this.baseUrl}/resources/${resourceId}/annotate-highlights-stream`;
329
- return createSSEStream(
330
- url,
331
- {
332
- method: "POST",
333
- headers: this.getHeaders(options.auth),
334
- body: JSON.stringify(request)
335
- },
336
- {
337
- progressEvents: ["mark:progress"],
338
- completeEvent: "mark:assist-finished",
339
- errorEvent: "mark:assist-failed",
340
- eventBus: options.eventBus,
341
- eventPrefix: void 0
342
- },
343
- this.logger
344
- );
345
- }
346
- /**
347
- * Detect assessments in a resource (streaming)
348
- *
349
- * Streams assessment annotation progress via Server-Sent Events.
350
- *
351
- * @param resourceId - Resource URI or ID
352
- * @param request - Detection configuration (optional instructions)
353
- * @param options - Request options (auth token)
354
- * @returns SSE stream controller with progress/complete/error callbacks
355
- *
356
- * @example
357
- * ```typescript
358
- * const stream = sseClient.markAssessments(
359
- * 'http://localhost:4000/resources/doc-123',
360
- * { instructions: 'Evaluate claims for accuracy' },
361
- * { auth: 'your-token' }
362
- * );
363
- *
364
- * stream.onProgress((progress) => {
365
- * console.log(`${progress.status}: ${progress.percentage}%`);
366
- * console.log(progress.message);
367
- * });
368
- *
369
- * stream.onComplete((result) => {
370
- * console.log(`Detection complete! Created ${result.createdCount} assessments`);
371
- * });
372
- *
373
- * stream.onError((error) => {
374
- * console.error('Detection failed:', error.message);
375
- * });
376
- *
377
- * // Cleanup when done
378
- * stream.close();
379
- * ```
380
- */
381
- markAssessments(resourceId, request = {}, options) {
382
- const url = `${this.baseUrl}/resources/${resourceId}/annotate-assessments-stream`;
383
- return createSSEStream(
384
- url,
385
- {
386
- method: "POST",
387
- headers: this.getHeaders(options.auth),
388
- body: JSON.stringify(request)
389
- },
390
- {
391
- progressEvents: ["mark:progress"],
392
- completeEvent: "mark:assist-finished",
393
- errorEvent: "mark:assist-failed",
394
- eventBus: options.eventBus,
395
- eventPrefix: void 0
396
- },
397
- this.logger
398
- );
399
- }
400
- /**
401
- * Detect comments in a resource (streaming)
402
- *
403
- * Streams comment annotation progress via Server-Sent Events.
404
- * Uses AI to identify passages that would benefit from explanatory comments
405
- * and creates comment annotations with contextual information.
406
- *
407
- * @param resourceId - Resource URI or ID
408
- * @param request - Detection configuration (optional instructions and tone)
409
- * @param options - Request options (auth token)
410
- * @returns SSE stream controller with progress/complete/error callbacks
411
- *
412
- * @example
413
- * ```typescript
414
- * const stream = sseClient.markComments(
415
- * 'http://localhost:4000/resources/doc-123',
416
- * {
417
- * instructions: 'Focus on technical terminology',
418
- * tone: 'scholarly'
419
- * },
420
- * { auth: 'your-token' }
421
- * );
422
- *
423
- * stream.onProgress((progress) => {
424
- * console.log(`${progress.status}: ${progress.percentage}%`);
425
- * });
426
- *
427
- * stream.onComplete((result) => {
428
- * console.log(`Detection complete! Created ${result.createdCount} comments`);
429
- * });
430
- *
431
- * stream.onError((error) => {
432
- * console.error('Detection failed:', error.message);
433
- * });
434
- *
435
- * // Cleanup when done
436
- * stream.close();
437
- * ```
438
- */
439
- markComments(resourceId, request = {}, options) {
440
- const url = `${this.baseUrl}/resources/${resourceId}/annotate-comments-stream`;
441
- return createSSEStream(
442
- url,
443
- {
444
- method: "POST",
445
- headers: this.getHeaders(options.auth),
446
- body: JSON.stringify(request)
447
- },
448
- {
449
- progressEvents: ["mark:progress"],
450
- completeEvent: "mark:assist-finished",
451
- errorEvent: "mark:assist-failed",
452
- eventBus: options.eventBus,
453
- eventPrefix: void 0
454
- },
455
- this.logger
456
- );
457
- }
458
- /**
459
- * Detect tags in a resource (streaming)
460
- *
461
- * Streams tag annotation progress via Server-Sent Events.
462
- * Uses AI to identify passages serving specific structural roles
463
- * (e.g., IRAC, IMRAD, Toulmin) and creates tag annotations with dual-body structure.
464
- *
465
- * @param resourceId - Resource URI or ID
466
- * @param request - Detection configuration (schema and categories to detect)
467
- * @param options - Request options (auth token)
468
- * @returns SSE stream controller with progress/complete/error callbacks
469
- *
470
- * @example
471
- * ```typescript
472
- * const stream = sseClient.markTags(
473
- * 'http://localhost:4000/resources/doc-123',
474
- * {
475
- * schemaId: 'legal-irac',
476
- * categories: ['Issue', 'Rule', 'Application', 'Conclusion']
477
- * },
478
- * { auth: 'your-token' }
479
- * );
480
- *
481
- * stream.onProgress((progress) => {
482
- * console.log(`${progress.status}: ${progress.percentage}%`);
483
- * console.log(`Processing ${progress.currentCategory}...`);
484
- * });
485
- *
486
- * stream.onComplete((result) => {
487
- * console.log(`Detection complete! Created ${result.tagsCreated} tags`);
488
- * });
489
- *
490
- * stream.onError((error) => {
491
- * console.error('Detection failed:', error.message);
492
- * });
493
- *
494
- * // Cleanup when done
495
- * stream.close();
496
- * ```
497
- */
498
- markTags(resourceId, request, options) {
499
- const url = `${this.baseUrl}/resources/${resourceId}/annotate-tags-stream`;
500
- return createSSEStream(
501
- url,
502
- {
503
- method: "POST",
504
- headers: this.getHeaders(options.auth),
505
- body: JSON.stringify(request)
506
- },
507
- {
508
- progressEvents: ["mark:progress"],
509
- completeEvent: "mark:assist-finished",
510
- errorEvent: "mark:assist-failed",
511
- eventBus: options.eventBus,
512
- eventPrefix: void 0
513
- },
514
- this.logger
515
- );
516
- }
517
- /**
518
- * Gather LLM context for a resource (streaming)
519
- *
520
- * Streams resource LLM context gathering progress via Server-Sent Events.
521
- *
522
- * @param resourceId - Resource URI or ID
523
- * @param request - Gather configuration (depth, maxResources, includeContent, includeSummary)
524
- * @param options - Request options (auth token, eventBus)
525
- * @returns SSE stream controller with progress/complete/error callbacks
526
- */
527
- gatherResource(resourceId, request, options) {
528
- const url = `${this.baseUrl}/resources/${resourceId}/gather-resource-stream`;
529
- return createSSEStream(
530
- url,
531
- {
532
- method: "POST",
533
- headers: this.getHeaders(options.auth),
534
- body: JSON.stringify(request)
535
- },
536
- {
537
- progressEvents: ["gather:progress"],
538
- completeEvent: "gather:finished",
539
- errorEvent: "gather:failed",
540
- eventBus: options.eventBus,
541
- eventPrefix: void 0
542
- },
543
- this.logger
544
- );
545
- }
546
- /**
547
- * Gather LLM context for an annotation (streaming)
548
- *
549
- * Streams annotation LLM context gathering progress via Server-Sent Events.
550
- *
551
- * @param resourceId - Resource URI or ID
552
- * @param annotationId - Annotation URI or ID
553
- * @param request - Gather configuration (contextWindow)
554
- * @param options - Request options (auth token, eventBus)
555
- * @returns SSE stream controller with progress/complete/error callbacks
556
- */
557
- gatherAnnotation(resourceId, annotationId, request, options) {
558
- const url = `${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/gather-annotation-stream`;
559
- return createSSEStream(
560
- url,
561
- {
562
- method: "POST",
563
- headers: this.getHeaders(options.auth),
564
- body: JSON.stringify(request)
565
- },
566
- {
567
- progressEvents: ["gather:annotation-progress"],
568
- completeEvent: "gather:annotation-finished",
569
- errorEvent: "gather:failed",
570
- eventBus: options.eventBus,
571
- eventPrefix: void 0
572
- },
573
- this.logger
574
- );
575
- }
576
- /**
577
- * Bind annotation body (streaming)
578
- *
579
- * Applies annotation body operations and streams completion via Server-Sent Events.
580
- *
581
- * @param resourceId - Resource URI or ID
582
- * @param annotationId - Annotation URI or ID
583
- * @param request - Bind operations (resourceId + operations array)
584
- * @param options - Request options (auth token, eventBus)
585
- * @returns SSE stream controller with complete/error callbacks
586
- */
587
- bindAnnotation(resourceId, annotationId, request, options) {
588
- const url = `${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/bind-stream`;
589
- return createSSEStream(
590
- url,
591
- {
592
- method: "POST",
593
- headers: this.getHeaders(options.auth),
594
- body: JSON.stringify(request)
595
- },
596
- {
597
- progressEvents: [],
598
- completeEvent: "bind:finished",
599
- errorEvent: "bind:failed",
600
- eventBus: options.eventBus,
601
- eventPrefix: void 0
602
- },
603
- this.logger
604
- );
605
- }
606
- /**
607
- * Search for binding candidates (streaming)
608
- *
609
- * Bridges match:search-requested to the backend Matcher actor via SSE.
610
- * Results emit as match:search-results on the browser EventBus.
611
- *
612
- * @param resourceId - Resource the annotation belongs to
613
- * @param request - Search configuration (referenceId, context, limit)
614
- * @param options - Request options (auth token, eventBus)
615
- * @returns SSE stream controller
616
- */
617
- matchSearch(resourceId, request, options) {
618
- const url = `${this.baseUrl}/resources/${resourceId}/match-search-stream`;
619
- return createSSEStream(
620
- url,
621
- {
622
- method: "POST",
623
- headers: this.getHeaders(options.auth),
624
- body: JSON.stringify(request)
625
- },
626
- {
627
- progressEvents: [],
628
- completeEvent: "match:search-results",
629
- errorEvent: "match:search-failed",
630
- eventBus: options.eventBus
631
- },
632
- this.logger
633
- );
634
- }
635
203
  /**
636
204
  * Subscribe to resource events (long-lived stream)
637
205
  *
@@ -681,7 +249,8 @@ var SSEClient = class {
681
249
  // Never completes (long-lived)
682
250
  errorEvent: null,
683
251
  // No error event (errors throw)
684
- eventBus: options.eventBus
252
+ eventBus: options.eventBus,
253
+ reconnect: true
685
254
  },
686
255
  this.logger
687
256
  );
@@ -706,11 +275,9 @@ var SSEClient = class {
706
275
  * ```typescript
707
276
  * const stream = sseClient.globalEvents({ auth: 'your-token', eventBus });
708
277
  *
709
- * // Events auto-emit to EventBus — subscribe there
710
- * eventBus.get('make-meaning:event').subscribe((event) => {
711
- * if (event.type === 'entitytype.added') {
712
- * // Invalidate entity types query
713
- * }
278
+ * // Events auto-emit to EventBus typed channels — subscribe there
279
+ * eventBus.get('mark:entity-type-added').subscribe((stored) => {
280
+ * // Invalidate entity types query
714
281
  * });
715
282
  *
716
283
  * // Close when no longer needed
@@ -729,7 +296,8 @@ var SSEClient = class {
729
296
  progressEvents: ["*"],
730
297
  completeEvent: null,
731
298
  errorEvent: null,
732
- eventBus: options.eventBus
299
+ eventBus: options.eventBus,
300
+ reconnect: true
733
301
  },
734
302
  this.logger
735
303
  );
@@ -765,7 +333,8 @@ var SSEClient = class {
765
333
  progressEvents: ["*"],
766
334
  completeEvent: null,
767
335
  errorEvent: null,
768
- eventBus: options.eventBus
336
+ eventBus: options.eventBus,
337
+ reconnect: true
769
338
  },
770
339
  this.logger
771
340
  );
@@ -778,497 +347,693 @@ var SSEClient = class {
778
347
  return stream;
779
348
  }
780
349
  };
781
- var FlowEngine = class {
782
- constructor(eventBus, sse, http) {
783
- this.eventBus = eventBus;
784
- this.sse = sse;
350
+ var BrowseNamespace = class {
351
+ constructor(http, eventBus, getToken) {
785
352
  this.http = http;
353
+ this.eventBus = eventBus;
354
+ this.getToken = getToken;
355
+ this.subscribeToEvents();
356
+ }
357
+ // ── Annotation list cache ───────────────────────────────────────────────
358
+ annotationList$ = new BehaviorSubject(/* @__PURE__ */ new Map());
359
+ fetchingAnnotationList = /* @__PURE__ */ new Set();
360
+ annotationListObs$ = /* @__PURE__ */ new Map();
361
+ // ── Annotation detail cache ─────────────────────────────────────────────
362
+ annotationDetail$ = new BehaviorSubject(/* @__PURE__ */ new Map());
363
+ fetchingAnnotationDetail = /* @__PURE__ */ new Set();
364
+ annotationDetailObs$ = /* @__PURE__ */ new Map();
365
+ // ── Resource detail cache ───────────────────────────────────────────────
366
+ resourceDetail$ = new BehaviorSubject(/* @__PURE__ */ new Map());
367
+ fetchingResourceDetail = /* @__PURE__ */ new Set();
368
+ resourceDetailObs$ = /* @__PURE__ */ new Map();
369
+ // ── Resource list cache ─────────────────────────────────────────────────
370
+ resourceList$ = new BehaviorSubject(/* @__PURE__ */ new Map());
371
+ fetchingResourceList = /* @__PURE__ */ new Set();
372
+ resourceListObs$ = /* @__PURE__ */ new Map();
373
+ // ── Entity types cache ──────────────────────────────────────────────────
374
+ entityTypes$ = new BehaviorSubject(void 0);
375
+ fetchingEntityTypes = false;
376
+ // ── Referenced-by cache ─────────────────────────────────────────────────
377
+ referencedBy$ = new BehaviorSubject(/* @__PURE__ */ new Map());
378
+ fetchingReferencedBy = /* @__PURE__ */ new Set();
379
+ referencedByObs$ = /* @__PURE__ */ new Map();
380
+ getToken;
381
+ // ── Live queries ────────────────────────────────────────────────────────
382
+ resource(resourceId) {
383
+ if (!this.resourceDetail$.value.has(resourceId) && !this.fetchingResourceDetail.has(resourceId)) {
384
+ this.fetchResourceDetail(resourceId);
385
+ }
386
+ let obs = this.resourceDetailObs$.get(resourceId);
387
+ if (!obs) {
388
+ obs = this.resourceDetail$.pipe(map((m) => m.get(resourceId)), distinctUntilChanged());
389
+ this.resourceDetailObs$.set(resourceId, obs);
390
+ }
391
+ return obs;
786
392
  }
787
- // ─── bind flow ─────────────────────────────────────────────────────────────
788
- /**
789
- * Activate the bind flow for a resource.
790
- *
791
- * @subscribes bind:update-body — calls SSE bindAnnotation
792
- * @subscribes match:search-requested — calls SSE bindSearch
793
- * @emits bind:body-updated, bind:body-update-failed
794
- */
795
- bind(rUri, getToken) {
796
- const sub = new Subscription();
797
- sub.add(
798
- this.eventBus.get("bind:update-body").subscribe((event) => {
799
- const annotationId = event.annotationId;
800
- const finishedSub = this.eventBus.get("bind:finished").subscribe((finishedEvent) => {
801
- if (finishedEvent.annotationId !== annotationId) return;
802
- finishedSub.unsubscribe();
803
- failedSub.unsubscribe();
804
- this.eventBus.get("bind:body-updated").next({ annotationId });
805
- });
806
- const failedSub = this.eventBus.get("bind:failed").subscribe(() => {
807
- finishedSub.unsubscribe();
808
- failedSub.unsubscribe();
809
- this.eventBus.get("bind:body-update-failed").next({ error: new Error("Bind failed") });
810
- });
811
- this.sse.bindAnnotation(
812
- rUri,
813
- annotationId,
814
- { resourceId: event.resourceId, operations: event.operations },
815
- { auth: getToken(), eventBus: this.eventBus }
816
- );
817
- })
818
- );
819
- sub.add(
820
- this.eventBus.get("match:search-requested").subscribe((event) => {
821
- this.sse.matchSearch(rUri, {
822
- correlationId: event.correlationId,
823
- referenceId: event.referenceId,
824
- context: event.context,
825
- limit: event.limit,
826
- useSemanticScoring: event.useSemanticScoring
827
- }, { auth: getToken(), eventBus: this.eventBus });
828
- })
829
- );
830
- return sub;
393
+ resources(filters) {
394
+ const key = JSON.stringify(filters ?? {});
395
+ if (!this.resourceList$.value.has(key) && !this.fetchingResourceList.has(key)) {
396
+ this.fetchResourceList(key, filters);
397
+ }
398
+ let obs = this.resourceListObs$.get(key);
399
+ if (!obs) {
400
+ obs = this.resourceList$.pipe(map((m) => m.get(key)), distinctUntilChanged());
401
+ this.resourceListObs$.set(key, obs);
402
+ }
403
+ return obs;
831
404
  }
832
- // ─── yield flow ────────────────────────────────────────────────────────────
833
- /**
834
- * Activate the yield (generation) flow for a resource.
835
- *
836
- * @subscribes yield:request — calls SSE yieldResource
837
- * @subscribes yield:finished — links generated resource back to the reference annotation via bind:update-body
838
- * @subscribes job:cancel-requested (generation) — aborts in-flight stream
839
- */
840
- yield(_rUri, getToken) {
841
- const sub = new Subscription();
842
- let abortController = null;
843
- sub.add(
844
- this.eventBus.get("yield:request").subscribe((event) => {
845
- abortController?.abort();
846
- abortController = new AbortController();
847
- this.sse.yieldResource(
848
- event.resourceId,
849
- event.annotationId,
850
- event.options,
851
- { auth: getToken(), eventBus: this.eventBus }
852
- );
853
- })
854
- );
855
- sub.add(
856
- this.eventBus.get("yield:finished").subscribe((event) => {
857
- if (!event.resourceId || !event.referenceId || !event.sourceResourceId) return;
858
- this.eventBus.get("bind:update-body").next({
859
- annotationId: annotationId(event.referenceId),
860
- resourceId: resourceId(event.sourceResourceId),
861
- operations: [{ op: "add", item: { type: "SpecificResource", source: event.resourceId } }]
862
- });
863
- })
864
- );
865
- sub.add(
866
- this.eventBus.get("job:cancel-requested").subscribe((event) => {
867
- if (event.jobType === "generation") {
868
- abortController?.abort();
869
- abortController = null;
870
- }
871
- })
872
- );
873
- sub.add(new Subscription(() => {
874
- abortController?.abort();
875
- }));
876
- return sub;
405
+ annotations(resourceId) {
406
+ if (!this.annotationList$.value.has(resourceId) && !this.fetchingAnnotationList.has(resourceId)) {
407
+ this.fetchAnnotationList(resourceId);
408
+ }
409
+ let obs = this.annotationListObs$.get(resourceId);
410
+ if (!obs) {
411
+ obs = this.annotationList$.pipe(
412
+ map((m) => m.get(resourceId)?.annotations),
413
+ distinctUntilChanged()
414
+ );
415
+ this.annotationListObs$.set(resourceId, obs);
416
+ }
417
+ return obs;
877
418
  }
878
- // ─── mark flow ─────────────────────────────────────────────────────────────
879
- /**
880
- * Activate the mark (annotation CRUD + assist) flow for a resource.
881
- *
882
- * @subscribes mark:submit — HTTP markAnnotation
883
- * @subscribes mark:delete — HTTP deleteAnnotation
884
- * @subscribes mark:assist-request SSE mark* (by motivation)
885
- * @subscribes job:cancel-requested (annotation) — aborts in-flight assist
886
- * @emits mark:created, mark:create-failed, mark:deleted, mark:delete-failed
887
- */
888
- mark(rUri, getToken) {
889
- const sub = new Subscription();
890
- let assistAbort = null;
891
- sub.add(
892
- this.eventBus.get("mark:submit").subscribe(async (event) => {
893
- try {
894
- const result = await this.http.markAnnotation(rUri, {
895
- motivation: event.motivation,
896
- target: { source: rUri, selector: event.selector },
897
- body: event.body
898
- }, { auth: getToken() });
899
- this.eventBus.get("mark:created").next({ annotationId: annotationId(result.annotationId) });
900
- } catch (error) {
901
- this.eventBus.get("mark:create-failed").next({ error });
902
- }
903
- })
904
- );
905
- sub.add(
906
- this.eventBus.get("mark:delete").subscribe(async (event) => {
907
- try {
908
- await this.http.deleteAnnotation(rUri, event.annotationId, { auth: getToken() });
909
- this.eventBus.get("mark:deleted").next({ annotationId: event.annotationId });
910
- } catch (error) {
911
- this.eventBus.get("mark:delete-failed").next({ error });
912
- }
913
- })
914
- );
915
- sub.add(
916
- this.eventBus.get("mark:assist-request").subscribe(async (event) => {
917
- assistAbort?.abort();
918
- assistAbort = new AbortController();
919
- const opts = { auth: getToken(), eventBus: this.eventBus };
920
- const { motivation, options } = event;
921
- try {
922
- if (motivation === "tagging") {
923
- const { schemaId, categories } = options;
924
- if (!schemaId || !categories?.length) throw new Error("Tag assist requires schemaId and categories");
925
- this.sse.markTags(rUri, { schemaId, categories }, opts);
926
- } else if (motivation === "linking") {
927
- const { entityTypes, includeDescriptiveReferences } = options;
928
- if (!entityTypes?.length) throw new Error("Reference assist requires entityTypes");
929
- this.sse.markReferences(rUri, {
930
- entityTypes,
931
- includeDescriptiveReferences: includeDescriptiveReferences ?? false
932
- }, opts);
933
- } else if (motivation === "highlighting") {
934
- this.sse.markHighlights(rUri, { instructions: options.instructions, density: options.density }, opts);
935
- } else if (motivation === "assessing") {
936
- this.sse.markAssessments(rUri, {
937
- instructions: options.instructions,
938
- tone: options.tone,
939
- density: options.density,
940
- language: options.language
941
- }, opts);
942
- } else if (motivation === "commenting") {
943
- this.sse.markComments(rUri, {
944
- instructions: options.instructions,
945
- tone: options.tone,
946
- density: options.density,
947
- language: options.language
948
- }, opts);
949
- }
950
- } catch (error) {
951
- if (error instanceof Error && error.name === "AbortError") {
952
- this.eventBus.get("mark:assist-cancelled").next(void 0);
953
- }
954
- }
955
- })
956
- );
957
- sub.add(
958
- this.eventBus.get("job:cancel-requested").subscribe((event) => {
959
- if (event.jobType === "annotation") {
960
- assistAbort?.abort();
961
- assistAbort = null;
962
- }
963
- })
964
- );
965
- sub.add(new Subscription(() => {
966
- assistAbort?.abort();
967
- }));
968
- return sub;
419
+ annotation(resourceId, annotationId) {
420
+ if (!this.annotationDetail$.value.has(annotationId) && !this.fetchingAnnotationDetail.has(annotationId)) {
421
+ this.fetchAnnotationDetail(resourceId, annotationId);
422
+ }
423
+ let obs = this.annotationDetailObs$.get(annotationId);
424
+ if (!obs) {
425
+ obs = this.annotationDetail$.pipe(map((m) => m.get(annotationId)), distinctUntilChanged());
426
+ this.annotationDetailObs$.set(annotationId, obs);
427
+ }
428
+ return obs;
969
429
  }
970
- // ─── gatherContext flow ─────────────────────────────────────────────────────
971
- /**
972
- * Activate the gather-context flow for a resource.
973
- *
974
- * @subscribes gather:requested — calls SSE gatherAnnotation, threads correlationId
975
- * @emits gather:complete (re-emitted from SSE gather:annotation-finished)
976
- */
977
- gatherContext(rUri, getToken) {
978
- return this.eventBus.get("gather:requested").subscribe((event) => {
979
- const { correlationId } = event;
980
- const contextWindow = event.options?.contextWindow ?? 2e3;
981
- const finishedSub = this.eventBus.get("gather:annotation-finished").subscribe((finishedEvent) => {
982
- if (finishedEvent.correlationId !== correlationId) return;
983
- finishedSub.unsubscribe();
984
- failedSub.unsubscribe();
985
- this.eventBus.get("gather:complete").next({
986
- correlationId,
987
- annotationId: finishedEvent.annotationId,
988
- response: finishedEvent.response
989
- });
990
- });
991
- const failedSub = this.eventBus.get("gather:failed").subscribe((failedEvent) => {
992
- if (failedEvent.correlationId !== correlationId) return;
993
- finishedSub.unsubscribe();
994
- failedSub.unsubscribe();
995
- });
996
- this.sse.gatherAnnotation(
997
- rUri,
998
- event.annotationId,
999
- { contextWindow, correlationId },
1000
- { auth: getToken(), eventBus: this.eventBus }
1001
- );
430
+ entityTypes() {
431
+ if (this.entityTypes$.value === void 0 && !this.fetchingEntityTypes) {
432
+ this.fetchEntityTypes();
433
+ }
434
+ return this.entityTypes$.asObservable();
435
+ }
436
+ referencedBy(resourceId) {
437
+ if (!this.referencedBy$.value.has(resourceId) && !this.fetchingReferencedBy.has(resourceId)) {
438
+ this.fetchReferencedBy(resourceId);
439
+ }
440
+ let obs = this.referencedByObs$.get(resourceId);
441
+ if (!obs) {
442
+ obs = this.referencedBy$.pipe(map((m) => m.get(resourceId)), distinctUntilChanged());
443
+ this.referencedByObs$.set(resourceId, obs);
444
+ }
445
+ return obs;
446
+ }
447
+ // ── One-shot reads ──────────────────────────────────────────────────────
448
+ async resourceContent(resourceId) {
449
+ const result = await this.http.getResourceRepresentation(resourceId, {
450
+ accept: "text/plain",
451
+ auth: this.getToken()
1002
452
  });
453
+ const decoder = new TextDecoder();
454
+ return decoder.decode(result.data);
1003
455
  }
1004
- // ─── resourceEvents flow ───────────────────────────────────────────────────
1005
- /**
1006
- * Open the long-lived resource-events SSE stream.
1007
- * Returns a Subscription whose teardown closes the stream.
1008
- */
1009
- resourceEvents(rUri, getToken) {
1010
- const stream = this.sse.resourceEvents(rUri, {
1011
- auth: getToken(),
1012
- eventBus: this.eventBus
456
+ async resourceRepresentation(resourceId, options) {
457
+ return this.http.getResourceRepresentation(resourceId, {
458
+ accept: options?.accept,
459
+ auth: this.getToken()
1013
460
  });
1014
- return new Subscription(() => stream.close());
1015
461
  }
1016
- // ─── attentionStream flow ──────────────────────────────────────────────────
1017
- /**
1018
- * Open the long-lived participant attention SSE stream.
1019
- * Returns a Subscription whose teardown closes the stream.
1020
- */
1021
- attentionStream(getToken) {
1022
- const stream = this.sse.attentionStream({
1023
- auth: getToken(),
1024
- eventBus: this.eventBus
462
+ async resourceRepresentationStream(resourceId, options) {
463
+ return this.http.getResourceRepresentationStream(resourceId, {
464
+ accept: options?.accept,
465
+ auth: this.getToken()
1025
466
  });
1026
- return new Subscription(() => stream.close());
1027
467
  }
1028
- };
1029
- var ResourceStore = class {
1030
- constructor(http, eventBus) {
1031
- this.http = http;
1032
- eventBus.get("yield:created").subscribe((event) => {
1033
- this.fetchDetail(event.resourceId);
1034
- this.invalidateLists();
468
+ async resourceEvents(resourceId) {
469
+ const result = await this.http.getResourceEvents(resourceId, { auth: this.getToken() });
470
+ return result.events;
471
+ }
472
+ async annotationHistory(resourceId, annotationId) {
473
+ return this.http.getAnnotationHistory(resourceId, annotationId, { auth: this.getToken() });
474
+ }
475
+ async connections(_resourceId) {
476
+ throw new Error("Not implemented: connections endpoint does not exist yet");
477
+ }
478
+ async backlinks(_resourceId) {
479
+ throw new Error("Not implemented: backlinks endpoint does not exist yet");
480
+ }
481
+ async resourcesByName(_query, _limit) {
482
+ throw new Error("Not implemented: resourcesByName endpoint does not exist yet");
483
+ }
484
+ async files(dirPath, sort) {
485
+ return this.http.browseFiles(dirPath, sort, { auth: this.getToken() });
486
+ }
487
+ // ── Invalidation (exposed for other namespaces) ─────────────────────────
488
+ invalidateAnnotationList(resourceId) {
489
+ const next = new Map(this.annotationList$.value);
490
+ next.delete(resourceId);
491
+ this.annotationList$.next(next);
492
+ this.fetchAnnotationList(resourceId);
493
+ }
494
+ invalidateAnnotationDetail(annotationId) {
495
+ const next = new Map(this.annotationDetail$.value);
496
+ next.delete(annotationId);
497
+ this.annotationDetail$.next(next);
498
+ }
499
+ invalidateResourceDetail(id) {
500
+ const next = new Map(this.resourceDetail$.value);
501
+ next.delete(id);
502
+ this.resourceDetail$.next(next);
503
+ this.fetchResourceDetail(id);
504
+ }
505
+ invalidateResourceLists() {
506
+ this.resourceList$.next(/* @__PURE__ */ new Map());
507
+ }
508
+ invalidateEntityTypes() {
509
+ this.entityTypes$.next(void 0);
510
+ this.fetchEntityTypes();
511
+ }
512
+ invalidateReferencedBy(resourceId) {
513
+ const next = new Map(this.referencedBy$.value);
514
+ next.delete(resourceId);
515
+ this.referencedBy$.next(next);
516
+ this.fetchReferencedBy(resourceId);
517
+ }
518
+ updateAnnotationInPlace(resourceId, annotation) {
519
+ const currentList = this.annotationList$.value.get(resourceId);
520
+ if (!currentList) return;
521
+ const existingIdx = currentList.annotations.findIndex((a) => a.id === annotation.id);
522
+ const nextAnnotations = existingIdx >= 0 ? currentList.annotations.map((a, i) => i === existingIdx ? annotation : a) : [...currentList.annotations, annotation];
523
+ const nextList = { ...currentList, annotations: nextAnnotations };
524
+ const nextMap = new Map(this.annotationList$.value);
525
+ nextMap.set(resourceId, nextList);
526
+ this.annotationList$.next(nextMap);
527
+ }
528
+ // ── EventBus subscriptions ──────────────────────────────────────────────
529
+ subscribeToEvents() {
530
+ const bus = this.eventBus;
531
+ bus.get("mark:delete-ok").subscribe((event) => {
532
+ this.invalidateAnnotationDetail(annotationId(event.annotationId));
1035
533
  });
1036
- eventBus.get("yield:updated").subscribe((event) => {
1037
- this.invalidateDetail(event.resourceId);
1038
- this.invalidateLists();
534
+ bus.get("mark:added").subscribe((stored) => {
535
+ if (stored.resourceId) {
536
+ this.invalidateAnnotationList(stored.resourceId);
537
+ }
1039
538
  });
1040
- eventBus.get("mark:archived").subscribe((event) => {
1041
- if (event.resourceId) {
1042
- this.invalidateDetail(event.resourceId);
1043
- this.invalidateLists();
539
+ bus.get("mark:removed").subscribe((stored) => {
540
+ if (stored.resourceId) {
541
+ this.invalidateAnnotationList(stored.resourceId);
542
+ }
543
+ this.invalidateAnnotationDetail(annotationId(stored.payload.annotationId));
544
+ });
545
+ bus.get("mark:body-updated").subscribe((event) => {
546
+ const enriched = event;
547
+ if (!enriched.resourceId || !enriched.annotation) return;
548
+ this.updateAnnotationInPlace(enriched.resourceId, enriched.annotation);
549
+ this.invalidateAnnotationDetail(annotationId(enriched.annotation.id));
550
+ });
551
+ bus.get("mark:entity-tag-added").subscribe((stored) => {
552
+ if (stored.resourceId) {
553
+ this.invalidateAnnotationList(stored.resourceId);
554
+ this.invalidateResourceDetail(stored.resourceId);
555
+ }
556
+ });
557
+ bus.get("mark:entity-tag-removed").subscribe((stored) => {
558
+ if (stored.resourceId) {
559
+ this.invalidateAnnotationList(stored.resourceId);
560
+ this.invalidateResourceDetail(stored.resourceId);
1044
561
  }
1045
562
  });
1046
- eventBus.get("mark:unarchived").subscribe((event) => {
563
+ bus.get("replay-window-exceeded").subscribe((event) => {
1047
564
  if (event.resourceId) {
1048
- this.invalidateDetail(event.resourceId);
1049
- this.invalidateLists();
565
+ this.invalidateAnnotationList(event.resourceId);
1050
566
  }
1051
567
  });
1052
- eventBus.get("mark:entity-tag-added").subscribe((event) => {
1053
- if (event.resourceId) {
1054
- this.invalidateDetail(event.resourceId);
568
+ bus.get("yield:create-ok").subscribe((event) => {
569
+ this.fetchResourceDetail(resourceId(event.resourceId));
570
+ this.invalidateResourceLists();
571
+ });
572
+ bus.get("yield:update-ok").subscribe((event) => {
573
+ this.invalidateResourceDetail(resourceId(event.resourceId));
574
+ this.invalidateResourceLists();
575
+ });
576
+ bus.get("mark:archived").subscribe((stored) => {
577
+ if (stored.resourceId) {
578
+ this.invalidateResourceDetail(stored.resourceId);
579
+ this.invalidateResourceLists();
1055
580
  }
1056
581
  });
1057
- eventBus.get("mark:entity-tag-removed").subscribe((event) => {
1058
- if (event.resourceId) {
1059
- this.invalidateDetail(event.resourceId);
582
+ bus.get("mark:unarchived").subscribe((stored) => {
583
+ if (stored.resourceId) {
584
+ this.invalidateResourceDetail(stored.resourceId);
585
+ this.invalidateResourceLists();
1060
586
  }
1061
587
  });
588
+ bus.get("mark:entity-type-added").subscribe(() => {
589
+ this.invalidateEntityTypes();
590
+ });
1062
591
  }
1063
- /** Cache of individual resource details, keyed by ResourceId */
1064
- detail$ = new BehaviorSubject(/* @__PURE__ */ new Map());
1065
- /** Cache of list responses, keyed by a serialized options string */
1066
- list$ = new BehaviorSubject(/* @__PURE__ */ new Map());
1067
- /** Track in-flight fetches to avoid duplicate requests */
1068
- fetchingDetail = /* @__PURE__ */ new Set();
1069
- fetchingList = /* @__PURE__ */ new Set();
1070
- /** Memoized Observables — same instance returned for the same key */
1071
- detailObs$ = /* @__PURE__ */ new Map();
1072
- listObs$ = /* @__PURE__ */ new Map();
1073
- /** Mutable token getter — updated from the React layer when auth changes */
1074
- getToken = () => void 0;
1075
- /** Update the token getter (called from React when auth token changes) */
1076
- setTokenGetter(getter) {
1077
- this.getToken = getter;
1078
- }
1079
- /**
1080
- * Get a single resource by ID as an Observable.
1081
- * Triggers a fetch if not cached.
1082
- */
1083
- get(id) {
1084
- if (!this.detail$.value.has(id) && !this.fetchingDetail.has(id)) {
1085
- this.fetchDetail(id);
1086
- }
1087
- let obs = this.detailObs$.get(id);
1088
- if (!obs) {
1089
- obs = this.detail$.pipe(map((m) => m.get(id)), distinctUntilChanged());
1090
- this.detailObs$.set(id, obs);
592
+ // ── Fetch helpers ───────────────────────────────────────────────────────
593
+ async fetchAnnotationList(resourceId) {
594
+ if (this.fetchingAnnotationList.has(resourceId)) return;
595
+ this.fetchingAnnotationList.add(resourceId);
596
+ try {
597
+ const result = await this.http.browseAnnotations(resourceId, void 0, { auth: this.getToken() });
598
+ const next = new Map(this.annotationList$.value);
599
+ next.set(resourceId, result);
600
+ this.annotationList$.next(next);
601
+ } catch {
602
+ } finally {
603
+ this.fetchingAnnotationList.delete(resourceId);
1091
604
  }
1092
- return obs;
1093
605
  }
1094
- /**
1095
- * List resources as an Observable.
1096
- * Triggers a fetch if not cached.
1097
- */
1098
- list(options) {
1099
- const key = JSON.stringify(options ?? {});
1100
- if (!this.list$.value.has(key) && !this.fetchingList.has(key)) {
1101
- this.fetchList(key, options);
1102
- }
1103
- let obs = this.listObs$.get(key);
1104
- if (!obs) {
1105
- obs = this.list$.pipe(map((m) => m.get(key)), distinctUntilChanged());
1106
- this.listObs$.set(key, obs);
606
+ async fetchAnnotationDetail(resourceId, annotationId) {
607
+ if (this.fetchingAnnotationDetail.has(annotationId)) return;
608
+ this.fetchingAnnotationDetail.add(annotationId);
609
+ try {
610
+ const result = await this.http.browseAnnotation(resourceId, annotationId, { auth: this.getToken() });
611
+ const next = new Map(this.annotationDetail$.value);
612
+ next.set(annotationId, result.annotation);
613
+ this.annotationDetail$.next(next);
614
+ } catch {
615
+ } finally {
616
+ this.fetchingAnnotationDetail.delete(annotationId);
1107
617
  }
1108
- return obs;
1109
618
  }
1110
- /** Invalidate and re-fetch a specific resource detail */
1111
- invalidateDetail(id) {
1112
- const next = new Map(this.detail$.value);
1113
- next.delete(id);
1114
- this.detail$.next(next);
1115
- this.fetchDetail(id);
619
+ async fetchResourceDetail(id) {
620
+ if (this.fetchingResourceDetail.has(id)) return;
621
+ this.fetchingResourceDetail.add(id);
622
+ try {
623
+ const result = await this.http.browseResource(id, { auth: this.getToken() });
624
+ const next = new Map(this.resourceDetail$.value);
625
+ next.set(id, result.resource);
626
+ this.resourceDetail$.next(next);
627
+ } catch {
628
+ } finally {
629
+ this.fetchingResourceDetail.delete(id);
630
+ }
1116
631
  }
1117
- /** Remove all list caches (triggers re-fetch on next subscribe) */
1118
- invalidateLists() {
1119
- this.list$.next(/* @__PURE__ */ new Map());
632
+ async fetchResourceList(key, filters) {
633
+ if (this.fetchingResourceList.has(key)) return;
634
+ this.fetchingResourceList.add(key);
635
+ try {
636
+ const result = await this.http.browseResources(filters?.limit, filters?.archived, void 0, { auth: this.getToken() });
637
+ const next = new Map(this.resourceList$.value);
638
+ next.set(key, result.resources);
639
+ this.resourceList$.next(next);
640
+ } catch {
641
+ } finally {
642
+ this.fetchingResourceList.delete(key);
643
+ }
1120
644
  }
1121
- async fetchDetail(id) {
1122
- if (this.fetchingDetail.has(id)) return;
1123
- this.fetchingDetail.add(id);
645
+ async fetchEntityTypes() {
646
+ if (this.fetchingEntityTypes) return;
647
+ this.fetchingEntityTypes = true;
1124
648
  try {
1125
- const resource = await this.http.browseResource(id, { auth: this.getToken() });
1126
- const next = new Map(this.detail$.value);
1127
- next.set(id, resource);
1128
- this.detail$.next(next);
649
+ const result = await this.http.listEntityTypes({ auth: this.getToken() });
650
+ this.entityTypes$.next(result.entityTypes);
1129
651
  } catch {
1130
652
  } finally {
1131
- this.fetchingDetail.delete(id);
653
+ this.fetchingEntityTypes = false;
1132
654
  }
1133
655
  }
1134
- async fetchList(key, options) {
1135
- if (this.fetchingList.has(key)) return;
1136
- this.fetchingList.add(key);
656
+ async fetchReferencedBy(resourceId) {
657
+ if (this.fetchingReferencedBy.has(resourceId)) return;
658
+ this.fetchingReferencedBy.add(resourceId);
1137
659
  try {
1138
- const result = await this.http.browseResources(options?.limit, options?.archived, void 0, { auth: this.getToken() });
1139
- const next = new Map(this.list$.value);
1140
- next.set(key, result);
1141
- this.list$.next(next);
660
+ const result = await this.http.browseReferences(resourceId, { auth: this.getToken() });
661
+ const next = new Map(this.referencedBy$.value);
662
+ next.set(resourceId, result.referencedBy);
663
+ this.referencedBy$.next(next);
1142
664
  } catch {
1143
665
  } finally {
1144
- this.fetchingList.delete(key);
666
+ this.fetchingReferencedBy.delete(resourceId);
1145
667
  }
1146
668
  }
1147
669
  };
1148
- var AnnotationStore = class {
1149
- constructor(http, eventBus) {
670
+ var MarkNamespace = class {
671
+ constructor(http, eventBus, getToken) {
1150
672
  this.http = http;
1151
- eventBus.get("mark:deleted").subscribe((event) => {
1152
- this.removeFromDetailCache(event.annotationId);
673
+ this.eventBus = eventBus;
674
+ this.getToken = getToken;
675
+ }
676
+ async annotation(resourceId, input) {
677
+ return this.http.markAnnotation(resourceId, input, { auth: this.getToken() });
678
+ }
679
+ async delete(resourceId, annotationId) {
680
+ return this.http.deleteAnnotation(resourceId, annotationId, { auth: this.getToken() });
681
+ }
682
+ async entityType(type) {
683
+ return this.http.addEntityType(type, { auth: this.getToken() });
684
+ }
685
+ async entityTypes(types) {
686
+ return this.http.addEntityTypesBulk(types, { auth: this.getToken() });
687
+ }
688
+ async updateResource(resourceId, data) {
689
+ return this.http.updateResource(resourceId, data, { auth: this.getToken() });
690
+ }
691
+ async archive(resourceId) {
692
+ return this.http.updateResource(resourceId, { archived: true }, { auth: this.getToken() });
693
+ }
694
+ async unarchive(resourceId) {
695
+ return this.http.updateResource(resourceId, { archived: false }, { auth: this.getToken() });
696
+ }
697
+ assist(resourceId, motivation, options) {
698
+ return new Observable((subscriber) => {
699
+ const progress$ = this.eventBus.get("mark:progress").pipe(
700
+ filter((e) => e.resourceId === resourceId)
701
+ );
702
+ const finished$ = this.eventBus.get("mark:assist-finished").pipe(
703
+ filter((e) => e.resourceId === resourceId && e.motivation === motivation)
704
+ );
705
+ const failed$ = this.eventBus.get("mark:assist-failed").pipe(
706
+ filter((e) => e.resourceId === resourceId)
707
+ );
708
+ const progressSub = progress$.pipe(takeUntil(merge(finished$, failed$))).subscribe((e) => subscriber.next(e));
709
+ const finishedSub = finished$.subscribe((e) => {
710
+ subscriber.next(e);
711
+ subscriber.complete();
712
+ });
713
+ const failedSub = failed$.subscribe((e) => {
714
+ subscriber.error(new Error(e.message));
715
+ });
716
+ const auth = this.getToken();
717
+ const postPromise = this.dispatchAssist(resourceId, motivation, options, auth);
718
+ postPromise.catch((error) => {
719
+ subscriber.error(error);
720
+ });
721
+ return () => {
722
+ progressSub.unsubscribe();
723
+ finishedSub.unsubscribe();
724
+ failedSub.unsubscribe();
725
+ };
1153
726
  });
1154
- eventBus.get("mark:added").subscribe((event) => {
1155
- if (event.resourceId) {
1156
- this.invalidateList(event.resourceId);
1157
- }
727
+ }
728
+ async dispatchAssist(resourceId, motivation, options, auth) {
729
+ if (motivation === "tagging") {
730
+ const { schemaId, categories } = options;
731
+ if (!schemaId || !categories?.length) throw new Error("Tag assist requires schemaId and categories");
732
+ await this.http.annotateTags(resourceId, { schemaId, categories }, { auth });
733
+ } else if (motivation === "linking") {
734
+ const { entityTypes, includeDescriptiveReferences } = options;
735
+ if (!entityTypes?.length) throw new Error("Reference assist requires entityTypes");
736
+ await this.http.annotateReferences(resourceId, {
737
+ entityTypes,
738
+ includeDescriptiveReferences: includeDescriptiveReferences ?? false
739
+ }, { auth });
740
+ } else if (motivation === "highlighting") {
741
+ await this.http.annotateHighlights(resourceId, {
742
+ instructions: options.instructions,
743
+ density: options.density
744
+ }, { auth });
745
+ } else if (motivation === "assessing") {
746
+ await this.http.annotateAssessments(resourceId, {
747
+ instructions: options.instructions,
748
+ tone: options.tone,
749
+ density: options.density,
750
+ language: options.language
751
+ }, { auth });
752
+ } else if (motivation === "commenting") {
753
+ await this.http.annotateComments(resourceId, {
754
+ instructions: options.instructions,
755
+ tone: options.tone,
756
+ density: options.density,
757
+ language: options.language
758
+ }, { auth });
759
+ }
760
+ }
761
+ };
762
+
763
+ // src/namespaces/bind.ts
764
+ var BindNamespace = class {
765
+ constructor(http, getToken) {
766
+ this.http = http;
767
+ this.getToken = getToken;
768
+ }
769
+ async body(resourceId, annotationId, operations) {
770
+ await this.http.bindAnnotation(resourceId, annotationId, { operations }, { auth: this.getToken() });
771
+ }
772
+ };
773
+ var GatherNamespace = class {
774
+ constructor(http, eventBus, getToken) {
775
+ this.http = http;
776
+ this.eventBus = eventBus;
777
+ this.getToken = getToken;
778
+ }
779
+ annotation(annotationId$1, resourceId, options) {
780
+ return new Observable((subscriber) => {
781
+ const correlationId = crypto.randomUUID();
782
+ const complete$ = this.eventBus.get("gather:complete").pipe(
783
+ filter((e) => e.correlationId === correlationId)
784
+ );
785
+ const failed$ = this.eventBus.get("gather:failed").pipe(
786
+ filter((e) => e.correlationId === correlationId)
787
+ );
788
+ const sub = merge(
789
+ this.eventBus.get("gather:annotation-progress").pipe(
790
+ // Progress events don't carry correlationId, so match by annotationId
791
+ filter((e) => e.annotationId === annotationId$1),
792
+ map((e) => e)
793
+ ),
794
+ complete$.pipe(map((e) => e))
795
+ ).pipe(takeUntil(merge(complete$, failed$))).subscribe({
796
+ next: (v) => subscriber.next(v),
797
+ error: (e) => subscriber.error(e)
798
+ });
799
+ const completeSub = complete$.subscribe((e) => {
800
+ subscriber.next(e);
801
+ subscriber.complete();
802
+ });
803
+ const failedSub = failed$.subscribe((e) => {
804
+ subscriber.error(new Error(e.message));
805
+ });
806
+ this.http.gatherAnnotationContext(
807
+ resourceId,
808
+ annotationId(annotationId$1),
809
+ { correlationId, contextWindow: options?.contextWindow ?? 2e3 },
810
+ { auth: this.getToken() }
811
+ ).catch((error) => {
812
+ subscriber.error(error);
813
+ });
814
+ return () => {
815
+ sub.unsubscribe();
816
+ completeSub.unsubscribe();
817
+ failedSub.unsubscribe();
818
+ };
1158
819
  });
1159
- eventBus.get("mark:removed").subscribe((event) => {
1160
- if (event.resourceId) {
1161
- this.invalidateList(event.resourceId);
1162
- }
1163
- this.invalidateDetail(event.payload.annotationId);
820
+ }
821
+ resource(_resourceId, _options) {
822
+ throw new Error("Not implemented: gather.resource() \u2014 no backend route yet");
823
+ }
824
+ };
825
+ var MatchNamespace = class {
826
+ constructor(http, eventBus, getToken) {
827
+ this.http = http;
828
+ this.eventBus = eventBus;
829
+ this.getToken = getToken;
830
+ }
831
+ search(resourceId, referenceId, context, options) {
832
+ return new Observable((subscriber) => {
833
+ const correlationId = crypto.randomUUID();
834
+ const result$ = this.eventBus.get("match:search-results").pipe(
835
+ filter((e) => e.correlationId === correlationId)
836
+ );
837
+ const failed$ = this.eventBus.get("match:search-failed").pipe(
838
+ filter((e) => e.correlationId === correlationId)
839
+ );
840
+ const resultSub = result$.subscribe((e) => {
841
+ subscriber.next(e);
842
+ subscriber.complete();
843
+ });
844
+ const failedSub = failed$.subscribe((e) => {
845
+ subscriber.error(new Error(e.error));
846
+ });
847
+ this.http.matchSearch(
848
+ resourceId,
849
+ {
850
+ correlationId,
851
+ referenceId,
852
+ context,
853
+ limit: options?.limit,
854
+ useSemanticScoring: options?.useSemanticScoring
855
+ },
856
+ { auth: this.getToken() }
857
+ ).catch((error) => {
858
+ subscriber.error(error);
859
+ });
860
+ return () => {
861
+ resultSub.unsubscribe();
862
+ failedSub.unsubscribe();
863
+ };
1164
864
  });
1165
- eventBus.get("mark:body-updated").subscribe((event) => {
1166
- if (event.resourceId) {
1167
- this.invalidateList(event.resourceId);
1168
- }
1169
- this.invalidateDetail(event.payload.annotationId);
865
+ }
866
+ };
867
+ var YieldNamespace = class {
868
+ constructor(http, eventBus, getToken) {
869
+ this.http = http;
870
+ this.eventBus = eventBus;
871
+ this.getToken = getToken;
872
+ }
873
+ async resource(data) {
874
+ return this.http.yieldResource(data, { auth: this.getToken() });
875
+ }
876
+ fromAnnotation(resourceId$1, annotationId$1, options) {
877
+ return new Observable((subscriber) => {
878
+ const progress$ = this.eventBus.get("yield:progress").pipe(
879
+ filter((e) => e.referenceId === annotationId$1)
880
+ );
881
+ const finished$ = this.eventBus.get("yield:finished").pipe(
882
+ filter((e) => e.referenceId === annotationId$1)
883
+ );
884
+ const failed$ = this.eventBus.get("yield:failed").pipe(
885
+ filter((e) => e.referenceId === annotationId$1)
886
+ );
887
+ const progressSub = progress$.pipe(takeUntil(merge(finished$, failed$))).subscribe((e) => subscriber.next(e));
888
+ const finishedSub = finished$.subscribe((event) => {
889
+ subscriber.next(event);
890
+ subscriber.complete();
891
+ if (event.resourceId && event.referenceId && event.sourceResourceId) {
892
+ this.eventBus.get("bind:update-body").next({
893
+ correlationId: crypto.randomUUID(),
894
+ annotationId: annotationId(event.referenceId),
895
+ resourceId: resourceId(event.sourceResourceId),
896
+ operations: [{ op: "add", item: { type: "SpecificResource", source: event.resourceId } }]
897
+ });
898
+ }
899
+ });
900
+ const failedSub = failed$.subscribe((e) => {
901
+ subscriber.error(new Error(e.error ?? e.message ?? "Generation failed"));
902
+ });
903
+ this.http.yieldResourceFromAnnotation(
904
+ resourceId$1,
905
+ annotationId$1,
906
+ options,
907
+ { auth: this.getToken() }
908
+ ).catch((error) => {
909
+ subscriber.error(error);
910
+ });
911
+ return () => {
912
+ progressSub.unsubscribe();
913
+ finishedSub.unsubscribe();
914
+ failedSub.unsubscribe();
915
+ };
1170
916
  });
1171
- eventBus.get("mark:entity-tag-added").subscribe((event) => {
1172
- if (event.resourceId) {
1173
- this.invalidateList(event.resourceId);
1174
- }
917
+ }
918
+ async cloneToken(resourceId) {
919
+ const result = await this.http.generateCloneToken(resourceId, { auth: this.getToken() });
920
+ return result;
921
+ }
922
+ async fromToken(token) {
923
+ const result = await this.http.getResourceByToken(token, { auth: this.getToken() });
924
+ return result.sourceResource;
925
+ }
926
+ async createFromToken(options) {
927
+ return this.http.createResourceFromToken(options, { auth: this.getToken() });
928
+ }
929
+ };
930
+
931
+ // src/namespaces/beckon.ts
932
+ var BeckonNamespace = class {
933
+ constructor(http, getToken) {
934
+ this.http = http;
935
+ this.getToken = getToken;
936
+ }
937
+ attention(annotationId, resourceId) {
938
+ this.http.beckonAttention(
939
+ "me",
940
+ // participantId — always 'me' for self-identification
941
+ { annotationId, resourceId },
942
+ { auth: this.getToken() }
943
+ ).catch(() => {
1175
944
  });
1176
- eventBus.get("mark:entity-tag-removed").subscribe((event) => {
1177
- if (event.resourceId) {
1178
- this.invalidateList(event.resourceId);
1179
- }
945
+ }
946
+ };
947
+
948
+ // src/namespaces/job.ts
949
+ var JobNamespace = class {
950
+ constructor(http, getToken) {
951
+ this.http = http;
952
+ this.getToken = getToken;
953
+ }
954
+ async status(jobId) {
955
+ return this.http.getJobStatus(jobId, { auth: this.getToken() });
956
+ }
957
+ async pollUntilComplete(jobId, options) {
958
+ return this.http.pollJobUntilComplete(jobId, {
959
+ interval: options?.interval,
960
+ timeout: options?.timeout,
961
+ onProgress: options?.onProgress,
962
+ auth: this.getToken()
1180
963
  });
1181
964
  }
1182
- /** Annotation list responses keyed by ResourceId */
1183
- list$ = new BehaviorSubject(/* @__PURE__ */ new Map());
1184
- /** Individual annotation details keyed by AnnotationId */
1185
- detail$ = new BehaviorSubject(/* @__PURE__ */ new Map());
1186
- /** Track in-flight fetches */
1187
- fetchingList = /* @__PURE__ */ new Set();
1188
- fetchingDetail = /* @__PURE__ */ new Set();
1189
- /** Memoized Observables — same instance returned for the same key */
1190
- listObs$ = /* @__PURE__ */ new Map();
1191
- detailObs$ = /* @__PURE__ */ new Map();
1192
- /** Mutable token getter — updated from the React layer when auth changes */
1193
- getToken = () => void 0;
1194
- /** Update the token getter (called from React when auth token changes) */
1195
- setTokenGetter(getter) {
1196
- this.getToken = getter;
965
+ async cancel(jobId, type) {
966
+ throw new Error(`Not implemented: job.cancel(${jobId}, ${type}) \u2014 needs EventBus wiring`);
1197
967
  }
1198
- /**
1199
- * Get annotations for a resource as an Observable.
1200
- * Triggers a fetch if not cached.
1201
- */
1202
- listForResource(resourceId) {
1203
- if (!this.list$.value.has(resourceId) && !this.fetchingList.has(resourceId)) {
1204
- this.fetchList(resourceId);
1205
- }
1206
- let obs = this.listObs$.get(resourceId);
1207
- if (!obs) {
1208
- obs = this.list$.pipe(map((m) => m.get(resourceId)), distinctUntilChanged());
1209
- this.listObs$.set(resourceId, obs);
1210
- }
1211
- return obs;
968
+ };
969
+ var AuthNamespace = class {
970
+ constructor(http, getToken) {
971
+ this.http = http;
972
+ this.getToken = getToken;
1212
973
  }
1213
- /**
1214
- * Get a single annotation detail as an Observable.
1215
- * Triggers a fetch if not cached.
1216
- */
1217
- get(resourceId, annotationId) {
1218
- if (!this.detail$.value.has(annotationId) && !this.fetchingDetail.has(annotationId)) {
1219
- this.fetchDetail(resourceId, annotationId);
1220
- }
1221
- let obs = this.detailObs$.get(annotationId);
1222
- if (!obs) {
1223
- obs = this.detail$.pipe(map((m) => m.get(annotationId)), distinctUntilChanged());
1224
- this.detailObs$.set(annotationId, obs);
1225
- }
1226
- return obs;
974
+ async password(emailStr, passwordStr) {
975
+ return this.http.authenticatePassword(email(emailStr), passwordStr);
1227
976
  }
1228
- /** Invalidate and re-fetch a resource's annotation list */
1229
- invalidateList(resourceId) {
1230
- const next = new Map(this.list$.value);
1231
- next.delete(resourceId);
1232
- this.list$.next(next);
1233
- this.fetchList(resourceId);
977
+ async google(credential) {
978
+ return this.http.authenticateGoogle(googleCredential(credential));
1234
979
  }
1235
- /** Invalidate a single annotation detail (re-fetched on next subscribe) */
1236
- invalidateDetail(annotationId) {
1237
- const next = new Map(this.detail$.value);
1238
- next.delete(annotationId);
1239
- this.detail$.next(next);
980
+ async refresh(token) {
981
+ return this.http.refreshToken(refreshToken(token));
1240
982
  }
1241
- /** Remove an annotation from the detail cache without re-fetching */
1242
- removeFromDetailCache(annotationId) {
1243
- const next = new Map(this.detail$.value);
1244
- next.delete(annotationId);
1245
- this.detail$.next(next);
983
+ async logout() {
984
+ await this.http.logout({ auth: this.getToken() });
1246
985
  }
1247
- async fetchList(resourceId) {
1248
- if (this.fetchingList.has(resourceId)) return;
1249
- this.fetchingList.add(resourceId);
1250
- try {
1251
- const result = await this.http.browseAnnotations(resourceId, void 0, { auth: this.getToken() });
1252
- const next = new Map(this.list$.value);
1253
- next.set(resourceId, result);
1254
- this.list$.next(next);
1255
- } catch {
1256
- } finally {
1257
- this.fetchingList.delete(resourceId);
1258
- }
986
+ async me() {
987
+ return this.http.getMe({ auth: this.getToken() });
1259
988
  }
1260
- async fetchDetail(resourceId, annotationId) {
1261
- if (this.fetchingDetail.has(annotationId)) return;
1262
- this.fetchingDetail.add(annotationId);
1263
- try {
1264
- const result = await this.http.browseAnnotation(resourceId, annotationId, { auth: this.getToken() });
1265
- const next = new Map(this.detail$.value);
1266
- next.set(annotationId, result);
1267
- this.detail$.next(next);
1268
- } catch {
1269
- } finally {
1270
- this.fetchingDetail.delete(annotationId);
1271
- }
989
+ async acceptTerms() {
990
+ await this.http.acceptTerms({ auth: this.getToken() });
991
+ }
992
+ async mcpToken() {
993
+ return this.http.generateMCPToken({ auth: this.getToken() });
994
+ }
995
+ async mediaToken(resourceId) {
996
+ return this.http.getMediaToken(resourceId, { auth: this.getToken() });
997
+ }
998
+ };
999
+
1000
+ // src/namespaces/admin.ts
1001
+ var AdminNamespace = class {
1002
+ constructor(http, getToken) {
1003
+ this.http = http;
1004
+ this.getToken = getToken;
1005
+ }
1006
+ async users() {
1007
+ const result = await this.http.listUsers({ auth: this.getToken() });
1008
+ return result.users;
1009
+ }
1010
+ async userStats() {
1011
+ return this.http.getUserStats({ auth: this.getToken() });
1012
+ }
1013
+ async updateUser(userId, data) {
1014
+ const result = await this.http.updateUser(userId, data, { auth: this.getToken() });
1015
+ return result.user;
1016
+ }
1017
+ async oauthConfig() {
1018
+ return this.http.getOAuthConfig({ auth: this.getToken() });
1019
+ }
1020
+ async healthCheck() {
1021
+ return this.http.healthCheck({ auth: this.getToken() });
1022
+ }
1023
+ async status() {
1024
+ return this.http.getStatus({ auth: this.getToken() });
1025
+ }
1026
+ async backup() {
1027
+ return this.http.backupKnowledgeBase({ auth: this.getToken() });
1028
+ }
1029
+ async restore(file, onProgress) {
1030
+ return this.http.restoreKnowledgeBase(file, { auth: this.getToken(), onProgress });
1031
+ }
1032
+ async exportKnowledgeBase(params) {
1033
+ return this.http.exportKnowledgeBase(params, { auth: this.getToken() });
1034
+ }
1035
+ async importKnowledgeBase(file, onProgress) {
1036
+ return this.http.importKnowledgeBase(file, { auth: this.getToken(), onProgress });
1272
1037
  }
1273
1038
  };
1274
1039
 
@@ -1288,6 +1053,11 @@ var SemiontApiClient = class {
1288
1053
  /** The workspace-scoped EventBus this client was constructed with. */
1289
1054
  eventBus;
1290
1055
  logger;
1056
+ /**
1057
+ * Shared mutable token getter. All verb namespaces read from this.
1058
+ * Updated via setTokenGetter() from the React auth layer.
1059
+ */
1060
+ _getToken = () => void 0;
1291
1061
  /**
1292
1062
  * SSE streaming client for real-time operations
1293
1063
  *
@@ -1295,25 +1065,30 @@ var SemiontApiClient = class {
1295
1065
  * Uses native fetch() instead of ky for SSE support.
1296
1066
  */
1297
1067
  sse;
1298
- /**
1299
- * Framework-agnostic flow orchestration.
1300
- * Each method returns a Subscription; call .unsubscribe() to tear down.
1301
- */
1302
- flows;
1303
- /**
1304
- * Per-workspace observable stores for entity data.
1305
- * Call stores.resources.setTokenGetter() / stores.annotations.setTokenGetter()
1306
- * from the React layer when the auth token changes.
1307
- */
1308
- stores;
1068
+ // ── Verb-oriented namespace API ──────────────────────────────────────────
1069
+ browse;
1070
+ mark;
1071
+ bind;
1072
+ gather;
1073
+ match;
1074
+ yield;
1075
+ beckon;
1076
+ job;
1077
+ auth;
1078
+ admin;
1309
1079
  constructor(config) {
1310
- const { baseUrl, eventBus, timeout: timeout2 = 3e4, retry = 2, logger } = config;
1080
+ const { baseUrl, eventBus, timeout = 3e4, retry = 2, logger, tokenRefresher } = config;
1311
1081
  this.eventBus = eventBus;
1312
1082
  this.logger = logger;
1313
1083
  this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
1084
+ const retryConfig = tokenRefresher ? {
1085
+ limit: 1,
1086
+ methods: ["get", "post", "put", "patch", "delete", "head", "options"],
1087
+ statusCodes: [401, 408, 413, 429, 500, 502, 503, 504]
1088
+ } : retry;
1314
1089
  this.http = ky.create({
1315
- timeout: timeout2,
1316
- retry,
1090
+ timeout,
1091
+ retry: retryConfig,
1317
1092
  credentials: "include",
1318
1093
  hooks: {
1319
1094
  beforeRequest: [
@@ -1329,6 +1104,21 @@ var SemiontApiClient = class {
1329
1104
  }
1330
1105
  }
1331
1106
  ],
1107
+ beforeRetry: tokenRefresher ? [
1108
+ async ({ request, error }) => {
1109
+ if (!(error instanceof HTTPError) || error.response.status !== 401) {
1110
+ return void 0;
1111
+ }
1112
+ try {
1113
+ const newToken = await tokenRefresher();
1114
+ if (!newToken) return ky.stop;
1115
+ request.headers.set("Authorization", `Bearer ${newToken}`);
1116
+ return void 0;
1117
+ } catch {
1118
+ return ky.stop;
1119
+ }
1120
+ }
1121
+ ] : [],
1332
1122
  afterResponse: [
1333
1123
  (request, _options, response) => {
1334
1124
  if (this.logger) {
@@ -1374,11 +1164,26 @@ var SemiontApiClient = class {
1374
1164
  baseUrl: this.baseUrl,
1375
1165
  logger: this.logger
1376
1166
  });
1377
- this.flows = new FlowEngine(this.eventBus, this.sse, this);
1378
- this.stores = {
1379
- resources: new ResourceStore(this, this.eventBus),
1380
- annotations: new AnnotationStore(this, this.eventBus)
1381
- };
1167
+ if (config.getToken) this._getToken = config.getToken;
1168
+ const getToken = () => this._getToken();
1169
+ this.browse = new BrowseNamespace(this, this.eventBus, getToken);
1170
+ this.mark = new MarkNamespace(this, this.eventBus, getToken);
1171
+ this.bind = new BindNamespace(this, getToken);
1172
+ this.gather = new GatherNamespace(this, this.eventBus, getToken);
1173
+ this.match = new MatchNamespace(this, this.eventBus, getToken);
1174
+ this.yield = new YieldNamespace(this, this.eventBus, getToken);
1175
+ this.beckon = new BeckonNamespace(this, getToken);
1176
+ this.job = new JobNamespace(this, getToken);
1177
+ this.auth = new AuthNamespace(this, getToken);
1178
+ this.admin = new AdminNamespace(this, getToken);
1179
+ }
1180
+ /**
1181
+ * Update the token getter for all verb namespaces.
1182
+ * Called from the React auth layer when the token changes.
1183
+ * All namespaces share this getter via closure — no per-namespace sync needed.
1184
+ */
1185
+ setTokenGetter(getter) {
1186
+ this._getToken = getter;
1382
1187
  }
1383
1188
  authHeaders(options) {
1384
1189
  return options?.auth ? { Authorization: `Bearer ${options.auth}` } : {};
@@ -1645,16 +1450,64 @@ var SemiontApiClient = class {
1645
1450
  });
1646
1451
  }
1647
1452
  async bindAnnotation(resourceId, annotationId, data, options) {
1648
- await this.http.put(`${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/body`, {
1649
- json: data,
1453
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/bind`, {
1454
+ json: { resourceId, ...data },
1650
1455
  headers: this.authHeaders(options)
1651
- });
1456
+ }).json();
1652
1457
  }
1653
1458
  async getAnnotationHistory(resourceId, annotationId, options) {
1654
1459
  return this.http.get(`${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/history`, {
1655
1460
  headers: this.authHeaders(options)
1656
1461
  }).json();
1657
1462
  }
1463
+ async annotateReferences(resourceId, data, options) {
1464
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotate-references`, {
1465
+ json: data,
1466
+ headers: this.authHeaders(options)
1467
+ }).json();
1468
+ }
1469
+ async annotateHighlights(resourceId, data, options) {
1470
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotate-highlights`, {
1471
+ json: data,
1472
+ headers: this.authHeaders(options)
1473
+ }).json();
1474
+ }
1475
+ async annotateAssessments(resourceId, data, options) {
1476
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotate-assessments`, {
1477
+ json: data,
1478
+ headers: this.authHeaders(options)
1479
+ }).json();
1480
+ }
1481
+ async annotateComments(resourceId, data, options) {
1482
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotate-comments`, {
1483
+ json: data,
1484
+ headers: this.authHeaders(options)
1485
+ }).json();
1486
+ }
1487
+ async annotateTags(resourceId, data, options) {
1488
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotate-tags`, {
1489
+ json: data,
1490
+ headers: this.authHeaders(options)
1491
+ }).json();
1492
+ }
1493
+ async yieldResourceFromAnnotation(resourceId, annotationId, data, options) {
1494
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/yield-resource`, {
1495
+ json: data,
1496
+ headers: this.authHeaders(options)
1497
+ }).json();
1498
+ }
1499
+ async gatherAnnotationContext(resourceId, annotationId, data, options) {
1500
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/annotations/${annotationId}/gather`, {
1501
+ json: data,
1502
+ headers: this.authHeaders(options)
1503
+ }).json();
1504
+ }
1505
+ async matchSearch(resourceId, data, options) {
1506
+ return this.http.post(`${this.baseUrl}/resources/${resourceId}/match-search`, {
1507
+ json: { resourceId, ...data },
1508
+ headers: this.authHeaders(options)
1509
+ }).json();
1510
+ }
1658
1511
  // ============================================================================
1659
1512
  // ENTITY TYPES
1660
1513
  // ============================================================================
@@ -1801,7 +1654,7 @@ var SemiontApiClient = class {
1801
1654
  */
1802
1655
  async pollJobUntilComplete(id, options) {
1803
1656
  const interval = options?.interval ?? 1e3;
1804
- const timeout2 = options?.timeout ?? 6e4;
1657
+ const timeout = options?.timeout ?? 6e4;
1805
1658
  const startTime = Date.now();
1806
1659
  while (true) {
1807
1660
  const status = await this.getJobStatus(id, { auth: options?.auth });
@@ -1811,8 +1664,8 @@ var SemiontApiClient = class {
1811
1664
  if (status.status === "complete" || status.status === "failed" || status.status === "cancelled") {
1812
1665
  return status;
1813
1666
  }
1814
- if (Date.now() - startTime > timeout2) {
1815
- throw new Error(`Job polling timeout after ${timeout2}ms`);
1667
+ if (Date.now() - startTime > timeout) {
1668
+ throw new Error(`Job polling timeout after ${timeout}ms`);
1816
1669
  }
1817
1670
  await new Promise((resolve) => setTimeout(resolve, interval));
1818
1671
  }
@@ -1840,273 +1693,6 @@ var SemiontApiClient = class {
1840
1693
  }).json();
1841
1694
  }
1842
1695
  };
1843
- async function eventBusRequest(eventBus, requestEvent, payload, successEvent, failureEvent, timeoutMs = 3e4) {
1844
- const { correlationId } = payload;
1845
- const result$ = merge(
1846
- eventBus.get(successEvent).pipe(
1847
- filter((e) => e.correlationId === correlationId),
1848
- map((e) => ({ ok: true, response: e.response }))
1849
- ),
1850
- eventBus.get(failureEvent).pipe(
1851
- filter((e) => e.correlationId === correlationId),
1852
- map((e) => ({ ok: false, error: e.error }))
1853
- )
1854
- ).pipe(take(1), timeout(timeoutMs));
1855
- const resultPromise = firstValueFrom(result$);
1856
- eventBus.get(requestEvent).next(payload);
1857
- const result = await resultPromise;
1858
- if (!result.ok) {
1859
- throw result.error;
1860
- }
1861
- return result.response;
1862
- }
1863
- var EventBusClient = class {
1864
- constructor(eventBus, timeoutMs = 3e4) {
1865
- this.eventBus = eventBus;
1866
- this.timeoutMs = timeoutMs;
1867
- }
1868
- // ========================================================================
1869
- // Browse Flow — Resource reads
1870
- // ========================================================================
1871
- async browseResource(resourceId) {
1872
- return eventBusRequest(
1873
- this.eventBus,
1874
- "browse:resource-requested",
1875
- { correlationId: crypto.randomUUID(), resourceId },
1876
- "browse:resource-result",
1877
- "browse:resource-failed",
1878
- this.timeoutMs
1879
- );
1880
- }
1881
- async browseResources(options) {
1882
- return eventBusRequest(
1883
- this.eventBus,
1884
- "browse:resources-requested",
1885
- { correlationId: crypto.randomUUID(), ...options },
1886
- "browse:resources-result",
1887
- "browse:resources-failed",
1888
- this.timeoutMs
1889
- );
1890
- }
1891
- // ========================================================================
1892
- // Browse Flow — Annotation reads
1893
- // ========================================================================
1894
- async getAnnotations(resourceId) {
1895
- return eventBusRequest(
1896
- this.eventBus,
1897
- "browse:annotations-requested",
1898
- { correlationId: crypto.randomUUID(), resourceId },
1899
- "browse:annotations-result",
1900
- "browse:annotations-failed",
1901
- this.timeoutMs
1902
- );
1903
- }
1904
- async getAnnotation(resourceId, annotationId) {
1905
- return eventBusRequest(
1906
- this.eventBus,
1907
- "browse:annotation-requested",
1908
- { correlationId: crypto.randomUUID(), resourceId, annotationId },
1909
- "browse:annotation-result",
1910
- "browse:annotation-failed",
1911
- this.timeoutMs
1912
- );
1913
- }
1914
- // ========================================================================
1915
- // Browse Flow — Event history
1916
- // ========================================================================
1917
- async getEvents(resourceId, options) {
1918
- return eventBusRequest(
1919
- this.eventBus,
1920
- "browse:events-requested",
1921
- { correlationId: crypto.randomUUID(), resourceId, ...options },
1922
- "browse:events-result",
1923
- "browse:events-failed",
1924
- this.timeoutMs
1925
- );
1926
- }
1927
- async getAnnotationHistory(resourceId, annotationId) {
1928
- return eventBusRequest(
1929
- this.eventBus,
1930
- "browse:annotation-history-requested",
1931
- { correlationId: crypto.randomUUID(), resourceId, annotationId },
1932
- "browse:annotation-history-result",
1933
- "browse:annotation-history-failed",
1934
- this.timeoutMs
1935
- );
1936
- }
1937
- // ========================================================================
1938
- // Bind Flow — Graph queries
1939
- // ========================================================================
1940
- async getReferencedBy(resourceId, motivation) {
1941
- return eventBusRequest(
1942
- this.eventBus,
1943
- "browse:referenced-by-requested",
1944
- { correlationId: crypto.randomUUID(), resourceId, motivation },
1945
- "browse:referenced-by-result",
1946
- "browse:referenced-by-failed",
1947
- this.timeoutMs
1948
- );
1949
- }
1950
- // ========================================================================
1951
- // Mark Flow — Entity types
1952
- // ========================================================================
1953
- async listEntityTypes() {
1954
- return eventBusRequest(
1955
- this.eventBus,
1956
- "browse:entity-types-requested",
1957
- { correlationId: crypto.randomUUID() },
1958
- "browse:entity-types-result",
1959
- "browse:entity-types-failed",
1960
- this.timeoutMs
1961
- );
1962
- }
1963
- addEntityType(tag, userId) {
1964
- this.eventBus.get("mark:add-entity-type").next({ tag, userId });
1965
- }
1966
- // ========================================================================
1967
- // Yield Flow — Clone tokens
1968
- // ========================================================================
1969
- async generateCloneToken(resourceId) {
1970
- return eventBusRequest(
1971
- this.eventBus,
1972
- "yield:clone-token-requested",
1973
- { correlationId: crypto.randomUUID(), resourceId },
1974
- "yield:clone-token-generated",
1975
- "yield:clone-token-failed",
1976
- this.timeoutMs
1977
- );
1978
- }
1979
- async getResourceByToken(token) {
1980
- return eventBusRequest(
1981
- this.eventBus,
1982
- "yield:clone-resource-requested",
1983
- { correlationId: crypto.randomUUID(), token },
1984
- "yield:clone-resource-result",
1985
- "yield:clone-resource-failed",
1986
- this.timeoutMs
1987
- );
1988
- }
1989
- async createResourceFromToken(options) {
1990
- return eventBusRequest(
1991
- this.eventBus,
1992
- "yield:clone-create",
1993
- { correlationId: crypto.randomUUID(), ...options },
1994
- "yield:clone-created",
1995
- "yield:clone-create-failed",
1996
- this.timeoutMs
1997
- );
1998
- }
1999
- // ========================================================================
2000
- // Job Control
2001
- // ========================================================================
2002
- async getJobStatus(jobId) {
2003
- return eventBusRequest(
2004
- this.eventBus,
2005
- "job:status-requested",
2006
- { correlationId: crypto.randomUUID(), jobId },
2007
- "job:status-result",
2008
- "job:status-failed",
2009
- this.timeoutMs
2010
- );
2011
- }
2012
- // ========================================================================
2013
- // Gather Flow — LLM context
2014
- // ========================================================================
2015
- async gatherAnnotation(annotationId, resourceId, options) {
2016
- const correlationId = crypto.randomUUID();
2017
- const result$ = merge(
2018
- this.eventBus.get("gather:complete").pipe(
2019
- filter((e) => e.correlationId === correlationId),
2020
- map((e) => ({ ok: true, response: e.response }))
2021
- ),
2022
- this.eventBus.get("gather:failed").pipe(
2023
- filter((e) => e.correlationId === correlationId),
2024
- map((e) => ({ ok: false, error: e.error }))
2025
- )
2026
- ).pipe(take(1), timeout(this.timeoutMs));
2027
- const resultPromise = firstValueFrom(result$);
2028
- this.eventBus.get("gather:requested").next({
2029
- correlationId,
2030
- annotationId,
2031
- resourceId,
2032
- options
2033
- });
2034
- const result = await resultPromise;
2035
- if (!result.ok) {
2036
- throw result.error;
2037
- }
2038
- return result.response;
2039
- }
2040
- async gatherResource(resourceId, options) {
2041
- const correlationId = crypto.randomUUID();
2042
- const result$ = merge(
2043
- this.eventBus.get("gather:resource-complete").pipe(
2044
- filter((e) => e.correlationId === correlationId),
2045
- map((e) => ({ ok: true, response: e.response }))
2046
- ),
2047
- this.eventBus.get("gather:resource-failed").pipe(
2048
- filter((e) => e.correlationId === correlationId),
2049
- map((e) => ({ ok: false, error: e.error }))
2050
- )
2051
- ).pipe(take(1), timeout(this.timeoutMs));
2052
- const resultPromise = firstValueFrom(result$);
2053
- this.eventBus.get("gather:resource-requested").next({
2054
- correlationId,
2055
- resourceId,
2056
- options
2057
- });
2058
- const result = await resultPromise;
2059
- if (!result.ok) {
2060
- throw result.error;
2061
- }
2062
- return result.response;
2063
- }
2064
- // ========================================================================
2065
- // Bind Flow — Search
2066
- // ========================================================================
2067
- async searchResources(searchTerm) {
2068
- const correlationId = crypto.randomUUID();
2069
- const referenceId = correlationId;
2070
- const result$ = merge(
2071
- this.eventBus.get("match:search-results").pipe(
2072
- filter((e) => e.correlationId === correlationId),
2073
- map((e) => ({ ok: true, response: e.response }))
2074
- ),
2075
- this.eventBus.get("match:search-failed").pipe(
2076
- filter((e) => e.correlationId === correlationId),
2077
- map((e) => ({ ok: false, error: new Error(e.error) }))
2078
- )
2079
- ).pipe(take(1), timeout(this.timeoutMs));
2080
- const resultPromise = firstValueFrom(result$);
2081
- this.eventBus.get("match:search-requested").next({
2082
- correlationId,
2083
- referenceId,
2084
- context: {
2085
- annotation: {
2086
- "@context": "http://www.w3.org/ns/anno.jsonld",
2087
- type: "Annotation",
2088
- id: referenceId,
2089
- motivation: "linking",
2090
- target: referenceId,
2091
- body: []
2092
- },
2093
- sourceResource: {
2094
- "@context": "https://schema.org",
2095
- "@id": referenceId,
2096
- name: searchTerm,
2097
- format: "text/plain",
2098
- representations: []
2099
- },
2100
- sourceContext: { selected: searchTerm }
2101
- }
2102
- });
2103
- const result = await resultPromise;
2104
- if (!result.ok) {
2105
- throw result.error;
2106
- }
2107
- return result.response;
2108
- }
2109
- };
2110
1696
  function getBodySource(body) {
2111
1697
  if (Array.isArray(body)) {
2112
1698
  for (const item of body) {
@@ -2749,14 +2335,14 @@ function isValidEmail(email) {
2749
2335
 
2750
2336
  // src/mime-utils.ts
2751
2337
  function getExtensionForMimeType(mimeType) {
2752
- const map4 = {
2338
+ const map3 = {
2753
2339
  "text/plain": "txt",
2754
2340
  "text/markdown": "md",
2755
2341
  "image/png": "png",
2756
2342
  "image/jpeg": "jpg",
2757
2343
  "application/pdf": "pdf"
2758
2344
  };
2759
- return map4[mimeType] || "dat";
2345
+ return map3[mimeType] || "dat";
2760
2346
  }
2761
2347
  function isImageMimeType(mimeType) {
2762
2348
  return mimeType === "image/png" || mimeType === "image/jpeg";
@@ -2777,6 +2363,6 @@ function getMimeCategory(mimeType) {
2777
2363
  return "unsupported";
2778
2364
  }
2779
2365
 
2780
- export { APIError, AnnotationStore, EventBusClient, FlowEngine, JWTTokenSchema, LOCALES, ResourceStore, SSEClient, SSE_STREAM_CONNECTED, SemiontApiClient, buildContentCache, createCircleSvg, createPolygonSvg, createRectangleSvg, decodeRepresentation, decodeWithCharset, extractBoundingBox, extractCharset, extractContext, findBestTextMatch, findTextWithContext, formatLocaleDisplay, getAllLocaleCodes, getAnnotationExactText, getBodySource, getBodyType, getChecksum, getCommentText, getCreator, getDerivedFrom, getExactText, getExtensionForMimeType, getLanguage, getLocaleEnglishName, getLocaleInfo, getLocaleNativeName, getMimeCategory, getNodeEncoding, getPrimaryMediaType, getPrimaryRepresentation, getPrimarySelector, getResourceEntityTypes, getResourceId, getStorageUri, getTargetSelector, getTargetSource, getTextQuoteSelector, hasTargetSelector, isArchived, isAssessment, isBodyResolved, isComment, isDraft, isHighlight, isImageMimeType, isPdfMimeType, isReference, isResolvedReference, isStubReference, isTag, isTextMimeType, isValidEmail, normalizeCoordinates, normalizeText, parseSvgSelector, scaleSvgToNative, validateAndCorrectOffsets, validateData, verifyPosition };
2366
+ export { APIError, AdminNamespace, AuthNamespace, BeckonNamespace, BindNamespace, BrowseNamespace, GatherNamespace, JWTTokenSchema, JobNamespace, LOCALES, MarkNamespace, MatchNamespace, SSEClient, SSE_STREAM_CONNECTED, SemiontApiClient, YieldNamespace, buildContentCache, createCircleSvg, createPolygonSvg, createRectangleSvg, decodeRepresentation, decodeWithCharset, extractBoundingBox, extractCharset, extractContext, findBestTextMatch, findTextWithContext, formatLocaleDisplay, getAllLocaleCodes, getAnnotationExactText, getBodySource, getBodyType, getChecksum, getCommentText, getCreator, getDerivedFrom, getExactText, getExtensionForMimeType, getLanguage, getLocaleEnglishName, getLocaleInfo, getLocaleNativeName, getMimeCategory, getNodeEncoding, getPrimaryMediaType, getPrimaryRepresentation, getPrimarySelector, getResourceEntityTypes, getResourceId, getStorageUri, getTargetSelector, getTargetSource, getTextQuoteSelector, hasTargetSelector, isArchived, isAssessment, isBodyResolved, isComment, isDraft, isHighlight, isImageMimeType, isPdfMimeType, isReference, isResolvedReference, isStubReference, isTag, isTextMimeType, isValidEmail, normalizeCoordinates, normalizeText, parseSvgSelector, scaleSvgToNative, validateAndCorrectOffsets, validateData, verifyPosition };
2781
2367
  //# sourceMappingURL=index.js.map
2782
2368
  //# sourceMappingURL=index.js.map