@semiont/sdk 0.5.0 → 0.5.2

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,13 +1,61 @@
1
- import { annotationId, resourceId, email, googleCredential, refreshToken, EventBus, accessToken, baseUrl, isHighlight, isComment, isAssessment, isReference, isTag, decodeWithCharset, getPrimaryMediaType, userDID, searchQuery } from '@semiont/core';
2
- import { merge, firstValueFrom, BehaviorSubject, map as map$1, distinctUntilChanged, Observable, Subject, Subscription, of, filter as filter$1, take as take$1, timeout as timeout$1 } from 'rxjs';
3
- import { filter, map, take, timeout, takeUntil, startWith, debounceTime, distinctUntilChanged as distinctUntilChanged$1, switchMap } from 'rxjs/operators';
4
- import { HttpTransport, HttpContentTransport, createActorVM, APIError } from '@semiont/api-client';
5
- export { APIError, DEGRADED_THRESHOLD_MS, HttpContentTransport, HttpTransport, createActorVM } from '@semiont/api-client';
1
+ import { SemiontError, annotationId, resourceId, email, googleCredential, refreshToken, EventBus, baseUrl, accessToken, searchQuery } from '@semiont/core';
2
+ export { SemiontError, accessToken, annotationId, baseUrl, entityType, refreshToken, resourceId, userId } from '@semiont/core';
3
+ import { Observable, lastValueFrom, firstValueFrom, merge, TimeoutError, throwError, map as map$1, BehaviorSubject, Subject, Subscription, of, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
4
+ export { firstValueFrom, lastValueFrom } from 'rxjs';
5
+ import { filter, map, take, timeout, catchError, takeUntil, startWith, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
6
+ import { HttpTransport, HttpContentTransport, APIError } from '@semiont/api-client';
7
+ export { APIError, HttpContentTransport, HttpTransport } from '@semiont/api-client';
6
8
 
7
9
  // src/client.ts
8
- var BusRequestError = class extends Error {
9
- constructor(message) {
10
- super(message);
10
+ var StreamObservable = class _StreamObservable extends Observable {
11
+ then(onfulfilled, onrejected) {
12
+ return lastValueFrom(this).then(onfulfilled, onrejected);
13
+ }
14
+ /** Wrap an existing Observable's subscribe behavior in a StreamObservable. */
15
+ static from(source) {
16
+ return new _StreamObservable((subscriber) => source.subscribe(subscriber));
17
+ }
18
+ };
19
+ var CacheObservable = class _CacheObservable extends Observable {
20
+ then(onfulfilled, onrejected) {
21
+ return firstValueFrom(this.pipe(filter((v) => v !== void 0))).then(onfulfilled, onrejected);
22
+ }
23
+ /**
24
+ * Wrap an existing Observable's subscribe behavior in a `CacheObservable`.
25
+ *
26
+ * Memoizes on source identity: passing the same `source` returns the same
27
+ * wrapper instance. The Browse cache primitive already returns a stable
28
+ * Observable per key (its B4 contract), so this preserves that contract
29
+ * through the awaitable wrapping. Without the memo, every public-method
30
+ * call would produce a fresh wrapper and break referential-equality
31
+ * guarantees that hook-style reactive consumers depend on.
32
+ *
33
+ * Backed by a `WeakMap`, so wrappers are GC'd when their source is.
34
+ */
35
+ static from(source) {
36
+ let wrapper = wrapperCache.get(source);
37
+ if (!wrapper) {
38
+ wrapper = new _CacheObservable((subscriber) => source.subscribe(subscriber));
39
+ wrapperCache.set(source, wrapper);
40
+ }
41
+ return wrapper;
42
+ }
43
+ };
44
+ var wrapperCache = /* @__PURE__ */ new WeakMap();
45
+ var UploadObservable = class extends Observable {
46
+ then(onfulfilled, onrejected) {
47
+ return lastValueFrom(this).then((v) => {
48
+ if (v.phase !== "finished") {
49
+ throw new Error(`UploadObservable resolved on a non-finished event: ${v.phase}`);
50
+ }
51
+ const result = { resourceId: v.resourceId };
52
+ return onfulfilled ? onfulfilled(result) : result;
53
+ }, onrejected);
54
+ }
55
+ };
56
+ var BusRequestError = class extends SemiontError {
57
+ constructor(message, code, details) {
58
+ super(message, code, details);
11
59
  this.name = "BusRequestError";
12
60
  }
13
61
  };
@@ -21,9 +69,31 @@ async function busRequest(bus, emitChannel, payload, resultChannel, failureChann
21
69
  ),
22
70
  bus.stream(failureChannel).pipe(
23
71
  filter((e) => e.correlationId === correlationId),
24
- map((e) => ({ ok: false, error: new BusRequestError(e.message) }))
72
+ map((e) => ({
73
+ ok: false,
74
+ error: new BusRequestError(e.message ?? "Bus request rejected", "bus.rejected", {
75
+ channel: failureChannel,
76
+ correlationId,
77
+ payload: e
78
+ })
79
+ }))
25
80
  )
26
- ).pipe(take(1), timeout(timeoutMs));
81
+ ).pipe(
82
+ take(1),
83
+ timeout(timeoutMs),
84
+ catchError((err) => {
85
+ if (err instanceof TimeoutError) {
86
+ return throwError(
87
+ () => new BusRequestError(
88
+ `Bus request timed out after ${timeoutMs}ms on ${resultChannel}`,
89
+ "bus.timeout",
90
+ { channel: emitChannel, resultChannel, correlationId, timeoutMs }
91
+ )
92
+ );
93
+ }
94
+ return throwError(() => err);
95
+ })
96
+ );
27
97
  const resultPromise = firstValueFrom(result$);
28
98
  await bus.emit(emitChannel, fullPayload);
29
99
  const result = await resultPromise;
@@ -58,7 +128,7 @@ function createCache(fetchFn) {
58
128
  if (!obs) {
59
129
  obs = store$.pipe(
60
130
  map$1((m) => m.get(key)),
61
- distinctUntilChanged()
131
+ distinctUntilChanged$1()
62
132
  );
63
133
  obsCache.set(key, obs);
64
134
  }
@@ -106,11 +176,6 @@ var BrowseNamespace = class {
106
176
  this.transport = transport;
107
177
  this.bus = bus;
108
178
  this.content = content;
109
- const g = globalThis;
110
- g.__SEMIONT_BROWSE_INSTANCES__ = (g.__SEMIONT_BROWSE_INSTANCES__ ?? 0) + 1;
111
- const browseSerial = g.__SEMIONT_BROWSE_INSTANCES__;
112
- this.__serial__ = browseSerial;
113
- console.debug(`[diag] BrowseNamespace #${browseSerial} constructed`);
114
179
  this.resourceCache = createCache(async (id) => {
115
180
  const result = await busRequest(
116
181
  this.transport,
@@ -133,32 +198,30 @@ var BrowseNamespace = class {
133
198
  );
134
199
  return result.resources;
135
200
  });
136
- this.annotationListCache = createCache(async (resourceId) => {
201
+ this.annotationListCache = createCache(async (resourceId2) => {
137
202
  return busRequest(
138
203
  this.transport,
139
204
  "browse:annotations-requested",
140
- { resourceId },
205
+ { resourceId: resourceId2 },
141
206
  "browse:annotations-result",
142
207
  "browse:annotations-failed"
143
208
  );
144
209
  });
145
- this.annotationDetailCache = createCache(async (annotationId) => {
146
- const resourceId = this.annotationResources.get(annotationId);
147
- if (!resourceId) {
148
- throw new Error(`Cannot fetch annotation ${annotationId}: no resourceId known`);
210
+ this.annotationDetailCache = createCache(async (annotationId2) => {
211
+ const resourceId2 = this.annotationResources.get(annotationId2);
212
+ if (!resourceId2) {
213
+ throw new Error(`Cannot fetch annotation ${annotationId2}: no resourceId known`);
149
214
  }
150
215
  const result = await busRequest(
151
216
  this.transport,
152
217
  "browse:annotation-requested",
153
- { resourceId, annotationId },
218
+ { resourceId: resourceId2, annotationId: annotationId2 },
154
219
  "browse:annotation-result",
155
220
  "browse:annotation-failed"
156
221
  );
157
222
  return result.annotation;
158
223
  });
159
224
  this.entityTypesCache = createCache(async () => {
160
- const serial = this.__serial__;
161
- console.debug(`[diag] BrowseNamespace#${serial} entityTypes fetchFn START`);
162
225
  const result = await busRequest(
163
226
  this.transport,
164
227
  "browse:entity-types-requested",
@@ -166,24 +229,23 @@ var BrowseNamespace = class {
166
229
  "browse:entity-types-result",
167
230
  "browse:entity-types-failed"
168
231
  );
169
- console.debug(`[diag] BrowseNamespace#${serial} entityTypes fetchFn RESOLVE`, JSON.stringify(result.entityTypes).slice(0, 200));
170
232
  return result.entityTypes;
171
233
  });
172
- this.referencedByCache = createCache(async (resourceId) => {
234
+ this.referencedByCache = createCache(async (resourceId2) => {
173
235
  const result = await busRequest(
174
236
  this.transport,
175
237
  "browse:referenced-by-requested",
176
- { resourceId },
238
+ { resourceId: resourceId2 },
177
239
  "browse:referenced-by-result",
178
240
  "browse:referenced-by-failed"
179
241
  );
180
242
  return result.referencedBy;
181
243
  });
182
- this.resourceEventsCache = createCache(async (resourceId) => {
244
+ this.resourceEventsCache = createCache(async (resourceId2) => {
183
245
  const result = await busRequest(
184
246
  this.transport,
185
247
  "browse:events-requested",
186
- { resourceId },
248
+ { resourceId: resourceId2 },
187
249
  "browse:events-result",
188
250
  "browse:events-failed"
189
251
  );
@@ -195,7 +257,7 @@ var BrowseNamespace = class {
195
257
  //
196
258
  // Each cache encapsulates the BehaviorSubject store, in-flight guard,
197
259
  // and per-key observable memoization that was previously open-coded
198
- // here. Behavioral contract: `packages/api-client/docs/CACHE-SEMANTICS.md`.
260
+ // here. Behavioral contract: `packages/sdk/docs/CACHE-SEMANTICS.md`.
199
261
  //
200
262
  // Public surface (`resource()`, `annotations()`, etc.) is unchanged;
201
263
  // the caches are an implementation detail of this namespace.
@@ -227,71 +289,66 @@ var BrowseNamespace = class {
227
289
  */
228
290
  annotationListObs = /* @__PURE__ */ new Map();
229
291
  // ── Live queries ────────────────────────────────────────────────────────
230
- resource(resourceId) {
231
- return this.resourceCache.observe(resourceId);
292
+ //
293
+ // These return `CacheObservable<T>`: subscribers see `T | undefined`
294
+ // (with `undefined` during initial load), and `await` resolves to the
295
+ // first non-undefined value.
296
+ resource(resourceId2) {
297
+ return CacheObservable.from(this.resourceCache.observe(resourceId2));
232
298
  }
233
299
  resources(filters) {
234
300
  const key = JSON.stringify(filters ?? {});
235
301
  this.resourceListFilters.set(key, filters ?? {});
236
- return this.resourceListCache.observe(key);
302
+ return CacheObservable.from(this.resourceListCache.observe(key));
237
303
  }
238
- annotations(resourceId) {
239
- let obs = this.annotationListObs.get(resourceId);
304
+ annotations(resourceId2) {
305
+ let obs = this.annotationListObs.get(resourceId2);
240
306
  if (!obs) {
241
- obs = this.annotationListCache.observe(resourceId).pipe(map$1((r) => r?.annotations));
242
- this.annotationListObs.set(resourceId, obs);
307
+ obs = this.annotationListCache.observe(resourceId2).pipe(map$1((r) => r?.annotations));
308
+ this.annotationListObs.set(resourceId2, obs);
243
309
  }
244
- return obs;
310
+ return CacheObservable.from(obs);
245
311
  }
246
- annotation(resourceId, annotationId) {
247
- this.annotationResources.set(annotationId, resourceId);
248
- return this.annotationDetailCache.observe(annotationId);
312
+ annotation(resourceId2, annotationId2) {
313
+ this.annotationResources.set(annotationId2, resourceId2);
314
+ return CacheObservable.from(this.annotationDetailCache.observe(annotationId2));
249
315
  }
250
316
  entityTypes() {
251
- const serial = this.__serial__;
252
- console.debug(`[diag] BrowseNamespace#${serial} entityTypes() called`);
253
- const self = this;
254
- if (!self.__entityTypesDiag__) {
255
- self.__entityTypesDiag__ = this.entityTypesCache.observe(ENTITY_TYPES_KEY).pipe(map$1((v) => {
256
- console.debug(`[diag] BrowseNamespace#${serial} entityTypes$ EMIT`, v === void 0 ? "undefined" : JSON.stringify(v).slice(0, 200));
257
- return v;
258
- }));
259
- }
260
- return self.__entityTypesDiag__;
317
+ return CacheObservable.from(this.entityTypesCache.observe(ENTITY_TYPES_KEY));
261
318
  }
262
- referencedBy(resourceId) {
263
- return this.referencedByCache.observe(resourceId);
319
+ referencedBy(resourceId2) {
320
+ return CacheObservable.from(this.referencedByCache.observe(resourceId2));
264
321
  }
265
- events(resourceId) {
266
- return this.resourceEventsCache.observe(resourceId);
322
+ events(resourceId2) {
323
+ return CacheObservable.from(this.resourceEventsCache.observe(resourceId2));
267
324
  }
268
325
  // ── One-shot reads ──────────────────────────────────────────────────────
269
- async resourceContent(resourceId) {
270
- const result = await this.content.getBinary(resourceId, { accept: "text/plain" });
326
+ async resourceContent(resourceId2) {
327
+ const result = await this.content.getBinary(resourceId2, { accept: "text/plain" });
271
328
  const decoder = new TextDecoder();
272
329
  return decoder.decode(result.data);
273
330
  }
274
- async resourceRepresentation(resourceId, options) {
275
- return this.content.getBinary(resourceId, options?.accept ? { accept: options.accept } : void 0);
331
+ async resourceRepresentation(resourceId2, options) {
332
+ return this.content.getBinary(resourceId2, options?.accept ? { accept: options.accept } : void 0);
276
333
  }
277
- async resourceRepresentationStream(resourceId, options) {
278
- return this.content.getBinaryStream(resourceId, options?.accept ? { accept: options.accept } : void 0);
334
+ async resourceRepresentationStream(resourceId2, options) {
335
+ return this.content.getBinaryStream(resourceId2, options?.accept ? { accept: options.accept } : void 0);
279
336
  }
280
- async resourceEvents(resourceId) {
337
+ async resourceEvents(resourceId2) {
281
338
  const result = await busRequest(
282
339
  this.transport,
283
340
  "browse:events-requested",
284
- { resourceId },
341
+ { resourceId: resourceId2 },
285
342
  "browse:events-result",
286
343
  "browse:events-failed"
287
344
  );
288
345
  return result.events;
289
346
  }
290
- async annotationHistory(resourceId, annotationId) {
347
+ async annotationHistory(resourceId2, annotationId2) {
291
348
  return busRequest(
292
349
  this.transport,
293
350
  "browse:annotation-history-requested",
294
- { resourceId, annotationId },
351
+ { resourceId: resourceId2, annotationId: annotationId2 },
295
352
  "browse:annotation-history-result",
296
353
  "browse:annotation-history-failed"
297
354
  );
@@ -315,11 +372,11 @@ var BrowseNamespace = class {
315
372
  );
316
373
  }
317
374
  // ── UI signals (local bus fan-out) ────────────────────────────────────
318
- click(annotationId, motivation) {
319
- this.bus.get("browse:click").next({ annotationId, motivation });
375
+ click(annotationId2, motivation) {
376
+ this.bus.get("browse:click").next({ annotationId: annotationId2, motivation });
320
377
  }
321
- navigateReference(resourceId) {
322
- this.bus.get("browse:reference-navigate").next({ resourceId });
378
+ navigateReference(resourceId2) {
379
+ this.bus.get("browse:reference-navigate").next({ resourceId: resourceId2 });
323
380
  }
324
381
  // ── Cache-mutation API (used by the bus-event subscribers below and by
325
382
  // other namespaces that know about specific updates) ─────────────────
@@ -327,12 +384,12 @@ var BrowseNamespace = class {
327
384
  // - `invalidate*` — SWR refetch (B7). Keeps prior value visible.
328
385
  // - `removeAnnotationDetail` — drops the entry (B13a: entity gone).
329
386
  // - `updateAnnotationInPlace` — write-through (B13b: new value known).
330
- invalidateAnnotationList(resourceId) {
331
- this.annotationListCache.invalidate(resourceId);
387
+ invalidateAnnotationList(resourceId2) {
388
+ this.annotationListCache.invalidate(resourceId2);
332
389
  }
333
- removeAnnotationDetail(annotationId) {
334
- this.annotationDetailCache.remove(annotationId);
335
- this.annotationResources.delete(annotationId);
390
+ removeAnnotationDetail(annotationId2) {
391
+ this.annotationDetailCache.remove(annotationId2);
392
+ this.annotationResources.delete(annotationId2);
336
393
  }
337
394
  invalidateResourceDetail(id) {
338
395
  this.resourceCache.invalidate(id);
@@ -343,21 +400,21 @@ var BrowseNamespace = class {
343
400
  invalidateEntityTypes() {
344
401
  this.entityTypesCache.invalidate(ENTITY_TYPES_KEY);
345
402
  }
346
- invalidateReferencedBy(resourceId) {
347
- this.referencedByCache.invalidate(resourceId);
403
+ invalidateReferencedBy(resourceId2) {
404
+ this.referencedByCache.invalidate(resourceId2);
348
405
  }
349
- invalidateResourceEvents(resourceId) {
350
- this.resourceEventsCache.invalidate(resourceId);
406
+ invalidateResourceEvents(resourceId2) {
407
+ this.resourceEventsCache.invalidate(resourceId2);
351
408
  }
352
- updateAnnotationInPlace(resourceId, annotation) {
353
- const currentList = this.annotationListCache.get(resourceId);
409
+ updateAnnotationInPlace(resourceId2, annotation) {
410
+ const currentList = this.annotationListCache.get(resourceId2);
354
411
  if (currentList) {
355
412
  const idx = currentList.annotations.findIndex((a) => a.id === annotation.id);
356
413
  const nextAnnotations = idx >= 0 ? currentList.annotations.map((a, i) => i === idx ? annotation : a) : [...currentList.annotations, annotation];
357
- this.annotationListCache.set(resourceId, { ...currentList, annotations: nextAnnotations });
414
+ this.annotationListCache.set(resourceId2, { ...currentList, annotations: nextAnnotations });
358
415
  }
359
416
  const aId = annotationId(annotation.id);
360
- this.annotationResources.set(aId, resourceId);
417
+ this.annotationResources.set(aId, resourceId2);
361
418
  this.annotationDetailCache.set(aId, annotation);
362
419
  }
363
420
  // ── EventBus subscriptions ──────────────────────────────────────────────
@@ -453,7 +510,7 @@ var BrowseNamespace = class {
453
510
  this.on("yield:update-ok", this.onYieldResourceMutated);
454
511
  this.on("mark:archived", this.onArchiveToggled);
455
512
  this.on("mark:unarchived", this.onArchiveToggled);
456
- this.on("mark:entity-type-added", () => this.invalidateEntityTypes());
513
+ this.on("frame:entity-type-added", () => this.invalidateEntityTypes());
457
514
  }
458
515
  };
459
516
  var MarkNamespace = class {
@@ -461,34 +518,28 @@ var MarkNamespace = class {
461
518
  this.transport = transport;
462
519
  this.bus = bus;
463
520
  }
464
- async annotation(resourceId, input) {
465
- return busRequest(
521
+ async annotation(input) {
522
+ const resourceId2 = resourceId(input.target.source);
523
+ const result = await busRequest(
466
524
  this.transport,
467
525
  "mark:create-request",
468
- { resourceId, request: input },
526
+ { resourceId: resourceId2, request: input },
469
527
  "mark:create-ok",
470
528
  "mark:create-failed"
471
529
  );
530
+ return { annotationId: annotationId(result.annotationId) };
472
531
  }
473
- async delete(resourceId, annotationId) {
474
- await this.transport.emit("mark:delete", { annotationId, resourceId });
475
- }
476
- async entityType(type) {
477
- await this.transport.emit("mark:add-entity-type", { tag: type });
478
- }
479
- async entityTypes(types) {
480
- for (const tag of types) {
481
- await this.transport.emit("mark:add-entity-type", { tag });
482
- }
532
+ async delete(resourceId2, annotationId2) {
533
+ await this.transport.emit("mark:delete", { annotationId: annotationId2, resourceId: resourceId2 });
483
534
  }
484
- async archive(resourceId) {
485
- await this.transport.emit("mark:archive", { resourceId });
535
+ async archive(resourceId2) {
536
+ await this.transport.emit("mark:archive", { resourceId: resourceId2 });
486
537
  }
487
- async unarchive(resourceId) {
488
- await this.transport.emit("mark:unarchive", { resourceId });
538
+ async unarchive(resourceId2) {
539
+ await this.transport.emit("mark:unarchive", { resourceId: resourceId2 });
489
540
  }
490
- assist(resourceId, motivation, options) {
491
- return new Observable((subscriber) => {
541
+ assist(resourceId2, motivation, options) {
542
+ return new StreamObservable((subscriber) => {
492
543
  let done = false;
493
544
  let pollTimer = null;
494
545
  let pollInterval = null;
@@ -528,7 +579,7 @@ var MarkNamespace = class {
528
579
  data: {
529
580
  jobId,
530
581
  jobType: status.jobType ?? "annotation",
531
- resourceId,
582
+ resourceId: resourceId2,
532
583
  result: status.result
533
584
  }
534
585
  });
@@ -565,7 +616,7 @@ var MarkNamespace = class {
565
616
  cleanup();
566
617
  subscriber.error(new Error(e.error));
567
618
  });
568
- this.dispatchAssist(resourceId, motivation, options).then(({ jobId }) => {
619
+ this.dispatchAssist(resourceId2, motivation, options).then(({ jobId }) => {
569
620
  if (jobId && !done) {
570
621
  activeJobId = jobId;
571
622
  resetPollTimer(jobId);
@@ -613,7 +664,7 @@ var MarkNamespace = class {
613
664
  toggleMode() {
614
665
  this.bus.get("mark:mode-toggled").next(void 0);
615
666
  }
616
- async dispatchAssist(resourceId, motivation, options) {
667
+ async dispatchAssist(resourceId2, motivation, options) {
617
668
  const jobTypeMap = {
618
669
  tagging: "tag-annotation",
619
670
  linking: "reference-annotation",
@@ -635,12 +686,13 @@ var MarkNamespace = class {
635
686
  if (options.density !== void 0) params.density = options.density;
636
687
  if (options.tone !== void 0) params.tone = options.tone;
637
688
  if (options.language !== void 0) params.language = options.language;
689
+ if (options.sourceLanguage !== void 0) params.sourceLanguage = options.sourceLanguage;
638
690
  if (options.schemaId !== void 0) params.schemaId = options.schemaId;
639
691
  if (options.categories !== void 0) params.categories = options.categories;
640
692
  return busRequest(
641
693
  this.transport,
642
694
  "job:create",
643
- { jobType, resourceId, params },
695
+ { jobType, resourceId: resourceId2, params },
644
696
  "job:created",
645
697
  "job:create-failed"
646
698
  );
@@ -653,11 +705,11 @@ var BindNamespace = class {
653
705
  this.transport = transport;
654
706
  this.bus = bus;
655
707
  }
656
- async body(resourceId, annotationId, operations) {
708
+ async body(resourceId2, annotationId2, operations) {
657
709
  await this.transport.emit("bind:update-body", {
658
710
  correlationId: crypto.randomUUID(),
659
- annotationId,
660
- resourceId,
711
+ annotationId: annotationId2,
712
+ resourceId: resourceId2,
661
713
  operations
662
714
  });
663
715
  }
@@ -670,8 +722,8 @@ var GatherNamespace = class {
670
722
  this.transport = transport;
671
723
  this.bus = bus;
672
724
  }
673
- annotation(annotationId, resourceId, options) {
674
- return new Observable((subscriber) => {
725
+ annotation(resourceId2, annotationId2, options) {
726
+ return new StreamObservable((subscriber) => {
675
727
  const correlationId = crypto.randomUUID();
676
728
  const complete$ = this.bus.get("gather:complete").pipe(
677
729
  filter((e) => e.correlationId === correlationId)
@@ -681,7 +733,7 @@ var GatherNamespace = class {
681
733
  );
682
734
  const sub = merge(
683
735
  this.bus.get("gather:annotation-progress").pipe(
684
- filter((e) => e.annotationId === annotationId),
736
+ filter((e) => e.annotationId === annotationId2),
685
737
  map((e) => e)
686
738
  ),
687
739
  complete$.pipe(map((e) => e))
@@ -698,8 +750,8 @@ var GatherNamespace = class {
698
750
  });
699
751
  this.transport.emit("gather:requested", {
700
752
  correlationId,
701
- annotationId,
702
- resourceId,
753
+ annotationId: annotationId2,
754
+ resourceId: resourceId2,
703
755
  options: { contextWindow: options?.contextWindow ?? 2e3 }
704
756
  }).catch((error) => {
705
757
  subscriber.error(error);
@@ -723,8 +775,8 @@ var MatchNamespace = class {
723
775
  requestSearch(input) {
724
776
  this.bus.get("match:search-requested").next(input);
725
777
  }
726
- search(resourceId, referenceId, context, options) {
727
- return new Observable((subscriber) => {
778
+ search(resourceId2, referenceId, context, options) {
779
+ return new StreamObservable((subscriber) => {
728
780
  const correlationId = crypto.randomUUID();
729
781
  const result$ = this.bus.get("match:search-results").pipe(
730
782
  filter((e) => e.correlationId === correlationId)
@@ -741,7 +793,7 @@ var MatchNamespace = class {
741
793
  });
742
794
  this.transport.emit("match:search-requested", {
743
795
  correlationId,
744
- resourceId,
796
+ resourceId: resourceId2,
745
797
  referenceId,
746
798
  context,
747
799
  limit: options?.limit ?? 10,
@@ -762,25 +814,56 @@ var YieldNamespace = class {
762
814
  this.bus = bus;
763
815
  this.content = content;
764
816
  }
765
- async resource(data) {
766
- const result = await this.content.putBinary({
767
- name: data.name,
768
- file: data.file,
769
- format: data.format,
770
- storageUri: data.storageUri,
771
- ...data.entityTypes ? { entityTypes: data.entityTypes } : {},
772
- ...data.language ? { language: data.language } : {},
773
- ...data.creationMethod ? { creationMethod: data.creationMethod } : {},
774
- ...data.sourceAnnotationId ? { sourceAnnotationId: data.sourceAnnotationId } : {},
775
- ...data.sourceResourceId ? { sourceResourceId: data.sourceResourceId } : {},
776
- ...data.generationPrompt ? { generationPrompt: data.generationPrompt } : {},
777
- ...data.generator ? { generator: data.generator } : {},
778
- ...data.isDraft !== void 0 ? { isDraft: data.isDraft } : {}
817
+ resource(data) {
818
+ const totalBytes = typeof Buffer !== "undefined" && data.file instanceof Buffer ? data.file.length : data.file.size;
819
+ return new UploadObservable((subscriber) => {
820
+ subscriber.next({ phase: "started", totalBytes });
821
+ let cancelled = false;
822
+ const abortController = new AbortController();
823
+ this.content.putBinary(
824
+ {
825
+ name: data.name,
826
+ file: data.file,
827
+ format: data.format,
828
+ storageUri: data.storageUri,
829
+ ...data.entityTypes ? { entityTypes: data.entityTypes } : {},
830
+ ...data.language ? { language: data.language } : {},
831
+ ...data.creationMethod ? { creationMethod: data.creationMethod } : {},
832
+ ...data.sourceAnnotationId ? { sourceAnnotationId: data.sourceAnnotationId } : {},
833
+ ...data.sourceResourceId ? { sourceResourceId: data.sourceResourceId } : {},
834
+ ...data.generationPrompt ? { generationPrompt: data.generationPrompt } : {},
835
+ ...data.generator ? { generator: data.generator } : {},
836
+ ...data.isDraft !== void 0 ? { isDraft: data.isDraft } : {}
837
+ },
838
+ {
839
+ // Byte-progress hook. Honored by `HttpContentTransport`'s XHR
840
+ // path; ignored by ky-path uploads (no `onProgress` consumer)
841
+ // and by `LocalContentTransport` (no wire to observe).
842
+ onProgress: ({ bytesUploaded, totalBytes: txTotal }) => {
843
+ if (cancelled) return;
844
+ const total = txTotal > 0 ? txTotal : totalBytes;
845
+ subscriber.next({ phase: "progress", bytesUploaded, totalBytes: total });
846
+ },
847
+ signal: abortController.signal
848
+ }
849
+ ).then((result) => {
850
+ if (cancelled) return;
851
+ subscriber.next({
852
+ phase: "finished",
853
+ resourceId: resourceId(result.resourceId)
854
+ });
855
+ subscriber.complete();
856
+ }).catch((err) => {
857
+ if (!cancelled) subscriber.error(err);
858
+ });
859
+ return () => {
860
+ cancelled = true;
861
+ abortController.abort();
862
+ };
779
863
  });
780
- return { resourceId: result.resourceId };
781
864
  }
782
- fromAnnotation(resourceId, annotationId, options) {
783
- return new Observable((subscriber) => {
865
+ fromAnnotation(resourceId2, annotationId2, options) {
866
+ return new StreamObservable((subscriber) => {
784
867
  let done = false;
785
868
  let pollTimer = null;
786
869
  let pollInterval = null;
@@ -820,7 +903,7 @@ var YieldNamespace = class {
820
903
  data: {
821
904
  jobId: jid,
822
905
  jobType: status.jobType ?? "generation",
823
- resourceId,
906
+ resourceId: resourceId2,
824
907
  result: status.result
825
908
  }
826
909
  });
@@ -862,12 +945,13 @@ var YieldNamespace = class {
862
945
  "job:create",
863
946
  {
864
947
  jobType: "generation",
865
- resourceId,
948
+ resourceId: resourceId2,
866
949
  params: {
867
- referenceId: annotationId,
950
+ referenceId: annotationId2,
868
951
  title: options.title,
869
952
  prompt: options.prompt,
870
953
  language: options.language,
954
+ sourceLanguage: options.sourceLanguage,
871
955
  temperature: options.temperature,
872
956
  maxTokens: options.maxTokens,
873
957
  storageUri: options.storageUri,
@@ -893,11 +977,11 @@ var YieldNamespace = class {
893
977
  };
894
978
  });
895
979
  }
896
- async cloneToken(resourceId) {
980
+ async cloneToken(resourceId2) {
897
981
  return busRequest(
898
982
  this.transport,
899
983
  "yield:clone-token-requested",
900
- { resourceId },
984
+ { resourceId: resourceId2 },
901
985
  "yield:clone-token-generated",
902
986
  "yield:clone-token-failed"
903
987
  );
@@ -913,13 +997,14 @@ var YieldNamespace = class {
913
997
  return result.sourceResource;
914
998
  }
915
999
  async createFromToken(options) {
916
- return busRequest(
1000
+ const result = await busRequest(
917
1001
  this.transport,
918
1002
  "yield:clone-create",
919
1003
  options,
920
1004
  "yield:clone-created",
921
1005
  "yield:clone-create-failed"
922
1006
  );
1007
+ return { resourceId: resourceId(result.resourceId) };
923
1008
  }
924
1009
  clone() {
925
1010
  this.bus.get("yield:clone").next(void 0);
@@ -932,14 +1017,29 @@ var BeckonNamespace = class {
932
1017
  this.transport = transport;
933
1018
  this.bus = bus;
934
1019
  }
935
- attention(annotationId, resourceId) {
936
- void this.transport.emit("beckon:focus", { annotationId, resourceId });
1020
+ attention(resourceId2, annotationId2) {
1021
+ void this.transport.emit("beckon:focus", { annotationId: annotationId2, resourceId: resourceId2 });
937
1022
  }
938
- hover(annotationId) {
939
- this.bus.get("beckon:hover").next({ annotationId });
1023
+ hover(annotationId2) {
1024
+ this.bus.get("beckon:hover").next({ annotationId: annotationId2 });
940
1025
  }
941
- sparkle(annotationId) {
942
- this.bus.get("beckon:sparkle").next({ annotationId });
1026
+ sparkle(annotationId2) {
1027
+ this.bus.get("beckon:sparkle").next({ annotationId: annotationId2 });
1028
+ }
1029
+ };
1030
+
1031
+ // src/namespaces/frame.ts
1032
+ var FrameNamespace = class {
1033
+ constructor(transport) {
1034
+ this.transport = transport;
1035
+ }
1036
+ async addEntityType(type) {
1037
+ await this.transport.emit("frame:add-entity-type", { tag: type });
1038
+ }
1039
+ async addEntityTypes(types) {
1040
+ for (const tag of types) {
1041
+ await this.transport.emit("frame:add-entity-type", { tag });
1042
+ }
943
1043
  }
944
1044
  };
945
1045
 
@@ -980,7 +1080,7 @@ var JobNamespace = class {
980
1080
  }
981
1081
  async pollUntilComplete(jobId, options) {
982
1082
  const interval = options?.interval ?? 1e3;
983
- const timeout7 = options?.timeout ?? 6e4;
1083
+ const timeout6 = options?.timeout ?? 6e4;
984
1084
  const startTime = Date.now();
985
1085
  while (true) {
986
1086
  const status = await this.status(jobId);
@@ -988,90 +1088,98 @@ var JobNamespace = class {
988
1088
  if (status.status === "complete" || status.status === "failed" || status.status === "cancelled") {
989
1089
  return status;
990
1090
  }
991
- if (Date.now() - startTime > timeout7) {
992
- throw new Error(`Job polling timeout after ${timeout7}ms`);
1091
+ if (Date.now() - startTime > timeout6) {
1092
+ throw new Error(`Job polling timeout after ${timeout6}ms`);
993
1093
  }
994
1094
  await new Promise((resolve) => setTimeout(resolve, interval));
995
1095
  }
996
1096
  }
997
- async cancel(_jobId, type) {
998
- await this.transport.emit("job:cancel-requested", {
999
- jobType: type === "generation" ? "generation" : "annotation"
1000
- });
1097
+ async cancelByType(jobType) {
1098
+ await this.transport.emit("job:cancel-requested", { jobType });
1001
1099
  }
1002
1100
  cancelRequest(jobType) {
1003
1101
  this.bus.get("job:cancel-requested").next({ jobType });
1004
1102
  }
1005
1103
  };
1006
1104
  var AuthNamespace = class {
1007
- constructor(transport) {
1008
- this.transport = transport;
1105
+ constructor(backend) {
1106
+ this.backend = backend;
1009
1107
  }
1010
1108
  async password(emailStr, passwordStr) {
1011
- return this.transport.authenticatePassword(email(emailStr), passwordStr);
1109
+ return this.backend.authenticatePassword(email(emailStr), passwordStr);
1012
1110
  }
1013
1111
  async google(credential) {
1014
- return this.transport.authenticateGoogle(googleCredential(credential));
1112
+ return this.backend.authenticateGoogle(googleCredential(credential));
1015
1113
  }
1016
1114
  async refresh(token) {
1017
- return this.transport.refreshAccessToken(refreshToken(token));
1115
+ return this.backend.refreshAccessToken(refreshToken(token));
1018
1116
  }
1019
1117
  async logout() {
1020
- await this.transport.logout();
1118
+ await this.backend.logout();
1021
1119
  }
1022
1120
  async me() {
1023
- return this.transport.getCurrentUser();
1121
+ return this.backend.getCurrentUser();
1024
1122
  }
1025
1123
  async acceptTerms() {
1026
- await this.transport.acceptTerms();
1124
+ await this.backend.acceptTerms();
1027
1125
  }
1028
1126
  async mcpToken() {
1029
- return this.transport.generateMcpToken();
1127
+ return this.backend.generateMcpToken();
1030
1128
  }
1031
- async mediaToken(resourceId) {
1032
- return this.transport.getMediaToken(resourceId);
1129
+ async mediaToken(resourceId2) {
1130
+ return this.backend.getMediaToken(resourceId2);
1033
1131
  }
1034
1132
  };
1035
1133
 
1036
1134
  // src/namespaces/admin.ts
1037
1135
  var AdminNamespace = class {
1038
- constructor(transport) {
1039
- this.transport = transport;
1136
+ constructor(backend) {
1137
+ this.backend = backend;
1040
1138
  }
1041
1139
  async users() {
1042
- const result = await this.transport.listUsers();
1140
+ const result = await this.backend.listUsers();
1043
1141
  return result.users;
1044
1142
  }
1045
1143
  async userStats() {
1046
- return this.transport.getUserStats();
1144
+ return this.backend.getUserStats();
1047
1145
  }
1048
- async updateUser(userId, data) {
1049
- const result = await this.transport.updateUser(userId, data);
1146
+ async updateUser(userId2, data) {
1147
+ const result = await this.backend.updateUser(userId2, data);
1050
1148
  return result.user;
1051
1149
  }
1052
1150
  async oauthConfig() {
1053
- return this.transport.getOAuthConfig();
1151
+ return this.backend.getOAuthConfig();
1054
1152
  }
1055
1153
  async healthCheck() {
1056
- return this.transport.healthCheck();
1154
+ return this.backend.healthCheck();
1057
1155
  }
1058
1156
  async status() {
1059
- return this.transport.getStatus();
1157
+ return this.backend.getStatus();
1060
1158
  }
1061
1159
  async backup() {
1062
- return this.transport.backupKnowledgeBase();
1160
+ return this.backend.backupKnowledgeBase();
1063
1161
  }
1064
- async restore(file, onProgress) {
1065
- return this.transport.restoreKnowledgeBase(file, onProgress);
1162
+ restore(file) {
1163
+ return wrapAsStream(this.backend.restoreKnowledgeBase(file));
1066
1164
  }
1067
1165
  async exportKnowledgeBase(params) {
1068
- return this.transport.exportKnowledgeBase(params);
1166
+ return this.backend.exportKnowledgeBase(params);
1069
1167
  }
1070
- async importKnowledgeBase(file, onProgress) {
1071
- return this.transport.importKnowledgeBase(file, onProgress);
1168
+ importKnowledgeBase(file) {
1169
+ return wrapAsStream(this.backend.importKnowledgeBase(file));
1072
1170
  }
1073
1171
  };
1074
- var SemiontClient = class {
1172
+ function wrapAsStream(source) {
1173
+ return new StreamObservable((subscriber) => {
1174
+ const sub = source.subscribe({
1175
+ next: (v) => subscriber.next(v),
1176
+ error: (e) => subscriber.error(e),
1177
+ complete: () => subscriber.complete()
1178
+ });
1179
+ return () => sub.unsubscribe();
1180
+ });
1181
+ }
1182
+ var SemiontClient = class _SemiontClient {
1075
1183
  /**
1076
1184
  * The wire-facing transport. Owns bus actor, HTTP, auth, admin, exchange,
1077
1185
  * system. Exposed for advanced consumers (workers, custom job adapters)
@@ -1090,6 +1198,15 @@ var SemiontClient = class {
1090
1198
  bus;
1091
1199
  baseUrl;
1092
1200
  // ── Verb-oriented namespace API ──────────────────────────────────────────
1201
+ //
1202
+ // The first nine namespaces are bus-driven and always present. `frame`
1203
+ // is the schema-layer flow's surface (eighth flow); the other eight are
1204
+ // content-layer flows plus `job`. `auth` and `admin` are backend-ops
1205
+ // namespaces — they're only constructed when the caller passes an
1206
+ // `IBackendOperations` instance to the constructor. A `SemiontClient`
1207
+ // over a transport-only setup (e.g. `LocalTransport`) has
1208
+ // `auth === undefined` / `admin === undefined`.
1209
+ frame;
1093
1210
  browse;
1094
1211
  mark;
1095
1212
  bind;
@@ -1112,13 +1229,20 @@ var SemiontClient = class {
1112
1229
  * Callers do not pass a bus in. If they need to interact with the bus
1113
1230
  * (e.g. for tests or to subscribe to arbitrary channels), they read it
1114
1231
  * back via `client.bus`.
1232
+ *
1233
+ * `backend` is optional. When provided, the `auth` and `admin`
1234
+ * namespaces are constructed against it; when omitted, they're
1235
+ * `undefined`. For HTTP setups this is conventionally the same
1236
+ * `HttpTransport` instance that's also passed as `transport` (HTTP
1237
+ * implements both `ITransport` and `IBackendOperations`).
1115
1238
  */
1116
- constructor(transport, content) {
1239
+ constructor(transport, content, backend) {
1117
1240
  this.transport = transport;
1118
1241
  this.content = content;
1119
1242
  this.baseUrl = transport.baseUrl;
1120
1243
  this.bus = new EventBus();
1121
1244
  this.transport.bridgeInto(this.bus);
1245
+ this.frame = new FrameNamespace(this.transport);
1122
1246
  this.browse = new BrowseNamespace(this.transport, this.bus, this.content);
1123
1247
  this.mark = new MarkNamespace(this.transport, this.bus);
1124
1248
  this.bind = new BindNamespace(this.transport, this.bus);
@@ -1127,20 +1251,88 @@ var SemiontClient = class {
1127
1251
  this.yield = new YieldNamespace(this.transport, this.bus, this.content);
1128
1252
  this.beckon = new BeckonNamespace(this.transport, this.bus);
1129
1253
  this.job = new JobNamespace(this.transport, this.bus);
1130
- this.auth = new AuthNamespace(this.transport);
1131
- this.admin = new AdminNamespace(this.transport);
1254
+ this.auth = backend ? new AuthNamespace(backend) : void 0;
1255
+ this.admin = backend ? new AdminNamespace(backend) : void 0;
1132
1256
  }
1133
1257
  /** Transport-level connection state. HTTP reflects SSE health; local is always 'connected'. */
1134
1258
  get state$() {
1135
1259
  return this.transport.state$;
1136
1260
  }
1137
- subscribeToResource(resourceId) {
1138
- return this.transport.subscribeToResource(resourceId);
1261
+ subscribeToResource(resourceId2) {
1262
+ return this.transport.subscribeToResource(resourceId2);
1139
1263
  }
1140
1264
  dispose() {
1141
1265
  this.transport.dispose();
1142
1266
  this.content.dispose();
1143
1267
  }
1268
+ /**
1269
+ * Convenience factory for the default HTTP setup. Constructs a
1270
+ * `BehaviorSubject<AccessToken | null>` internally, plus an
1271
+ * `HttpTransport` and `HttpContentTransport`, and returns the wired
1272
+ * `SemiontClient`.
1273
+ *
1274
+ * Use this for one-shot scripts, CLI commands, or any consumer that
1275
+ * doesn't need to drive the token from outside (no manual refresh,
1276
+ * no cross-tab sync). For long-running scripts that need refresh,
1277
+ * use `SemiontSession.fromHttp(...)` (with a token already on hand)
1278
+ * or `SemiontSession.signInHttp(...)` (credentials-first) instead —
1279
+ * either owns the same transport/client wiring plus the
1280
+ * proactive-refresh + storage machinery.
1281
+ *
1282
+ * Strings are accepted for `baseUrl` and `token`; they are branded
1283
+ * via `baseUrl()` / `accessToken()` from `@semiont/core` automatically.
1284
+ * Pass the already-branded values if you have them.
1285
+ *
1286
+ * Omit `token` for unauthenticated usage (public endpoints only).
1287
+ */
1288
+ static fromHttp(opts) {
1289
+ const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
1290
+ const tok = opts.token == null ? null : typeof opts.token === "string" ? accessToken(opts.token) : opts.token;
1291
+ const token$ = new BehaviorSubject(tok);
1292
+ const transport = new HttpTransport({ baseUrl: url, token$ });
1293
+ const content = new HttpContentTransport(transport);
1294
+ return new _SemiontClient(transport, content, transport);
1295
+ }
1296
+ /**
1297
+ * Async factory for the credentials-first script case. Builds a
1298
+ * transient HTTP transport, calls `auth.password(email, password)`
1299
+ * to acquire an access token, and returns the wired client with
1300
+ * the token populated.
1301
+ *
1302
+ * This is the right entry point for skills, CLI scripts, and any
1303
+ * consumer that starts with email + password rather than a JWT
1304
+ * already on hand. For consumers that already hold a token (CLI
1305
+ * cached-token path, env-var token, embedded auth flow), use
1306
+ * `fromHttp({ baseUrl, token })` instead.
1307
+ *
1308
+ * For long-running scripts that need refresh, use
1309
+ * `SemiontSession.signInHttp(...)` — same credentials shape, plus
1310
+ * the session machinery for proactive refresh and persistence.
1311
+ *
1312
+ * Named `signInHttp` because email+password authentication is
1313
+ * inherently an HTTP-shaped operation in the current backend; an
1314
+ * in-process `LocalTransport` doesn't have a credentials login
1315
+ * path. Non-HTTP transports construct the client directly from
1316
+ * their package's transport instance.
1317
+ *
1318
+ * Throws if authentication fails. The transient client is disposed
1319
+ * before the throw, so no resources leak on failure.
1320
+ */
1321
+ static async signInHttp(opts) {
1322
+ const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
1323
+ const token$ = new BehaviorSubject(null);
1324
+ const transport = new HttpTransport({ baseUrl: url, token$ });
1325
+ const content = new HttpContentTransport(transport);
1326
+ const client = new _SemiontClient(transport, content, transport);
1327
+ try {
1328
+ const auth = await client.auth.password(opts.email, opts.password);
1329
+ token$.next(accessToken(auth.token));
1330
+ return client;
1331
+ } catch (err) {
1332
+ client.dispose();
1333
+ throw err;
1334
+ }
1335
+ }
1144
1336
  };
1145
1337
 
1146
1338
  // src/session/storage.ts
@@ -1185,35 +1377,28 @@ function isJwtExpired(token) {
1185
1377
  if (!expiry) return true;
1186
1378
  return expiry.getTime() < Date.now();
1187
1379
  }
1188
- function migrateLegacyEntry(entry) {
1189
- if (entry.host !== void 0) return entry;
1190
- try {
1191
- const url = new URL(entry.backendUrl);
1192
- return {
1193
- id: entry.id,
1194
- label: entry.label,
1195
- host: url.hostname,
1196
- port: parseInt(url.port, 10) || (url.protocol === "https:" ? 443 : 80),
1197
- protocol: url.protocol === "https:" ? "https" : "http",
1198
- email: ""
1199
- };
1200
- } catch {
1201
- return {
1202
- id: entry.id,
1203
- label: entry.label || "Unknown",
1204
- host: "localhost",
1205
- port: 4e3,
1206
- protocol: "http",
1207
- email: ""
1208
- };
1380
+ function isKnowledgeBase(entry) {
1381
+ if (!entry || typeof entry !== "object") return false;
1382
+ const e = entry;
1383
+ if (typeof e.id !== "string" || typeof e.label !== "string" || typeof e.email !== "string") {
1384
+ return false;
1385
+ }
1386
+ const ep = e.endpoint;
1387
+ if (!ep || typeof ep !== "object") return false;
1388
+ if (ep.kind === "http") {
1389
+ return typeof ep.host === "string" && typeof ep.port === "number" && (ep.protocol === "http" || ep.protocol === "https");
1390
+ }
1391
+ if (ep.kind === "local") {
1392
+ return typeof ep.kbId === "string";
1209
1393
  }
1394
+ return false;
1210
1395
  }
1211
1396
  function loadKnowledgeBases(storage) {
1212
1397
  try {
1213
1398
  const raw = storage.get(STORAGE_KEY);
1214
1399
  if (!raw) return [];
1215
1400
  const entries = JSON.parse(raw);
1216
- return entries.map(migrateLegacyEntry);
1401
+ return entries.filter(isKnowledgeBase);
1217
1402
  } catch {
1218
1403
  return [];
1219
1404
  }
@@ -1228,39 +1413,47 @@ var HOSTNAME_RE = /^(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]
1228
1413
  function isValidHostname(host) {
1229
1414
  return HOSTNAME_RE.test(host);
1230
1415
  }
1231
- function kbBackendUrl(kb) {
1232
- if (!isValidHostname(kb.host)) {
1233
- throw new Error(`Invalid KB hostname: "${kb.host}"`);
1416
+ function kbBackendUrl(endpoint) {
1417
+ if (!isValidHostname(endpoint.host)) {
1418
+ throw new Error(`Invalid KB hostname: "${endpoint.host}"`);
1234
1419
  }
1235
1420
  const url = new URL("http://x");
1236
- url.protocol = kb.protocol + ":";
1237
- url.hostname = kb.host;
1238
- url.port = String(kb.port);
1239
- return `${kb.protocol}://${url.hostname}:${kb.port}`;
1421
+ url.protocol = endpoint.protocol + ":";
1422
+ url.hostname = endpoint.host;
1423
+ url.port = String(endpoint.port);
1424
+ return `${endpoint.protocol}://${url.hostname}:${endpoint.port}`;
1240
1425
  }
1241
1426
  function generateKbId() {
1242
1427
  return crypto.randomUUID();
1243
1428
  }
1244
-
1245
- // src/session/errors.ts
1246
- var SemiontError = class extends Error {
1247
- code;
1429
+ var SemiontSessionError = class extends SemiontError {
1248
1430
  kbId;
1249
1431
  constructor(code, message, kbId = null) {
1250
- super(message);
1251
- this.name = "SemiontError";
1252
- this.code = code;
1432
+ super(message, code, { kbId });
1433
+ this.name = "SemiontSessionError";
1253
1434
  this.kbId = kbId;
1254
1435
  }
1255
1436
  };
1256
1437
 
1257
1438
  // src/session/semiont-session.ts
1258
- var SemiontSession = class {
1439
+ var SemiontSession = class _SemiontSession {
1259
1440
  kb;
1260
1441
  client;
1261
1442
  token$;
1262
1443
  user$;
1263
1444
  streamState$;
1445
+ /**
1446
+ * Stream of `SemiontError` instances surfaced by the underlying transport
1447
+ * just before they're thrown to the caller. For `HttpTransport` this is
1448
+ * an `APIError` (status-coded); other transports emit their own subclass.
1449
+ * Surfaced here so a host layer (e.g. `SemiontBrowser`) can route by
1450
+ * `err.code` to global notifications without every call site handling
1451
+ * errors itself. Headless consumers can subscribe for logging.
1452
+ *
1453
+ * Re-published from `client.transport.errors$` per the `ITransport`
1454
+ * contract — the session is purely a passthrough.
1455
+ */
1456
+ errors$;
1264
1457
  /** Resolves after the initial validation round-trip completes (success or failure). */
1265
1458
  ready;
1266
1459
  storage;
@@ -1283,6 +1476,7 @@ var SemiontSession = class {
1283
1476
  this.client = config.client;
1284
1477
  this.token$ = config.token$;
1285
1478
  this.user$ = new BehaviorSubject(null);
1479
+ this.errors$ = this.client.transport.errors$;
1286
1480
  const stored = getStoredSession(this.storage, this.kb.id);
1287
1481
  if (stored && !isJwtExpired(stored.access) && this.token$.getValue() === null) {
1288
1482
  this.token$.next(accessToken(stored.access));
@@ -1344,7 +1538,7 @@ var SemiontSession = class {
1344
1538
  this.onAuthFailed("Your session has expired. Please sign in again.");
1345
1539
  } else {
1346
1540
  this.onError(
1347
- new SemiontError(
1541
+ new SemiontSessionError(
1348
1542
  "session.auth-failed",
1349
1543
  err instanceof Error ? err.message : String(err),
1350
1544
  this.kb.id
@@ -1377,7 +1571,7 @@ var SemiontSession = class {
1377
1571
  clearStoredSession(this.storage, this.kb.id);
1378
1572
  this.onAuthFailed("Your session has expired. Please sign in again.");
1379
1573
  this.onError(
1380
- new SemiontError("session.refresh-exhausted", "Token refresh failed", this.kb.id)
1574
+ new SemiontSessionError("session.refresh-exhausted", "Token refresh failed", this.kb.id)
1381
1575
  );
1382
1576
  return null;
1383
1577
  }
@@ -1448,26 +1642,102 @@ var SemiontSession = class {
1448
1642
  this.token$.complete();
1449
1643
  this.user$.complete();
1450
1644
  }
1645
+ /**
1646
+ * Convenience factory for the default HTTP setup. Constructs the
1647
+ * shared `BehaviorSubject<AccessToken | null>`, an `HttpTransport`,
1648
+ * an `HttpContentTransport`, and a `SemiontClient`, then wires
1649
+ * the session over them. Removes the load-bearing
1650
+ * "same-token$-instance" invariant from the caller's hands.
1651
+ *
1652
+ * Strings are accepted for `baseUrl` and `token`; they are branded
1653
+ * via `baseUrl()` / `accessToken()` from `@semiont/core` automatically.
1654
+ *
1655
+ * The remaining options (`refresh`, `validate`, `onAuthFailed`,
1656
+ * `onError`) match `SemiontSessionConfig` exactly.
1657
+ */
1658
+ static fromHttp(opts) {
1659
+ const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
1660
+ const tok = opts.token == null ? null : typeof opts.token === "string" ? accessToken(opts.token) : opts.token;
1661
+ const token$ = new BehaviorSubject(tok);
1662
+ const transport = new HttpTransport({ baseUrl: url, token$ });
1663
+ const content = new HttpContentTransport(transport);
1664
+ const client = new SemiontClient(transport, content, transport);
1665
+ const config = { kb: opts.kb, storage: opts.storage, client, token$ };
1666
+ if (opts.refresh) config.refresh = opts.refresh;
1667
+ if (opts.validate) config.validate = opts.validate;
1668
+ if (opts.onAuthFailed) config.onAuthFailed = opts.onAuthFailed;
1669
+ if (opts.onError) config.onError = opts.onError;
1670
+ return new _SemiontSession(config);
1671
+ }
1672
+ /**
1673
+ * Async factory for the credentials-first long-running script case.
1674
+ * Builds the HTTP transport stack, calls `auth.password(email,
1675
+ * password)` to acquire access + refresh tokens, persists them via
1676
+ * the storage adapter, wires a default `refresh` callback that
1677
+ * exchanges the refresh token via `auth.refresh(...)`, and returns
1678
+ * the ready session.
1679
+ *
1680
+ * The consumer-supplied `refresh` callback becomes optional — only
1681
+ * needed for non-standard refresh flows (worker-pool shared secret,
1682
+ * OAuth refresh-token grant, interactive re-prompt). The default
1683
+ * uses the refresh token returned by `auth.password`.
1684
+ *
1685
+ * `kb` is required and must be a full `KnowledgeBase`. The `id` field
1686
+ * is the storage key for this session — distinct scripts sharing the
1687
+ * same `SessionStorage` instance must use distinct ids to avoid
1688
+ * trampling each other's tokens. The factory does not synthesize a
1689
+ * default; the consumer makes the choice.
1690
+ *
1691
+ * Named `signInHttp` because email+password authentication is
1692
+ * inherently an HTTP-shaped operation in the current backend; an
1693
+ * in-process `LocalTransport` doesn't have a credentials login
1694
+ * path. Non-HTTP transports construct the session directly from
1695
+ * their package's transport instance.
1696
+ *
1697
+ * Throws on auth failure with no resources leaked. On success, the
1698
+ * returned session's `ready` promise has already resolved.
1699
+ */
1700
+ static async signInHttp(opts) {
1701
+ const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
1702
+ const token$ = new BehaviorSubject(null);
1703
+ const transport = new HttpTransport({ baseUrl: url, token$ });
1704
+ const content = new HttpContentTransport(transport);
1705
+ const client = new SemiontClient(transport, content, transport);
1706
+ let auth;
1707
+ try {
1708
+ auth = await client.auth.password(opts.email, opts.password);
1709
+ } catch (err) {
1710
+ client.dispose();
1711
+ throw err;
1712
+ }
1713
+ setStoredSession(opts.storage, opts.kb.id, { access: auth.token, refresh: auth.refreshToken });
1714
+ token$.next(accessToken(auth.token));
1715
+ const defaultRefresh = async () => {
1716
+ const stored = getStoredSession(opts.storage, opts.kb.id);
1717
+ if (!stored) return null;
1718
+ try {
1719
+ const response = await client.auth.refresh(stored.refresh);
1720
+ return response.access_token;
1721
+ } catch {
1722
+ return null;
1723
+ }
1724
+ };
1725
+ const config = {
1726
+ kb: opts.kb,
1727
+ storage: opts.storage,
1728
+ client,
1729
+ token$,
1730
+ refresh: defaultRefresh
1731
+ };
1732
+ if (opts.validate) config.validate = opts.validate;
1733
+ if (opts.onAuthFailed) config.onAuthFailed = opts.onAuthFailed;
1734
+ if (opts.onError) config.onError = opts.onError;
1735
+ const session = new _SemiontSession(config);
1736
+ await session.ready;
1737
+ return session;
1738
+ }
1451
1739
  };
1452
-
1453
- // src/session/notify.ts
1454
- var activeOnSessionExpired = null;
1455
- var activeOnPermissionDenied = null;
1456
- function notifySessionExpired(message) {
1457
- activeOnSessionExpired?.(message);
1458
- }
1459
- function notifyPermissionDenied(message) {
1460
- activeOnPermissionDenied?.(message);
1461
- }
1462
- function registerAuthNotifyHandlers(handlers) {
1463
- activeOnSessionExpired = handlers.onSessionExpired;
1464
- activeOnPermissionDenied = handlers.onPermissionDenied;
1465
- return () => {
1466
- activeOnSessionExpired = null;
1467
- activeOnPermissionDenied = null;
1468
- };
1469
- }
1470
- var FrontendSessionSignals = class {
1740
+ var SessionSignals = class {
1471
1741
  sessionExpiredAt$;
1472
1742
  sessionExpiredMessage$;
1473
1743
  permissionDeniedAt$;
@@ -1532,7 +1802,7 @@ var SemiontBrowser = class {
1532
1802
  * non-null when `activeSession$` is non-null, always null when it
1533
1803
  * is. Extracted from the session itself so headless sessions
1534
1804
  * (workers, CLIs, tests) don't carry dead modal observables.
1535
- * See [FrontendSessionSignals](./frontend-session-signals.ts).
1805
+ * See [SessionSignals](./session-signals.ts).
1536
1806
  */
1537
1807
  activeSignals$;
1538
1808
  /**
@@ -1548,6 +1818,7 @@ var SemiontBrowser = class {
1548
1818
  error$;
1549
1819
  identityToken$;
1550
1820
  storage;
1821
+ sessionFactory;
1551
1822
  /**
1552
1823
  * App-scoped EventBus. Hosts UI-shell events that must work regardless
1553
1824
  * of whether a KB session is active: panel toggles, sidebar state,
@@ -1556,20 +1827,12 @@ var SemiontBrowser = class {
1556
1827
  * (mark:*, beckon:*, gather:*, match:*, bind:*, yield:*, browse:click).
1557
1828
  */
1558
1829
  eventBus = new EventBus();
1559
- unregisterNotify = null;
1560
1830
  unsubscribeStorage = null;
1561
1831
  disposed = false;
1562
1832
  activating = null;
1563
- /**
1564
- * Per-KB in-flight refresh dedup. Simultaneous 401s for the same
1565
- * KB converge on a single `/api/tokens/refresh` network call.
1566
- * Was previously module-scoped in `refresh.ts`; moved here when
1567
- * that file was deleted — SemiontBrowser is a singleton so the
1568
- * scoping is equivalent.
1569
- */
1570
- inFlightRefreshes = /* @__PURE__ */ new Map();
1571
1833
  constructor(config) {
1572
1834
  this.storage = config.storage;
1835
+ this.sessionFactory = config.sessionFactory;
1573
1836
  const kbs = loadKnowledgeBases(this.storage);
1574
1837
  const storedActive = this.storage.get(ACTIVE_KEY);
1575
1838
  const initialActive = storedActive && kbs.some((kb) => kb.id === storedActive) ? storedActive : kbs[0]?.id ?? null;
@@ -1596,14 +1859,6 @@ var SemiontBrowser = class {
1596
1859
  } catch {
1597
1860
  }
1598
1861
  }) ?? null;
1599
- this.unregisterNotify = registerAuthNotifyHandlers({
1600
- onSessionExpired: (message) => {
1601
- this.activeSignals$.getValue()?.notifySessionExpired(message ?? null);
1602
- },
1603
- onPermissionDenied: (message) => {
1604
- this.activeSignals$.getValue()?.notifyPermissionDenied(message ?? null);
1605
- }
1606
- });
1607
1862
  if (initialActive) {
1608
1863
  void this.setActiveKb(initialActive);
1609
1864
  }
@@ -1625,9 +1880,10 @@ var SemiontBrowser = class {
1625
1880
  }
1626
1881
  // ── Identity token (NextAuth bridge; D1) ──────────────────────────────
1627
1882
  /**
1628
- * Set the app-level identity token (from NextAuth's useSession).
1629
- * Called at the root layout via a single `useEffect`. No other site
1630
- * in the codebase should call this.
1883
+ * Set the app-level identity token. Sourced from whatever the host
1884
+ * environment uses for OAuth sessions (e.g. NextAuth in a browser app).
1885
+ * Should be called once from the host's startup-and-on-change site;
1886
+ * no other code should write to this slot.
1631
1887
  */
1632
1888
  setIdentityToken(token) {
1633
1889
  if (this.disposed) return;
@@ -1649,6 +1905,12 @@ var SemiontBrowser = class {
1649
1905
  void this.setActiveKb(next[0]?.id ?? null);
1650
1906
  }
1651
1907
  }
1908
+ /**
1909
+ * Patch a KB in the list. Restricted to the common, endpoint-agnostic
1910
+ * fields (`label`, `email`, `gitBranch`) — the `endpoint` shape isn't
1911
+ * editable in place; remove and re-add to change the connection
1912
+ * target.
1913
+ */
1652
1914
  updateKb(id, updates) {
1653
1915
  this.kbs$.next(
1654
1916
  this.kbs$.getValue().map((kb) => kb.id === id ? { ...kb, ...updates } : kb)
@@ -1704,31 +1966,38 @@ var SemiontBrowser = class {
1704
1966
  if (!id) return;
1705
1967
  const kb = this.kbs$.getValue().find((k) => k.id === id);
1706
1968
  if (!kb) return;
1707
- const signals = new FrontendSessionSignals();
1708
- const token$ = new BehaviorSubject(null);
1969
+ const signals = new SessionSignals();
1709
1970
  let session;
1710
- const transport = new HttpTransport({
1711
- baseUrl: baseUrl(kbBackendUrl(kb)),
1712
- token$,
1713
- tokenRefresher: () => session.refresh().then((t) => t ?? null)
1714
- });
1715
- const content = new HttpContentTransport(transport);
1716
- const client = new SemiontClient(transport, content);
1717
- session = new SemiontSession({
1718
- kb,
1719
- storage: this.storage,
1720
- client,
1721
- token$,
1722
- refresh: () => this.performRefresh(kb),
1723
- validate: (token) => this.performValidate(kb, token),
1724
- onAuthFailed: (msg) => signals.notifySessionExpired(msg),
1725
- onError: (err) => this.error$.next(err)
1971
+ try {
1972
+ session = this.sessionFactory({
1973
+ kb,
1974
+ storage: this.storage,
1975
+ signals,
1976
+ onError: (err) => this.error$.next(err)
1977
+ });
1978
+ } catch (err) {
1979
+ this.error$.next(
1980
+ err instanceof SemiontSessionError ? err : new SemiontSessionError(
1981
+ "session.construct-failed",
1982
+ err instanceof Error ? err.message : String(err),
1983
+ id
1984
+ )
1985
+ );
1986
+ signals.dispose();
1987
+ return;
1988
+ }
1989
+ session.errors$.subscribe((err) => {
1990
+ if (err.code === "unauthorized") {
1991
+ signals.notifySessionExpired(err.message);
1992
+ } else if (err.code === "forbidden") {
1993
+ signals.notifyPermissionDenied(err.message);
1994
+ }
1726
1995
  });
1727
1996
  try {
1728
1997
  await session.ready;
1729
1998
  } catch (err) {
1730
1999
  this.error$.next(
1731
- new SemiontError(
2000
+ new SemiontSessionError(
1732
2001
  "session.construct-failed",
1733
2002
  err instanceof Error ? err.message : String(err),
1734
2003
  id
@@ -1838,80 +2107,10 @@ var SemiontBrowser = class {
1838
2107
  if (moved) list.splice(newIndex, 0, moved);
1839
2108
  this.openResources$.next(list);
1840
2109
  }
1841
- // ── Auth callbacks bound per session ──────────────────────────────────
1842
- //
1843
- // These closures back the `refresh` and `validate` callbacks passed
1844
- // to `SemiontSession` in `setActiveKb`. Factored out as methods
1845
- // (rather than inline in the activation closure) so test-doubles
1846
- // can override them cleanly, and so the in-flight dedup map
1847
- // survives across activations of the same KB.
1848
- /**
1849
- * Refresh the active KB's access token. Returns the new token on
1850
- * success, null on failure. Concurrent calls for the same KB
1851
- * dedupe through `inFlightRefreshes`, so simultaneous 401s trigger
1852
- * only one `/api/tokens/refresh` round trip.
1853
- *
1854
- * Uses a throwaway `SemiontClient` with no `tokenRefresher` —
1855
- * a refresh call returning 401 would otherwise re-enter this
1856
- * function infinitely.
1857
- */
1858
- async performRefresh(kb) {
1859
- const existing = this.inFlightRefreshes.get(kb.id);
1860
- if (existing) return existing;
1861
- const promise = (async () => {
1862
- const stored = getStoredSession(this.storage, kb.id);
1863
- if (!stored) return null;
1864
- const throwawayTransport = new HttpTransport({ baseUrl: baseUrl(kbBackendUrl(kb)) });
1865
- const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport));
1866
- try {
1867
- const response = await throwaway.auth.refresh(stored.refresh);
1868
- const newAccess = response.access_token;
1869
- if (!newAccess) return null;
1870
- setStoredSession(this.storage, kb.id, { access: newAccess, refresh: stored.refresh });
1871
- return newAccess;
1872
- } catch {
1873
- return null;
1874
- } finally {
1875
- throwaway.dispose();
1876
- }
1877
- })();
1878
- this.inFlightRefreshes.set(kb.id, promise);
1879
- try {
1880
- return await promise;
1881
- } finally {
1882
- this.inFlightRefreshes.delete(kb.id);
1883
- }
1884
- }
1885
- /**
1886
- * Validate an access token by calling `auth.me` on a throwaway
1887
- * client. The session uses this once at startup to populate
1888
- * `user$`; 401 triggers a refresh-then-retry inside the session.
1889
- *
1890
- * The throwaway transport is seeded with the specific token to
1891
- * validate so the request actually carries it (HttpTransport
1892
- * sources `Authorization` from its `token$`).
1893
- */
1894
- async performValidate(kb, token) {
1895
- const tokenSubject = new BehaviorSubject(token);
1896
- const throwawayTransport = new HttpTransport({
1897
- baseUrl: baseUrl(kbBackendUrl(kb)),
1898
- token$: tokenSubject
1899
- });
1900
- const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport));
1901
- try {
1902
- const data = await throwaway.auth.me();
1903
- return data;
1904
- } finally {
1905
- throwaway.dispose();
1906
- tokenSubject.complete();
1907
- }
1908
- }
1909
2110
  // ── Lifecycle ─────────────────────────────────────────────────────────
1910
2111
  async dispose() {
1911
2112
  if (this.disposed) return;
1912
2113
  this.disposed = true;
1913
- this.unregisterNotify?.();
1914
- this.unregisterNotify = null;
1915
2114
  if (this.unsubscribeStorage) {
1916
2115
  this.unsubscribeStorage();
1917
2116
  this.unsubscribeStorage = null;
@@ -1932,12 +2131,91 @@ var SemiontBrowser = class {
1932
2131
  this.eventBus.destroy();
1933
2132
  }
1934
2133
  };
2134
+ function createHttpSessionFactory() {
2135
+ const inFlightRefreshes = /* @__PURE__ */ new Map();
2136
+ return (opts) => {
2137
+ const { kb, storage, signals, onError } = opts;
2138
+ if (kb.endpoint.kind !== "http") {
2139
+ throw new SemiontSessionError(
2140
+ "session.construct-failed",
2141
+ `HTTP session factory cannot construct a session for endpoint kind "${kb.endpoint.kind}"`,
2142
+ kb.id
2143
+ );
2144
+ }
2145
+ const endpoint = kb.endpoint;
2146
+ const performRefresh = async () => {
2147
+ const existing = inFlightRefreshes.get(kb.id);
2148
+ if (existing) return existing;
2149
+ const promise = (async () => {
2150
+ const stored = getStoredSession(storage, kb.id);
2151
+ if (!stored) return null;
2152
+ const throwawayTransport = new HttpTransport({ baseUrl: baseUrl(kbBackendUrl(endpoint)) });
2153
+ const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport), throwawayTransport);
2154
+ try {
2155
+ const response = await throwaway.auth.refresh(stored.refresh);
2156
+ const newAccess = response.access_token;
2157
+ if (!newAccess) return null;
2158
+ setStoredSession(storage, kb.id, { access: newAccess, refresh: stored.refresh });
2159
+ return newAccess;
2160
+ } catch {
2161
+ return null;
2162
+ } finally {
2163
+ throwaway.dispose();
2164
+ }
2165
+ })();
2166
+ inFlightRefreshes.set(kb.id, promise);
2167
+ try {
2168
+ return await promise;
2169
+ } finally {
2170
+ inFlightRefreshes.delete(kb.id);
2171
+ }
2172
+ };
2173
+ const performValidate = async (token) => {
2174
+ const tokenSubject = new BehaviorSubject(token);
2175
+ const throwawayTransport = new HttpTransport({
2176
+ baseUrl: baseUrl(kbBackendUrl(endpoint)),
2177
+ token$: tokenSubject
2178
+ });
2179
+ const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport), throwawayTransport);
2180
+ try {
2181
+ const data = await throwaway.auth.me();
2182
+ return data;
2183
+ } finally {
2184
+ throwaway.dispose();
2185
+ tokenSubject.complete();
2186
+ }
2187
+ };
2188
+ const token$ = new BehaviorSubject(null);
2189
+ let session;
2190
+ const transport = new HttpTransport({
2191
+ baseUrl: baseUrl(kbBackendUrl(endpoint)),
2192
+ token$,
2193
+ tokenRefresher: () => session.refresh().then((t) => t ?? null)
2194
+ });
2195
+ const content = new HttpContentTransport(transport);
2196
+ const client = new SemiontClient(transport, content, transport);
2197
+ session = new SemiontSession({
2198
+ kb,
2199
+ storage,
2200
+ client,
2201
+ token$,
2202
+ refresh: performRefresh,
2203
+ validate: performValidate,
2204
+ onAuthFailed: (msg) => signals.notifySessionExpired(msg),
2205
+ onError
2206
+ });
2207
+ return session;
2208
+ };
2209
+ }
1935
2210
 
1936
2211
  // src/session/registry.ts
1937
2212
  var instance = null;
1938
2213
  function getBrowser(options) {
1939
2214
  if (!instance) {
1940
- instance = new SemiontBrowser({ storage: options.storage });
2215
+ instance = new SemiontBrowser({
2216
+ storage: options.storage,
2217
+ sessionFactory: options.sessionFactory
2218
+ });
1941
2219
  }
1942
2220
  return instance;
1943
2221
  }
@@ -1955,6 +2233,18 @@ var InMemorySessionStorage = class {
1955
2233
  this.map.delete(key);
1956
2234
  }
1957
2235
  };
2236
+
2237
+ // src/session/knowledge-base.ts
2238
+ function httpKb(opts) {
2239
+ const { id, label, email, host, port, protocol, gitBranch } = opts;
2240
+ return {
2241
+ id,
2242
+ label,
2243
+ email,
2244
+ ...gitBranch !== void 0 ? { gitBranch } : {},
2245
+ endpoint: { kind: "http", host, port, protocol }
2246
+ };
2247
+ }
1958
2248
  function createDisposer() {
1959
2249
  const sub = new Subscription();
1960
2250
  return {
@@ -1970,7 +2260,7 @@ function createSearchPipeline(fetch, options = {}) {
1970
2260
  const state$ = input$.pipe(
1971
2261
  startWith(initial),
1972
2262
  debounceTime(debounceMs),
1973
- distinctUntilChanged$1(),
2263
+ distinctUntilChanged(),
1974
2264
  switchMap((q) => {
1975
2265
  const trimmed = q.trim();
1976
2266
  if (!trimmed) {
@@ -1992,23 +2282,23 @@ function createSearchPipeline(fetch, options = {}) {
1992
2282
  dispose: () => input$.complete()
1993
2283
  };
1994
2284
  }
1995
- function createBeckonVM(client) {
2285
+ function createBeckonStateUnit(client) {
1996
2286
  const subs = [];
1997
2287
  const hovered$ = new BehaviorSubject(null);
1998
- subs.push(client.bus.get("beckon:hover").subscribe(({ annotationId }) => {
1999
- hovered$.next(annotationId);
2000
- if (annotationId) {
2001
- client.bus.get("beckon:sparkle").next({ annotationId });
2288
+ subs.push(client.bus.get("beckon:hover").subscribe(({ annotationId: annotationId2 }) => {
2289
+ hovered$.next(annotationId2);
2290
+ if (annotationId2) {
2291
+ client.bus.get("beckon:sparkle").next({ annotationId: annotationId2 });
2002
2292
  }
2003
2293
  }));
2004
- subs.push(client.bus.get("browse:click").subscribe(({ annotationId }) => {
2005
- client.bus.get("beckon:focus").next({ annotationId });
2294
+ subs.push(client.bus.get("browse:click").subscribe(({ annotationId: annotationId2 }) => {
2295
+ client.bus.get("beckon:focus").next({ annotationId: annotationId2 });
2006
2296
  }));
2007
2297
  return {
2008
2298
  hoveredAnnotationId$: hovered$.asObservable(),
2009
- hover: (annotationId) => client.bus.get("beckon:hover").next({ annotationId }),
2010
- focus: (annotationId) => client.bus.get("beckon:focus").next({ annotationId }),
2011
- sparkle: (annotationId) => client.bus.get("beckon:sparkle").next({ annotationId }),
2299
+ hover: (annotationId2) => client.bus.get("beckon:hover").next({ annotationId: annotationId2 }),
2300
+ focus: (annotationId2) => client.bus.get("beckon:focus").next({ annotationId: annotationId2 }),
2301
+ sparkle: (annotationId2) => client.bus.get("beckon:sparkle").next({ annotationId: annotationId2 }),
2012
2302
  dispose() {
2013
2303
  subs.forEach((s) => s.unsubscribe());
2014
2304
  hovered$.complete();
@@ -2025,13 +2315,13 @@ function createHoverHandlers(emit, delayMs) {
2025
2315
  timer = null;
2026
2316
  }
2027
2317
  };
2028
- const handleMouseEnter = (annotationId) => {
2029
- if (currentHover === annotationId) return;
2318
+ const handleMouseEnter = (annotationId2) => {
2319
+ if (currentHover === annotationId2) return;
2030
2320
  cancelTimer();
2031
2321
  timer = setTimeout(() => {
2032
2322
  timer = null;
2033
- currentHover = annotationId;
2034
- emit(annotationId);
2323
+ currentHover = annotationId2;
2324
+ emit(annotationId2);
2035
2325
  }, delayMs);
2036
2326
  };
2037
2327
  const handleMouseLeave = () => {
@@ -2043,59 +2333,7 @@ function createHoverHandlers(emit, delayMs) {
2043
2333
  };
2044
2334
  return { handleMouseEnter, handleMouseLeave, cleanup: cancelTimer };
2045
2335
  }
2046
- var COMMON_PANELS = ["knowledge-base", "user", "settings"];
2047
- var RESOURCE_PANELS = ["history", "info", "annotations", "collaboration", "jsonld"];
2048
- var MOTIVATION_TO_TAB = {
2049
- "linking": "reference",
2050
- "commenting": "comment",
2051
- "tagging": "tag",
2052
- "highlighting": "highlight",
2053
- "assessing": "assessment"
2054
- };
2055
- var tabGenerationCounter = 0;
2056
- function createShellVM(browser, options) {
2057
- const subs = [];
2058
- const activePanel$ = new BehaviorSubject(options?.initialPanel ?? null);
2059
- const scrollToAnnotationId$ = new BehaviorSubject(null);
2060
- const panelInitialTab$ = new BehaviorSubject(null);
2061
- if (options?.onPanelChange) {
2062
- const cb = options.onPanelChange;
2063
- subs.push(activePanel$.subscribe(cb));
2064
- }
2065
- subs.push(browser.stream("panel:toggle").subscribe(({ panel }) => {
2066
- const current = activePanel$.getValue();
2067
- activePanel$.next(current === panel ? null : panel);
2068
- }));
2069
- subs.push(browser.stream("panel:open").subscribe(({ panel, scrollToAnnotationId, motivation }) => {
2070
- if (scrollToAnnotationId) {
2071
- scrollToAnnotationId$.next(scrollToAnnotationId);
2072
- }
2073
- if (motivation) {
2074
- const tab = MOTIVATION_TO_TAB[motivation] || "highlight";
2075
- panelInitialTab$.next({ tab, generation: ++tabGenerationCounter });
2076
- }
2077
- activePanel$.next(panel);
2078
- }));
2079
- subs.push(browser.stream("panel:close").subscribe(() => {
2080
- activePanel$.next(null);
2081
- }));
2082
- return {
2083
- activePanel$: activePanel$.asObservable(),
2084
- scrollToAnnotationId$: scrollToAnnotationId$.asObservable(),
2085
- panelInitialTab$: panelInitialTab$.asObservable(),
2086
- openPanel: (panel) => browser.emit("panel:open", { panel }),
2087
- closePanel: () => browser.emit("panel:close", void 0),
2088
- togglePanel: (panel) => browser.emit("panel:toggle", { panel }),
2089
- onScrollCompleted: () => scrollToAnnotationId$.next(null),
2090
- dispose() {
2091
- subs.forEach((s) => s.unsubscribe());
2092
- activePanel$.complete();
2093
- scrollToAnnotationId$.complete();
2094
- panelInitialTab$.complete();
2095
- }
2096
- };
2097
- }
2098
- function createGatherVM(client, resourceId) {
2336
+ function createGatherStateUnit(client, resourceId2) {
2099
2337
  const subs = [];
2100
2338
  const context$ = new BehaviorSubject(null);
2101
2339
  const loading$ = new BehaviorSubject(false);
@@ -2107,8 +2345,8 @@ function createGatherVM(client, resourceId) {
2107
2345
  context$.next(null);
2108
2346
  annotationId$.next(annotationId(event.annotationId));
2109
2347
  const gatherSub = client.gather.annotation(
2348
+ resourceId2,
2110
2349
  annotationId(event.annotationId),
2111
- resourceId,
2112
2350
  { contextWindow: event.options?.contextWindow ?? 2e3 }
2113
2351
  ).pipe(
2114
2352
  timeout(6e4)
@@ -2145,12 +2383,12 @@ function createGatherVM(client, resourceId) {
2145
2383
  }
2146
2384
  };
2147
2385
  }
2148
- function createMatchVM(client, _resourceId) {
2386
+ function createMatchStateUnit(client, _resourceId) {
2149
2387
  const subs = [];
2150
2388
  subs.push(client.bus.get("match:search-requested").subscribe((event) => {
2151
2389
  const searchSub = client.match.search(
2152
2390
  resourceId(event.resourceId),
2153
- event.referenceId,
2391
+ annotationId(event.referenceId),
2154
2392
  event.context,
2155
2393
  { limit: event.limit, useSemanticScoring: event.useSemanticScoring }
2156
2394
  ).pipe(
@@ -2171,14 +2409,14 @@ function createMatchVM(client, _resourceId) {
2171
2409
  }
2172
2410
  };
2173
2411
  }
2174
- function createYieldVM(client, resourceId$1, locale) {
2412
+ function createYieldStateUnit(client, resourceId2, locale) {
2175
2413
  const subs = [];
2176
2414
  const isGenerating$ = new BehaviorSubject(false);
2177
2415
  const progress$ = new BehaviorSubject(null);
2178
2416
  let clearTimer = null;
2179
2417
  const generate = (referenceId, options) => {
2180
2418
  const genSub = client.yield.fromAnnotation(
2181
- resourceId(resourceId$1),
2419
+ resourceId(resourceId2),
2182
2420
  annotationId(referenceId),
2183
2421
  { ...options, language: options.language || locale }
2184
2422
  ).pipe(
@@ -2226,7 +2464,7 @@ function selectionToSelector(selection) {
2226
2464
  }
2227
2465
  return { type: "TextQuoteSelector", exact: selection.exact, ...selection.prefix && { prefix: selection.prefix }, ...selection.suffix && { suffix: selection.suffix } };
2228
2466
  }
2229
- function createMarkVM(client, resourceId) {
2467
+ function createMarkStateUnit(client, resourceId2) {
2230
2468
  const subs = [];
2231
2469
  const pendingAnnotation$ = new BehaviorSubject(null);
2232
2470
  const assistingMotivation$ = new BehaviorSubject(null);
@@ -2250,9 +2488,9 @@ function createMarkVM(client, resourceId) {
2250
2488
  subs.push(client.bus.get("mark:create-ok").subscribe(() => pendingAnnotation$.next(null)));
2251
2489
  subs.push(client.bus.get("mark:submit").subscribe(async (event) => {
2252
2490
  try {
2253
- const result = await client.mark.annotation(resourceId, {
2491
+ const result = await client.mark.annotation({
2254
2492
  motivation: event.motivation,
2255
- target: { source: resourceId, selector: event.selector },
2493
+ target: { source: resourceId2, selector: event.selector },
2256
2494
  body: event.body
2257
2495
  });
2258
2496
  client.bus.get("mark:create-ok").next({ annotationId: result.annotationId });
@@ -2262,7 +2500,7 @@ function createMarkVM(client, resourceId) {
2262
2500
  }));
2263
2501
  subs.push(client.bus.get("mark:delete").subscribe(async (event) => {
2264
2502
  try {
2265
- await client.mark.delete(resourceId, event.annotationId);
2503
+ await client.mark.delete(resourceId2, event.annotationId);
2266
2504
  client.bus.get("mark:delete-ok").next({ annotationId: event.annotationId });
2267
2505
  } catch (error) {
2268
2506
  client.bus.get("mark:delete-failed").next({ message: error instanceof Error ? error.message : String(error) });
@@ -2272,7 +2510,7 @@ function createMarkVM(client, resourceId) {
2272
2510
  clearProgressTimer();
2273
2511
  assistingMotivation$.next(event.motivation);
2274
2512
  progress$.next(null);
2275
- const assistSub = client.mark.assist(resourceId, event.motivation, event.options).pipe(
2513
+ const assistSub = client.mark.assist(resourceId2, event.motivation, event.options).pipe(
2276
2514
  timeout({ each: 18e4 })
2277
2515
  ).subscribe({
2278
2516
  next: (e) => {
@@ -2311,720 +2549,7 @@ function createMarkVM(client, resourceId) {
2311
2549
  }
2312
2550
  };
2313
2551
  }
2314
- var RECENT_LIMIT = 10;
2315
- var SEARCH_LIMIT = 20;
2316
- function createDiscoverVM(client, browse) {
2317
- const disposer = createDisposer();
2318
- const search = createSearchPipeline(
2319
- (q) => client.browse.resources({ search: q, limit: SEARCH_LIMIT })
2320
- );
2321
- disposer.add(search);
2322
- disposer.add(browse);
2323
- const recent$ = client.browse.resources({ limit: RECENT_LIMIT, archived: false });
2324
- const recentResources$ = recent$.pipe(
2325
- map$1((r) => r ?? [])
2326
- );
2327
- const isLoadingRecent$ = recent$.pipe(
2328
- map$1((r) => r === void 0)
2329
- );
2330
- const entityTypes$ = client.browse.entityTypes().pipe(
2331
- map$1((e) => e ?? [])
2332
- );
2333
- return {
2334
- browse,
2335
- search,
2336
- recentResources$,
2337
- entityTypes$,
2338
- isLoadingRecent$,
2339
- dispose: () => disposer.dispose()
2340
- };
2341
- }
2342
- function createEntityTagsVM(client, browse) {
2343
- const disposer = createDisposer();
2344
- disposer.add(browse);
2345
- const newTag$ = new BehaviorSubject("");
2346
- const error$ = new BehaviorSubject("");
2347
- const isAdding$ = new BehaviorSubject(false);
2348
- const raw$ = client.browse.entityTypes();
2349
- const entityTypes$ = raw$.pipe(map$1((e) => e ?? []));
2350
- const isLoading$ = raw$.pipe(map$1((e) => e === void 0));
2351
- const addTag = async () => {
2352
- const tag = newTag$.getValue().trim();
2353
- if (!tag) return;
2354
- error$.next("");
2355
- isAdding$.next(true);
2356
- try {
2357
- await client.mark.entityType(tag);
2358
- newTag$.next("");
2359
- } catch (err) {
2360
- error$.next(err instanceof Error ? err.message : "Failed to add entity type");
2361
- } finally {
2362
- isAdding$.next(false);
2363
- }
2364
- };
2365
- return {
2366
- browse,
2367
- entityTypes$,
2368
- isLoading$,
2369
- newTag$: newTag$.asObservable(),
2370
- error$: error$.asObservable(),
2371
- isAdding$: isAdding$.asObservable(),
2372
- setNewTag: (v) => newTag$.next(v),
2373
- addTag,
2374
- dispose: () => {
2375
- newTag$.complete();
2376
- error$.complete();
2377
- isAdding$.complete();
2378
- disposer.dispose();
2379
- }
2380
- };
2381
- }
2382
- function createExchangeVM(browse, exportFn, importFn) {
2383
- const disposer = createDisposer();
2384
- disposer.add(browse);
2385
- const selectedFile$ = new BehaviorSubject(null);
2386
- const preview$ = new BehaviorSubject(null);
2387
- const importPhase$ = new BehaviorSubject(null);
2388
- const importMessage$ = new BehaviorSubject(void 0);
2389
- const importResult$ = new BehaviorSubject(void 0);
2390
- const isExporting$ = new BehaviorSubject(false);
2391
- const isImporting$ = new BehaviorSubject(false);
2392
- const selectFile = (file) => {
2393
- selectedFile$.next(file);
2394
- importPhase$.next(null);
2395
- importMessage$.next(void 0);
2396
- importResult$.next(void 0);
2397
- preview$.next({
2398
- format: file.name.endsWith(".tar.gz") || file.name.endsWith(".gz") ? "semiont-linked-data" : "unknown",
2399
- version: 1,
2400
- sourceUrl: "",
2401
- stats: {}
2402
- });
2403
- };
2404
- const cancelImport = () => {
2405
- selectedFile$.next(null);
2406
- preview$.next(null);
2407
- importPhase$.next(null);
2408
- importMessage$.next(void 0);
2409
- importResult$.next(void 0);
2410
- };
2411
- const doExport = async () => {
2412
- isExporting$.next(true);
2413
- try {
2414
- const response = await exportFn();
2415
- if (!response.ok) throw new Error(`Export failed: ${response.status} ${response.statusText}`);
2416
- const blob = await response.blob();
2417
- const contentDisposition = response.headers.get("Content-Disposition");
2418
- const filename = contentDisposition?.match(/filename="(.+?)"/)?.[1] ?? `semiont-export-${Date.now()}.tar.gz`;
2419
- return { blob, filename };
2420
- } finally {
2421
- isExporting$.next(false);
2422
- }
2423
- };
2424
- const doImport = async () => {
2425
- const file = selectedFile$.getValue();
2426
- if (!file) return;
2427
- isImporting$.next(true);
2428
- importPhase$.next("started");
2429
- importMessage$.next(void 0);
2430
- importResult$.next(void 0);
2431
- try {
2432
- await importFn(file, {
2433
- onProgress: (event) => {
2434
- importPhase$.next(event.phase);
2435
- importMessage$.next(event.message);
2436
- if (event.result) importResult$.next(event.result);
2437
- }
2438
- });
2439
- } finally {
2440
- isImporting$.next(false);
2441
- }
2442
- };
2443
- return {
2444
- browse,
2445
- selectedFile$: selectedFile$.asObservable(),
2446
- preview$: preview$.asObservable(),
2447
- importPhase$: importPhase$.asObservable(),
2448
- importMessage$: importMessage$.asObservable(),
2449
- importResult$: importResult$.asObservable(),
2450
- isExporting$: isExporting$.asObservable(),
2451
- isImporting$: isImporting$.asObservable(),
2452
- selectFile,
2453
- cancelImport,
2454
- doExport,
2455
- doImport,
2456
- dispose: () => {
2457
- selectedFile$.complete();
2458
- preview$.complete();
2459
- importPhase$.complete();
2460
- importMessage$.complete();
2461
- importResult$.complete();
2462
- isExporting$.complete();
2463
- isImporting$.complete();
2464
- disposer.dispose();
2465
- }
2466
- };
2467
- }
2468
- function createAdminUsersVM(client, browse) {
2469
- const disposer = createDisposer();
2470
- disposer.add(browse);
2471
- const users$ = new BehaviorSubject([]);
2472
- const stats$ = new BehaviorSubject(null);
2473
- const usersLoading$ = new BehaviorSubject(true);
2474
- const statsLoading$ = new BehaviorSubject(true);
2475
- const fetchUsers = () => {
2476
- usersLoading$.next(true);
2477
- client.admin.users().then((data) => {
2478
- users$.next(data.users ?? []);
2479
- usersLoading$.next(false);
2480
- }).catch(() => usersLoading$.next(false));
2481
- };
2482
- const fetchStats = () => {
2483
- statsLoading$.next(true);
2484
- client.admin.userStats().then((data) => {
2485
- stats$.next(data.stats ?? null);
2486
- statsLoading$.next(false);
2487
- }).catch(() => statsLoading$.next(false));
2488
- };
2489
- fetchUsers();
2490
- fetchStats();
2491
- const updateUser = async (id, data) => {
2492
- await client.admin.updateUser(userDID(id), data);
2493
- fetchUsers();
2494
- fetchStats();
2495
- };
2496
- return {
2497
- browse,
2498
- users$: users$.asObservable(),
2499
- stats$: stats$.asObservable(),
2500
- usersLoading$: usersLoading$.asObservable(),
2501
- statsLoading$: statsLoading$.asObservable(),
2502
- updateUser,
2503
- dispose: () => {
2504
- users$.complete();
2505
- stats$.complete();
2506
- usersLoading$.complete();
2507
- statsLoading$.complete();
2508
- disposer.dispose();
2509
- }
2510
- };
2511
- }
2512
- function createAdminSecurityVM(client, browse) {
2513
- const disposer = createDisposer();
2514
- disposer.add(browse);
2515
- const providers$ = new BehaviorSubject([]);
2516
- const allowedDomains$ = new BehaviorSubject([]);
2517
- const isLoading$ = new BehaviorSubject(true);
2518
- client.admin.oauthConfig().then((data) => {
2519
- const config = data;
2520
- providers$.next(config.providers ?? []);
2521
- allowedDomains$.next(config.allowedDomains ?? []);
2522
- isLoading$.next(false);
2523
- }).catch(() => isLoading$.next(false));
2524
- return {
2525
- browse,
2526
- providers$: providers$.asObservable(),
2527
- allowedDomains$: allowedDomains$.asObservable(),
2528
- isLoading$: isLoading$.asObservable(),
2529
- dispose: () => {
2530
- providers$.complete();
2531
- allowedDomains$.complete();
2532
- isLoading$.complete();
2533
- disposer.dispose();
2534
- }
2535
- };
2536
- }
2537
- function createWelcomeVM(client) {
2538
- const disposer = createDisposer();
2539
- const userData$ = new BehaviorSubject(null);
2540
- const isProcessing$ = new BehaviorSubject(false);
2541
- client.auth.me().then((data) => userData$.next(data)).catch(() => {
2542
- });
2543
- const acceptTerms = async () => {
2544
- isProcessing$.next(true);
2545
- try {
2546
- await client.auth.acceptTerms();
2547
- userData$.next({ ...userData$.getValue(), termsAcceptedAt: (/* @__PURE__ */ new Date()).toISOString() });
2548
- } finally {
2549
- isProcessing$.next(false);
2550
- }
2551
- };
2552
- return {
2553
- userData$: userData$.asObservable(),
2554
- isProcessing$: isProcessing$.asObservable(),
2555
- acceptTerms,
2556
- dispose: () => {
2557
- userData$.complete();
2558
- isProcessing$.complete();
2559
- disposer.dispose();
2560
- }
2561
- };
2562
- }
2563
- function createResourceLoaderVM(client, resourceId) {
2564
- const raw$ = client.browse.resource(resourceId);
2565
- const resource$ = raw$;
2566
- const isLoading$ = raw$.pipe(map$1((r) => r === void 0));
2567
- return {
2568
- resource$,
2569
- isLoading$,
2570
- invalidate: () => client.browse.invalidateResourceDetail(resourceId),
2571
- dispose: () => {
2572
- }
2573
- };
2574
- }
2575
- function createSessionVM(client) {
2576
- const isLoggingOut$ = new BehaviorSubject(false);
2577
- const logout = async () => {
2578
- isLoggingOut$.next(true);
2579
- try {
2580
- await client.auth.logout();
2581
- } catch {
2582
- } finally {
2583
- isLoggingOut$.next(false);
2584
- }
2585
- };
2586
- return {
2587
- isLoggingOut$: isLoggingOut$.asObservable(),
2588
- logout,
2589
- dispose: () => {
2590
- isLoggingOut$.complete();
2591
- }
2592
- };
2593
- }
2594
- var SMELTER_CHANNELS = [
2595
- "yield:created",
2596
- "yield:updated",
2597
- "yield:representation-added",
2598
- "mark:archived",
2599
- "mark:added",
2600
- "mark:removed"
2601
- ];
2602
- function createSmelterActorVM(options) {
2603
- const actor = createActorVM({
2604
- baseUrl: options.baseUrl,
2605
- token: options.token,
2606
- channels: [...SMELTER_CHANNELS],
2607
- reconnectMs: options.reconnectMs
2608
- });
2609
- const events$ = merge(
2610
- ...SMELTER_CHANNELS.map(
2611
- (channel) => actor.on$(channel).pipe(
2612
- map((payload) => ({
2613
- type: channel,
2614
- resourceId: payload.resourceId,
2615
- payload
2616
- }))
2617
- )
2618
- )
2619
- );
2620
- return {
2621
- events$,
2622
- state$: actor.state$,
2623
- emit: (channel, payload) => actor.emit(channel, payload),
2624
- start: () => actor.start(),
2625
- stop: () => actor.stop(),
2626
- dispose: () => actor.dispose()
2627
- };
2628
- }
2629
- function createJobClaimAdapter(options) {
2630
- const { actor, jobTypes } = options;
2631
- const activeJob$ = new BehaviorSubject(null);
2632
- const isProcessing$ = new BehaviorSubject(false);
2633
- const jobsCompleted$ = new BehaviorSubject(0);
2634
- const errors$ = new Subject();
2635
- let jobSubscription = null;
2636
- let started = false;
2637
- const claimJob = async (assignment) => {
2638
- try {
2639
- const correlationId = crypto.randomUUID();
2640
- const result$ = merge(
2641
- actor.on$("job:claimed").pipe(
2642
- filter$1((e) => e.correlationId === correlationId),
2643
- map$1((e) => ({ ok: true, response: e.response }))
2644
- ),
2645
- actor.on$("job:claim-failed").pipe(
2646
- filter$1((e) => e.correlationId === correlationId),
2647
- map$1(() => ({ ok: false }))
2648
- )
2649
- ).pipe(take$1(1), timeout$1(1e4));
2650
- const resultPromise = firstValueFrom(result$);
2651
- await actor.emit("job:claim", { correlationId, jobId: assignment.jobId });
2652
- const result = await resultPromise;
2653
- if (!result.ok) return null;
2654
- const job = result.response;
2655
- return {
2656
- jobId: assignment.jobId,
2657
- type: assignment.type,
2658
- resourceId: assignment.resourceId,
2659
- userId: job.metadata?.userId ?? "",
2660
- params: job.params ?? {}
2661
- };
2662
- } catch {
2663
- return null;
2664
- }
2665
- };
2666
- return {
2667
- activeJob$: activeJob$.asObservable(),
2668
- isProcessing$: isProcessing$.asObservable(),
2669
- jobsCompleted$: jobsCompleted$.asObservable(),
2670
- errors$: errors$.asObservable(),
2671
- start: () => {
2672
- if (started) return;
2673
- started = true;
2674
- actor.addChannels(["job:queued"]);
2675
- jobSubscription = actor.on$("job:queued").subscribe((event) => {
2676
- const jobType = event.jobType;
2677
- if (jobTypes.length > 0 && !jobTypes.includes(jobType)) return;
2678
- if (isProcessing$.getValue()) return;
2679
- isProcessing$.next(true);
2680
- claimJob({ jobId: event.jobId, type: jobType, resourceId: event.resourceId }).then((job) => {
2681
- if (job) {
2682
- activeJob$.next(job);
2683
- } else {
2684
- isProcessing$.next(false);
2685
- }
2686
- }).catch(() => {
2687
- isProcessing$.next(false);
2688
- });
2689
- });
2690
- },
2691
- stop: () => {
2692
- jobSubscription?.unsubscribe();
2693
- jobSubscription = null;
2694
- started = false;
2695
- },
2696
- completeJob: () => {
2697
- activeJob$.next(null);
2698
- isProcessing$.next(false);
2699
- jobsCompleted$.next(jobsCompleted$.getValue() + 1);
2700
- },
2701
- failJob: (jid, error) => {
2702
- activeJob$.next(null);
2703
- isProcessing$.next(false);
2704
- errors$.next({ jobId: jid, error });
2705
- },
2706
- dispose: () => {
2707
- jobSubscription?.unsubscribe();
2708
- jobSubscription = null;
2709
- started = false;
2710
- activeJob$.complete();
2711
- isProcessing$.complete();
2712
- jobsCompleted$.complete();
2713
- errors$.complete();
2714
- }
2715
- };
2716
- }
2717
- function createJobQueueVM(client) {
2718
- const jobs$ = new BehaviorSubject([]);
2719
- const jobCreated$ = new Subject();
2720
- const jobCompleted$ = new Subject();
2721
- const jobFailed$ = new Subject();
2722
- const pendingByType$ = jobs$.pipe(
2723
- map$1((all) => {
2724
- const counts = /* @__PURE__ */ new Map();
2725
- for (const j of all) {
2726
- if (j.status === "pending") {
2727
- counts.set(j.type, (counts.get(j.type) ?? 0) + 1);
2728
- }
2729
- }
2730
- return counts;
2731
- })
2732
- );
2733
- const runningJobs$ = jobs$.pipe(
2734
- map$1((all) => all.filter((j) => j.status === "running"))
2735
- );
2736
- const addOrUpdate = (job) => {
2737
- const current = jobs$.getValue();
2738
- const idx = current.findIndex((j) => j.jobId === job.jobId);
2739
- if (idx >= 0) {
2740
- const next = [...current];
2741
- next[idx] = job;
2742
- jobs$.next(next);
2743
- } else {
2744
- jobs$.next([...current, job]);
2745
- }
2746
- };
2747
- const subs = [
2748
- client.bus.get("job:queued").subscribe((event) => {
2749
- const job = {
2750
- jobId: event.jobId,
2751
- type: event.jobType,
2752
- status: "pending",
2753
- resourceId: event.resourceId,
2754
- userId: event.userId,
2755
- created: (/* @__PURE__ */ new Date()).toISOString()
2756
- };
2757
- addOrUpdate(job);
2758
- jobCreated$.next(job);
2759
- }),
2760
- client.bus.get("job:complete").subscribe((event) => {
2761
- if (!event._userId) {
2762
- throw new Error("job:complete missing _userId (gateway injection)");
2763
- }
2764
- const job = {
2765
- jobId: event.jobId,
2766
- type: event.jobType,
2767
- status: "complete",
2768
- resourceId: event.resourceId,
2769
- userId: event._userId,
2770
- created: "",
2771
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2772
- result: event.result
2773
- };
2774
- addOrUpdate(job);
2775
- jobCompleted$.next(job);
2776
- }),
2777
- client.bus.get("job:fail").subscribe((event) => {
2778
- if (!event._userId) {
2779
- throw new Error("job:fail missing _userId (gateway injection)");
2780
- }
2781
- const job = {
2782
- jobId: event.jobId,
2783
- type: event.jobType,
2784
- status: "failed",
2785
- resourceId: event.resourceId,
2786
- userId: event._userId,
2787
- created: "",
2788
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2789
- error: event.error
2790
- };
2791
- addOrUpdate(job);
2792
- jobFailed$.next(job);
2793
- })
2794
- ];
2795
- return {
2796
- jobs$: jobs$.asObservable(),
2797
- pendingByType$,
2798
- runningJobs$,
2799
- jobCreated$: jobCreated$.asObservable(),
2800
- jobCompleted$: jobCompleted$.asObservable(),
2801
- jobFailed$: jobFailed$.asObservable(),
2802
- dispose: () => {
2803
- subs.forEach((s) => s.unsubscribe());
2804
- jobs$.complete();
2805
- jobCreated$.complete();
2806
- jobCompleted$.complete();
2807
- jobFailed$.complete();
2808
- }
2809
- };
2810
- }
2811
- var WIZARD_CLOSED = {
2812
- open: false,
2813
- annotationId: null,
2814
- resourceId: null,
2815
- defaultTitle: "",
2816
- entityTypes: []
2817
- };
2818
- function createResourceViewerPageVM(client, resourceId, locale, browse, options) {
2819
- const disposer = createDisposer();
2820
- const beckon = createBeckonVM(client);
2821
- const mark = createMarkVM(client, resourceId);
2822
- const gather = createGatherVM(client, resourceId);
2823
- const matchVM = createMatchVM(client);
2824
- const yieldVM = createYieldVM(client, resourceId, locale);
2825
- disposer.add(beckon);
2826
- disposer.add(browse);
2827
- disposer.add(mark);
2828
- disposer.add(gather);
2829
- disposer.add(matchVM);
2830
- disposer.add(yieldVM);
2831
- const annotations$ = client.browse.annotations(resourceId).pipe(
2832
- map$1((a) => a ?? [])
2833
- );
2834
- const annotationGroups$ = annotations$.pipe(
2835
- map$1((anns) => {
2836
- const groups = { highlights: [], comments: [], assessments: [], references: [], tags: [] };
2837
- for (const ann of anns) {
2838
- if (isHighlight(ann)) groups.highlights.push(ann);
2839
- else if (isComment(ann)) groups.comments.push(ann);
2840
- else if (isAssessment(ann)) groups.assessments.push(ann);
2841
- else if (isReference(ann)) groups.references.push(ann);
2842
- else if (isTag(ann)) groups.tags.push(ann);
2843
- }
2844
- return groups;
2845
- })
2846
- );
2847
- const entityTypes$ = client.browse.entityTypes().pipe(
2848
- map$1((e) => e ?? [])
2849
- );
2850
- const events$ = client.browse.events(resourceId).pipe(
2851
- map$1((e) => e ?? [])
2852
- );
2853
- const referencedBy$ = client.browse.referencedBy(resourceId).pipe(
2854
- map$1((r) => r ?? [])
2855
- );
2856
- const content$ = new BehaviorSubject("");
2857
- const contentLoading$ = new BehaviorSubject(false);
2858
- const mediaToken$ = new BehaviorSubject(null);
2859
- const mediaType = options?.mediaType || "text/plain";
2860
- const isBinaryType = mediaType.startsWith("image/") || mediaType === "application/pdf";
2861
- if (!isBinaryType && mediaType) {
2862
- contentLoading$.next(true);
2863
- client.browse.resourceRepresentation(resourceId, { accept: mediaType }).then(({ data }) => {
2864
- content$.next(decodeWithCharset(data, mediaType));
2865
- contentLoading$.next(false);
2866
- }).catch(() => {
2867
- contentLoading$.next(false);
2868
- });
2869
- }
2870
- if (isBinaryType) {
2871
- client.auth.mediaToken(resourceId).then(({ token }) => mediaToken$.next(token)).catch(() => {
2872
- });
2873
- }
2874
- const wizard$ = new BehaviorSubject(WIZARD_CLOSED);
2875
- const unsubscribeResource = client.subscribeToResource(resourceId);
2876
- disposer.add(unsubscribeResource);
2877
- const bindInitiateSub = client.bus.get("bind:initiate").subscribe((event) => {
2878
- wizard$.next({
2879
- open: true,
2880
- annotationId: event.annotationId,
2881
- resourceId: event.resourceId,
2882
- defaultTitle: event.defaultTitle,
2883
- entityTypes: event.entityTypes
2884
- });
2885
- client.bus.get("gather:requested").next({
2886
- correlationId: crypto.randomUUID(),
2887
- annotationId: event.annotationId,
2888
- resourceId: event.resourceId,
2889
- options: { contextWindow: 2e3 }
2890
- });
2891
- });
2892
- disposer.add(() => bindInitiateSub.unsubscribe());
2893
- return {
2894
- beckon,
2895
- browse,
2896
- mark,
2897
- gather,
2898
- yield: yieldVM,
2899
- annotations$,
2900
- annotationGroups$,
2901
- entityTypes$,
2902
- events$,
2903
- referencedBy$,
2904
- content$: content$.asObservable(),
2905
- contentLoading$: contentLoading$.asObservable(),
2906
- mediaToken$: mediaToken$.asObservable(),
2907
- wizard$: wizard$.asObservable(),
2908
- closeWizard: () => wizard$.next(WIZARD_CLOSED),
2909
- dispose: () => {
2910
- wizard$.complete();
2911
- content$.complete();
2912
- contentLoading$.complete();
2913
- mediaToken$.complete();
2914
- disposer.dispose();
2915
- }
2916
- };
2917
- }
2918
- function createComposePageVM(client, browse, params, auth) {
2919
- const disposer = createDisposer();
2920
- disposer.add(browse);
2921
- const isReferenceMode = Boolean(params.annotationUri && params.sourceDocumentId && params.name);
2922
- const isCloneMode = params.mode === "clone" && Boolean(params.token);
2923
- const pageMode = isCloneMode ? "clone" : isReferenceMode ? "reference" : "new";
2924
- const mode$ = new BehaviorSubject(pageMode);
2925
- const loading$ = new BehaviorSubject(true);
2926
- const cloneData$ = new BehaviorSubject(null);
2927
- const referenceData$ = new BehaviorSubject(null);
2928
- const gatheredContext$ = new BehaviorSubject(null);
2929
- const entityTypes$ = client.browse.entityTypes().pipe(
2930
- map$1((e) => e ?? [])
2931
- );
2932
- if (isReferenceMode) {
2933
- const entityTypes = params.entityTypes ? params.entityTypes.split(",") : [];
2934
- referenceData$.next({
2935
- annotationUri: params.annotationUri,
2936
- sourceDocumentId: params.sourceDocumentId,
2937
- name: params.name,
2938
- entityTypes
2939
- });
2940
- if (params.storedContext) {
2941
- try {
2942
- gatheredContext$.next(JSON.parse(params.storedContext));
2943
- } catch {
2944
- }
2945
- }
2946
- loading$.next(false);
2947
- } else if (isCloneMode) {
2948
- void (async () => {
2949
- try {
2950
- const tokenResult = await client.yield.fromToken(params.token);
2951
- if (tokenResult && auth) {
2952
- const rId = resourceId(tokenResult["@id"]);
2953
- const mediaType = getPrimaryMediaType(tokenResult) || "text/plain";
2954
- const { data } = await client.browse.resourceRepresentation(rId, {
2955
- accept: mediaType
2956
- });
2957
- const content = decodeWithCharset(data, mediaType);
2958
- cloneData$.next({ sourceResource: tokenResult, sourceContent: content });
2959
- }
2960
- } catch {
2961
- }
2962
- loading$.next(false);
2963
- })();
2964
- } else {
2965
- loading$.next(false);
2966
- }
2967
- const save = async (saveParams) => {
2968
- if (saveParams.mode === "clone") {
2969
- const response2 = await client.yield.createFromToken({
2970
- token: params.token,
2971
- name: saveParams.name,
2972
- content: saveParams.content,
2973
- archiveOriginal: saveParams.archiveOriginal ?? true
2974
- });
2975
- return response2.resourceId;
2976
- }
2977
- let fileToUpload;
2978
- let mimeType;
2979
- if (saveParams.file) {
2980
- fileToUpload = saveParams.file;
2981
- mimeType = saveParams.format ?? "application/octet-stream";
2982
- } else {
2983
- const blob = new Blob([saveParams.content || ""], { type: saveParams.format ?? "application/octet-stream" });
2984
- const extension = saveParams.format === "text/plain" ? ".txt" : saveParams.format === "text/html" ? ".html" : ".md";
2985
- fileToUpload = new File([blob], saveParams.name + extension, { type: saveParams.format ?? "application/octet-stream" });
2986
- mimeType = saveParams.format ?? "application/octet-stream";
2987
- }
2988
- const format = saveParams.charset && !saveParams.file ? `${mimeType}; charset=${saveParams.charset}` : mimeType;
2989
- const response = await client.yield.resource({
2990
- name: saveParams.name,
2991
- file: fileToUpload,
2992
- format,
2993
- entityTypes: saveParams.entityTypes || [],
2994
- language: saveParams.language,
2995
- creationMethod: "ui",
2996
- storageUri: saveParams.storageUri
2997
- });
2998
- const newResourceId = response.resourceId;
2999
- if (saveParams.mode === "reference" && saveParams.annotationUri && saveParams.sourceDocumentId) {
3000
- await client.bind.body(
3001
- resourceId(saveParams.sourceDocumentId),
3002
- annotationId(saveParams.annotationUri),
3003
- [{ op: "add", item: { type: "SpecificResource", source: newResourceId, purpose: "linking" } }]
3004
- );
3005
- }
3006
- return newResourceId;
3007
- };
3008
- return {
3009
- browse,
3010
- mode$: mode$.asObservable(),
3011
- loading$: loading$.asObservable(),
3012
- cloneData$: cloneData$.asObservable(),
3013
- referenceData$: referenceData$.asObservable(),
3014
- gatheredContext$: gatheredContext$.asObservable(),
3015
- entityTypes$,
3016
- save,
3017
- dispose: () => {
3018
- mode$.complete();
3019
- loading$.complete();
3020
- cloneData$.complete();
3021
- referenceData$.complete();
3022
- gatheredContext$.complete();
3023
- disposer.dispose();
3024
- }
3025
- };
3026
- }
3027
2552
 
3028
- export { AdminNamespace, AuthNamespace, BeckonNamespace, BindNamespace, BrowseNamespace, BusRequestError, COMMON_PANELS, FrontendSessionSignals, GatherNamespace, HOVER_DELAY_MS, InMemorySessionStorage, JobNamespace, MarkNamespace, MatchNamespace, RESOURCE_PANELS, SemiontBrowser, SemiontClient, SemiontError, SemiontSession, YieldNamespace, busRequest, createAdminSecurityVM, createAdminUsersVM, createBeckonVM, createCache, createComposePageVM, createDiscoverVM, createDisposer, createEntityTagsVM, createExchangeVM, createGatherVM, createHoverHandlers, createJobClaimAdapter, createJobQueueVM, createMarkVM, createMatchVM, createResourceLoaderVM, createResourceViewerPageVM, createSearchPipeline, createSessionVM, createShellVM, createSmelterActorVM, createWelcomeVM, createYieldVM, defaultProtocol, getBrowser, isValidHostname, kbBackendUrl, notifyPermissionDenied, notifySessionExpired, setStoredSession };
2553
+ export { AdminNamespace, AuthNamespace, BeckonNamespace, BindNamespace, BrowseNamespace, BusRequestError, CacheObservable, FrameNamespace, GatherNamespace, HOVER_DELAY_MS, InMemorySessionStorage, JobNamespace, MarkNamespace, MatchNamespace, SemiontBrowser, SemiontClient, SemiontSession, SemiontSessionError, SessionSignals, StreamObservable, UploadObservable, YieldNamespace, busRequest, createBeckonStateUnit, createDisposer, createGatherStateUnit, createHoverHandlers, createHttpSessionFactory, createMarkStateUnit, createMatchStateUnit, createSearchPipeline, createYieldStateUnit, defaultProtocol, getBrowser, httpKb, isValidHostname, kbBackendUrl, setStoredSession };
3029
2554
  //# sourceMappingURL=index.js.map
3030
2555
  //# sourceMappingURL=index.js.map