@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/README.md +136 -44
- package/dist/index.d.ts +663 -609
- package/dist/index.js +672 -1147
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,61 @@
|
|
|
1
|
-
import { annotationId, resourceId, email, googleCredential, refreshToken, EventBus,
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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) => ({
|
|
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(
|
|
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 (
|
|
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 (
|
|
146
|
-
const
|
|
147
|
-
if (!
|
|
148
|
-
throw new Error(`Cannot fetch annotation ${
|
|
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 (
|
|
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 (
|
|
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/
|
|
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
|
-
|
|
231
|
-
|
|
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(
|
|
239
|
-
let obs = this.annotationListObs.get(
|
|
304
|
+
annotations(resourceId2) {
|
|
305
|
+
let obs = this.annotationListObs.get(resourceId2);
|
|
240
306
|
if (!obs) {
|
|
241
|
-
obs = this.annotationListCache.observe(
|
|
242
|
-
this.annotationListObs.set(
|
|
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(
|
|
247
|
-
this.annotationResources.set(
|
|
248
|
-
return this.annotationDetailCache.observe(
|
|
312
|
+
annotation(resourceId2, annotationId2) {
|
|
313
|
+
this.annotationResources.set(annotationId2, resourceId2);
|
|
314
|
+
return CacheObservable.from(this.annotationDetailCache.observe(annotationId2));
|
|
249
315
|
}
|
|
250
316
|
entityTypes() {
|
|
251
|
-
|
|
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(
|
|
263
|
-
return this.referencedByCache.observe(
|
|
319
|
+
referencedBy(resourceId2) {
|
|
320
|
+
return CacheObservable.from(this.referencedByCache.observe(resourceId2));
|
|
264
321
|
}
|
|
265
|
-
events(
|
|
266
|
-
return this.resourceEventsCache.observe(
|
|
322
|
+
events(resourceId2) {
|
|
323
|
+
return CacheObservable.from(this.resourceEventsCache.observe(resourceId2));
|
|
267
324
|
}
|
|
268
325
|
// ── One-shot reads ──────────────────────────────────────────────────────
|
|
269
|
-
async resourceContent(
|
|
270
|
-
const result = await this.content.getBinary(
|
|
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(
|
|
275
|
-
return this.content.getBinary(
|
|
331
|
+
async resourceRepresentation(resourceId2, options) {
|
|
332
|
+
return this.content.getBinary(resourceId2, options?.accept ? { accept: options.accept } : void 0);
|
|
276
333
|
}
|
|
277
|
-
async resourceRepresentationStream(
|
|
278
|
-
return this.content.getBinaryStream(
|
|
334
|
+
async resourceRepresentationStream(resourceId2, options) {
|
|
335
|
+
return this.content.getBinaryStream(resourceId2, options?.accept ? { accept: options.accept } : void 0);
|
|
279
336
|
}
|
|
280
|
-
async resourceEvents(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
331
|
-
this.annotationListCache.invalidate(
|
|
387
|
+
invalidateAnnotationList(resourceId2) {
|
|
388
|
+
this.annotationListCache.invalidate(resourceId2);
|
|
332
389
|
}
|
|
333
|
-
removeAnnotationDetail(
|
|
334
|
-
this.annotationDetailCache.remove(
|
|
335
|
-
this.annotationResources.delete(
|
|
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(
|
|
347
|
-
this.referencedByCache.invalidate(
|
|
403
|
+
invalidateReferencedBy(resourceId2) {
|
|
404
|
+
this.referencedByCache.invalidate(resourceId2);
|
|
348
405
|
}
|
|
349
|
-
invalidateResourceEvents(
|
|
350
|
-
this.resourceEventsCache.invalidate(
|
|
406
|
+
invalidateResourceEvents(resourceId2) {
|
|
407
|
+
this.resourceEventsCache.invalidate(resourceId2);
|
|
351
408
|
}
|
|
352
|
-
updateAnnotationInPlace(
|
|
353
|
-
const currentList = this.annotationListCache.get(
|
|
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(
|
|
414
|
+
this.annotationListCache.set(resourceId2, { ...currentList, annotations: nextAnnotations });
|
|
358
415
|
}
|
|
359
416
|
const aId = annotationId(annotation.id);
|
|
360
|
-
this.annotationResources.set(aId,
|
|
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("
|
|
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(
|
|
465
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
491
|
-
return new
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
674
|
-
return new
|
|
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 ===
|
|
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(
|
|
727
|
-
return new
|
|
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
|
-
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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(
|
|
783
|
-
return new
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
939
|
-
this.bus.get("beckon:hover").next({ annotationId });
|
|
1023
|
+
hover(annotationId2) {
|
|
1024
|
+
this.bus.get("beckon:hover").next({ annotationId: annotationId2 });
|
|
940
1025
|
}
|
|
941
|
-
sparkle(
|
|
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
|
|
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 >
|
|
992
|
-
throw new Error(`Job polling timeout after ${
|
|
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
|
|
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(
|
|
1008
|
-
this.
|
|
1105
|
+
constructor(backend) {
|
|
1106
|
+
this.backend = backend;
|
|
1009
1107
|
}
|
|
1010
1108
|
async password(emailStr, passwordStr) {
|
|
1011
|
-
return this.
|
|
1109
|
+
return this.backend.authenticatePassword(email(emailStr), passwordStr);
|
|
1012
1110
|
}
|
|
1013
1111
|
async google(credential) {
|
|
1014
|
-
return this.
|
|
1112
|
+
return this.backend.authenticateGoogle(googleCredential(credential));
|
|
1015
1113
|
}
|
|
1016
1114
|
async refresh(token) {
|
|
1017
|
-
return this.
|
|
1115
|
+
return this.backend.refreshAccessToken(refreshToken(token));
|
|
1018
1116
|
}
|
|
1019
1117
|
async logout() {
|
|
1020
|
-
await this.
|
|
1118
|
+
await this.backend.logout();
|
|
1021
1119
|
}
|
|
1022
1120
|
async me() {
|
|
1023
|
-
return this.
|
|
1121
|
+
return this.backend.getCurrentUser();
|
|
1024
1122
|
}
|
|
1025
1123
|
async acceptTerms() {
|
|
1026
|
-
await this.
|
|
1124
|
+
await this.backend.acceptTerms();
|
|
1027
1125
|
}
|
|
1028
1126
|
async mcpToken() {
|
|
1029
|
-
return this.
|
|
1127
|
+
return this.backend.generateMcpToken();
|
|
1030
1128
|
}
|
|
1031
|
-
async mediaToken(
|
|
1032
|
-
return this.
|
|
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(
|
|
1039
|
-
this.
|
|
1136
|
+
constructor(backend) {
|
|
1137
|
+
this.backend = backend;
|
|
1040
1138
|
}
|
|
1041
1139
|
async users() {
|
|
1042
|
-
const result = await this.
|
|
1140
|
+
const result = await this.backend.listUsers();
|
|
1043
1141
|
return result.users;
|
|
1044
1142
|
}
|
|
1045
1143
|
async userStats() {
|
|
1046
|
-
return this.
|
|
1144
|
+
return this.backend.getUserStats();
|
|
1047
1145
|
}
|
|
1048
|
-
async updateUser(
|
|
1049
|
-
const result = await this.
|
|
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.
|
|
1151
|
+
return this.backend.getOAuthConfig();
|
|
1054
1152
|
}
|
|
1055
1153
|
async healthCheck() {
|
|
1056
|
-
return this.
|
|
1154
|
+
return this.backend.healthCheck();
|
|
1057
1155
|
}
|
|
1058
1156
|
async status() {
|
|
1059
|
-
return this.
|
|
1157
|
+
return this.backend.getStatus();
|
|
1060
1158
|
}
|
|
1061
1159
|
async backup() {
|
|
1062
|
-
return this.
|
|
1160
|
+
return this.backend.backupKnowledgeBase();
|
|
1063
1161
|
}
|
|
1064
|
-
|
|
1065
|
-
return this.
|
|
1162
|
+
restore(file) {
|
|
1163
|
+
return wrapAsStream(this.backend.restoreKnowledgeBase(file));
|
|
1066
1164
|
}
|
|
1067
1165
|
async exportKnowledgeBase(params) {
|
|
1068
|
-
return this.
|
|
1166
|
+
return this.backend.exportKnowledgeBase(params);
|
|
1069
1167
|
}
|
|
1070
|
-
|
|
1071
|
-
return this.
|
|
1168
|
+
importKnowledgeBase(file) {
|
|
1169
|
+
return wrapAsStream(this.backend.importKnowledgeBase(file));
|
|
1072
1170
|
}
|
|
1073
1171
|
};
|
|
1074
|
-
|
|
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(
|
|
1131
|
-
this.admin = new AdminNamespace(
|
|
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(
|
|
1138
|
-
return this.transport.subscribeToResource(
|
|
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
|
|
1189
|
-
if (entry
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
return
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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.
|
|
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(
|
|
1232
|
-
if (!isValidHostname(
|
|
1233
|
-
throw new Error(`Invalid KB hostname: "${
|
|
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 =
|
|
1237
|
-
url.hostname =
|
|
1238
|
-
url.port = String(
|
|
1239
|
-
return `${
|
|
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 = "
|
|
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
|
|
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
|
|
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 [
|
|
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
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
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
|
|
1708
|
-
const token$ = new BehaviorSubject(null);
|
|
1969
|
+
const signals = new SessionSignals();
|
|
1709
1970
|
let session;
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
|
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({
|
|
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
|
|
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
|
|
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(
|
|
2000
|
-
if (
|
|
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: (
|
|
2010
|
-
focus: (
|
|
2011
|
-
sparkle: (
|
|
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 = (
|
|
2029
|
-
if (currentHover ===
|
|
2318
|
+
const handleMouseEnter = (annotationId2) => {
|
|
2319
|
+
if (currentHover === annotationId2) return;
|
|
2030
2320
|
cancelTimer();
|
|
2031
2321
|
timer = setTimeout(() => {
|
|
2032
2322
|
timer = null;
|
|
2033
|
-
currentHover =
|
|
2034
|
-
emit(
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
2491
|
+
const result = await client.mark.annotation({
|
|
2254
2492
|
motivation: event.motivation,
|
|
2255
|
-
target: { source:
|
|
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(
|
|
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(
|
|
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,
|
|
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
|