@semiont/sdk 0.5.5 → 0.5.7
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 +3 -3
- package/dist/index.d.ts +65 -21
- package/dist/index.js +140 -51
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ const client = new SemiontClient(localTransport, localContentTransport);
|
|
|
87
87
|
- **Collaboration primitives** — fire-and-forget signals on the verb namespaces (`beckon.hover`, `bind.initiate`, `mark.changeShape`, `browse.click`, ...) coordinate attention and intent across participants. Not afterthoughts, not browser-app fluff: they're how a multi-participant session stays coherent.
|
|
88
88
|
- **Session layer** — `SemiontSession` (per-KB authentication, token refresh, lifecycle), `SemiontBrowser` (multi-KB orchestration), and `SessionStorage` adapters (`InMemorySessionStorage`, plus a browser-backed one in `@semiont/react-ui`).
|
|
89
89
|
- **Flow state machines** — RxJS-based factories (`createMarkStateUnit`, `createGatherStateUnit`, `createMatchStateUnit`, `createYieldStateUnit`, `createBeckonStateUnit`) that wrap each long-running flow with `loading$` / `error$` / progress observables. UI-shape-agnostic — any consumer (browser, terminal, mobile, daemon) can subscribe.
|
|
90
|
-
- **`WorkerBus`** — the transport-neutral channel-bus interface that worker-side adapters consume. Domain-specific worker adapters live with their domain — `createJobClaimAdapter`
|
|
90
|
+
- **`WorkerBus`** — the transport-neutral channel-bus interface that worker-side adapters consume. Domain-specific worker adapters live with their domain — `createJobClaimAdapter` in [`@semiont/jobs`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/jobs); `createSmelterActorStateUnit` in [`@semiont/make-meaning`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/make-meaning) — and consume `WorkerBus` from here.
|
|
91
91
|
- **Helpers** — `bus-request` (correlation-ID request/reply), the cache primitive backing live queries, and `createSearchPipeline` (debounced-search RxJS pipeline).
|
|
92
92
|
|
|
93
93
|
Page-shaped state machines (admin tables, compose page, resource viewer page, etc.) live in [`@semiont/react-ui`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/react-ui), alongside the components that render them. Those are framework-neutral but tied to the Semiont web frontend's specific page taxonomy; they don't apply to non-web consumers.
|
|
@@ -104,7 +104,7 @@ Page-shaped state machines (admin tables, compose page, resource viewer page, et
|
|
|
104
104
|
- The transport-neutral `WorkerBus` interface (worker adapters live in their domain packages — `@semiont/jobs`, `@semiont/make-meaning`)
|
|
105
105
|
- Branded ID types, the unified error hierarchy, the `TransportErrorCode` neutral vocabulary
|
|
106
106
|
|
|
107
|
-
Nothing page-shaped, nothing web-shell-shaped. A TUI, mobile reader, daemon, or AI agent installs `@semiont/sdk` alone (plus a transport package — `@semiont/
|
|
107
|
+
Nothing page-shaped, nothing web-shell-shaped. A TUI, mobile reader, daemon, or AI agent installs `@semiont/sdk` alone (plus a transport package — `@semiont/http-transport` for HTTP, `@semiont/make-meaning` for in-process).
|
|
108
108
|
|
|
109
109
|
## Installation
|
|
110
110
|
|
|
@@ -245,7 +245,7 @@ Apache-2.0 — see [LICENSE](https://github.com/The-AI-Alliance/semiont/blob/mai
|
|
|
245
245
|
## Related packages
|
|
246
246
|
|
|
247
247
|
- [`@semiont/core`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/core) — domain types, `ITransport` contract, OpenAPI-derived schemas
|
|
248
|
-
- [`@semiont/
|
|
248
|
+
- [`@semiont/http-transport`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/http-transport) — HTTP transport (`HttpTransport`, `HttpContentTransport`)
|
|
249
249
|
- [`@semiont/make-meaning`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/make-meaning) — in-process transport (`LocalTransport`) and the actor model behind it
|
|
250
250
|
- [`@semiont/observability`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/observability) — OpenTelemetry tracing the SDK propagates across the bus
|
|
251
251
|
- [`@semiont/react-ui`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/react-ui) — React bindings (`useStateUnit`, web `SessionStorage`)
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { firstValueFrom, lastValueFrom } from 'rxjs';
|
|
|
4
4
|
import * as _semiont_core from '@semiont/core';
|
|
5
5
|
import { ResourceId, components, UserDID, paths, BackendDownload, ProgressEvent, AnnotationId, BodyOperation, EventMap, ResourceDescriptor, Annotation, TagSchema, GraphConnection, Motivation, GatheredContext, JobId, ITransport, EventBus, IContentTransport, IBackendOperations, BaseUrl, AccessToken, SemiontError, ConnectionState, Selector } from '@semiont/core';
|
|
6
6
|
export { AccessToken, Annotation, AnnotationId, BaseUrl, BodyItem, BodyOperation, ConnectionState, EntityType, EventMap, GatheredContext, IContentTransport, ITransport, Logger, Motivation, RefreshToken, ResourceDescriptor, ResourceId, SemiontError, TagCategory, TagSchema, UserId, accessToken, annotationId, baseUrl, entityType, refreshToken, resourceId, userId } from '@semiont/core';
|
|
7
|
-
export { APIError, HttpContentTransport, HttpTransport, HttpTransportConfig, TokenRefresher } from '@semiont/
|
|
7
|
+
export { APIError, HttpContentTransport, HttpTransport, HttpTransportConfig, TokenRefresher } from '@semiont/http-transport';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Thenable Observable subclasses.
|
|
@@ -45,9 +45,12 @@ declare class StreamObservable<T> extends Observable<T> implements PromiseLike<T
|
|
|
45
45
|
* cache entry. Used by Browse live-query methods (`browse.resource`,
|
|
46
46
|
* `browse.annotations`, etc.).
|
|
47
47
|
*
|
|
48
|
-
* Awaiting
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Awaiting (the one-shot path) fetches a **fresh** value via the optional
|
|
49
|
+
* `fetchFresh` action and rejects on failure — a re-read reflects writes
|
|
50
|
+
* (#847). Subscribing yields the SWR sequence: the initial `undefined`, the
|
|
51
|
+
* loaded value, and re-emits on invalidation. (Without a `fetchFresh` action
|
|
52
|
+
* — e.g. a non-cache wrapper — the await falls back to the first
|
|
53
|
+
* non-undefined emission.)
|
|
51
54
|
*
|
|
52
55
|
* The class is parameterized as `CacheObservable<T>` even though the
|
|
53
56
|
* stream's element type is `T | undefined` — `T` is what the consumer
|
|
@@ -56,10 +59,22 @@ declare class StreamObservable<T> extends Observable<T> implements PromiseLike<T
|
|
|
56
59
|
* `.pipe` in the natural way.
|
|
57
60
|
*/
|
|
58
61
|
declare class CacheObservable<T> extends Observable<T | undefined> implements PromiseLike<T> {
|
|
62
|
+
/**
|
|
63
|
+
* Optional one-shot fresh-fetch action. When present, `then()` (the await
|
|
64
|
+
* path) resolves to a freshly fetched value and rejects on fetch failure —
|
|
65
|
+
* so a re-read reflects writes (#847). `.subscribe(...)` never uses it: it
|
|
66
|
+
* keeps the stale-while-revalidate cached view over `source`.
|
|
67
|
+
*/
|
|
68
|
+
private fetchFresh?;
|
|
59
69
|
then<R1 = T, R2 = never>(onfulfilled?: ((v: T) => R1 | PromiseLike<R1>) | null, onrejected?: ((e: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
60
70
|
/**
|
|
61
71
|
* Wrap an existing Observable's subscribe behavior in a `CacheObservable`.
|
|
62
72
|
*
|
|
73
|
+
* `fetchFresh`, when supplied, backs the await path: `await` resolves to a
|
|
74
|
+
* freshly fetched value (rejecting on failure), so a one-shot read reflects
|
|
75
|
+
* writes without a scoped subscription (#847). `.subscribe(...)` consumers
|
|
76
|
+
* keep the SWR view over `source`.
|
|
77
|
+
*
|
|
63
78
|
* Memoizes on source identity: passing the same `source` returns the same
|
|
64
79
|
* wrapper instance. The Browse cache primitive already returns a stable
|
|
65
80
|
* Observable per key (its B4 contract), so this preserves that contract
|
|
@@ -69,7 +84,7 @@ declare class CacheObservable<T> extends Observable<T | undefined> implements Pr
|
|
|
69
84
|
*
|
|
70
85
|
* Backed by a `WeakMap`, so wrappers are GC'd when their source is.
|
|
71
86
|
*/
|
|
72
|
-
static from<T>(source: Observable<T | undefined>): CacheObservable<T>;
|
|
87
|
+
static from<T>(source: Observable<T | undefined>, fetchFresh?: () => Promise<T>): CacheObservable<T>;
|
|
73
88
|
}
|
|
74
89
|
/**
|
|
75
90
|
* Discriminated phases of an upload's lifecycle.
|
|
@@ -137,6 +152,7 @@ declare class UploadObservable extends Observable<UploadProgress> implements Pro
|
|
|
137
152
|
*/
|
|
138
153
|
|
|
139
154
|
type StoredEventResponse$1 = components['schemas']['StoredEventResponse'];
|
|
155
|
+
type GetResourceResponse$1 = components['schemas']['GetResourceResponse'];
|
|
140
156
|
type GatherProgress = components['schemas']['GatherProgress'];
|
|
141
157
|
type MatchSearchResult = components['schemas']['MatchSearchResult'];
|
|
142
158
|
type JobProgress$2 = components['schemas']['JobProgress'];
|
|
@@ -294,6 +310,7 @@ interface BrowseNamespace$1 {
|
|
|
294
310
|
limit?: number;
|
|
295
311
|
archived?: boolean;
|
|
296
312
|
search?: string;
|
|
313
|
+
entityType?: string;
|
|
297
314
|
}): CacheObservable<ResourceDescriptor[]>;
|
|
298
315
|
annotations(resourceId: ResourceId): CacheObservable<Annotation[]>;
|
|
299
316
|
annotation(resourceId: ResourceId, annotationId: AnnotationId): CacheObservable<Annotation>;
|
|
@@ -302,15 +319,12 @@ interface BrowseNamespace$1 {
|
|
|
302
319
|
referencedBy(resourceId: ResourceId): CacheObservable<ReferencedByEntry[]>;
|
|
303
320
|
events(resourceId: ResourceId): CacheObservable<StoredEventResponse$1[]>;
|
|
304
321
|
resourceContent(resourceId: ResourceId): Promise<string>;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}): Promise<{
|
|
322
|
+
resourceGraph(resourceId: ResourceId): Promise<GetResourceResponse$1>;
|
|
323
|
+
resourceRepresentation(resourceId: ResourceId): Promise<{
|
|
308
324
|
data: ArrayBuffer;
|
|
309
325
|
contentType: string;
|
|
310
326
|
}>;
|
|
311
|
-
resourceRepresentationStream(resourceId: ResourceId
|
|
312
|
-
accept?: string;
|
|
313
|
-
}): Promise<{
|
|
327
|
+
resourceRepresentationStream(resourceId: ResourceId): Promise<{
|
|
314
328
|
stream: ReadableStream<Uint8Array>;
|
|
315
329
|
contentType: string;
|
|
316
330
|
}>;
|
|
@@ -541,10 +555,12 @@ interface AdminNamespace$1 {
|
|
|
541
555
|
}
|
|
542
556
|
|
|
543
557
|
type StoredEventResponse = components['schemas']['StoredEventResponse'];
|
|
558
|
+
type GetResourceResponse = components['schemas']['GetResourceResponse'];
|
|
544
559
|
type ResourceListFilters = {
|
|
545
560
|
limit?: number;
|
|
546
561
|
archived?: boolean;
|
|
547
562
|
search?: string;
|
|
563
|
+
entityType?: string;
|
|
548
564
|
};
|
|
549
565
|
declare class BrowseNamespace implements BrowseNamespace$1 {
|
|
550
566
|
private readonly transport;
|
|
@@ -578,7 +594,33 @@ declare class BrowseNamespace implements BrowseNamespace$1 {
|
|
|
578
594
|
* `distinctUntilChanged` at a higher level — would misbehave.
|
|
579
595
|
*/
|
|
580
596
|
private readonly annotationListObs;
|
|
597
|
+
/**
|
|
598
|
+
* Per-source memo for the scope-acquiring wrapper (#847 Phase 4), keyed by
|
|
599
|
+
* the underlying (stable, per-key) cache observable so the wrapped
|
|
600
|
+
* observable is itself stable per key — preserving B4/B11 referential
|
|
601
|
+
* identity through to `CacheObservable.from`'s own memo.
|
|
602
|
+
*/
|
|
603
|
+
private readonly scopedSources;
|
|
581
604
|
constructor(transport: ITransport, bus: EventBus, content: IContentTransport);
|
|
605
|
+
/**
|
|
606
|
+
* Wrap a resource-scoped live query's source so that *subscribing* acquires
|
|
607
|
+
* the resource's scope (via the transport's ref-counted
|
|
608
|
+
* `subscribeToResource`) and the last unsubscribe releases it (#847 Phase 4).
|
|
609
|
+
* Freshness follows observation: a `.subscribe()` keeps `rId`'s scoped
|
|
610
|
+
* events flowing — so `mark:*` / entity-tag invalidations reach this cache —
|
|
611
|
+
* with no separate `subscribeToResource` call from the consumer.
|
|
612
|
+
*
|
|
613
|
+
* The one-shot `await` path does NOT go through here (it resolves via the
|
|
614
|
+
* cache's `fetch` — see `CacheObservable.from`'s `fetchFresh`), so a
|
|
615
|
+
* one-shot read acquires no scope.
|
|
616
|
+
*
|
|
617
|
+
* Memoized per source so the wrapped observable is stable per key (B4/B11).
|
|
618
|
+
* Each subscription calls `subscribeToResource(rId)`; the transport
|
|
619
|
+
* ref-counts concurrent subscriptions for the same resource onto a single
|
|
620
|
+
* SSE scope. Single-scope model unchanged — multi-scope is deferred (see
|
|
621
|
+
* `.plans/MULTI-RESOURCE-SCOPE.md`).
|
|
622
|
+
*/
|
|
623
|
+
private withScope;
|
|
582
624
|
resource(resourceId: ResourceId): CacheObservable<ResourceDescriptor>;
|
|
583
625
|
resources(filters?: ResourceListFilters): CacheObservable<ResourceDescriptor[]>;
|
|
584
626
|
annotations(resourceId: ResourceId): CacheObservable<Annotation[]>;
|
|
@@ -588,15 +630,18 @@ declare class BrowseNamespace implements BrowseNamespace$1 {
|
|
|
588
630
|
referencedBy(resourceId: ResourceId): CacheObservable<ReferencedByEntry[]>;
|
|
589
631
|
events(resourceId: ResourceId): CacheObservable<StoredEventResponse[]>;
|
|
590
632
|
resourceContent(resourceId: ResourceId): Promise<string>;
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
633
|
+
/**
|
|
634
|
+
* Fetch the resource's JSON-LD metadata graph (descriptor + annotations +
|
|
635
|
+
* inbound entity references). One-shot, uncached, dereferenced via the
|
|
636
|
+
* transport's HTTP `/jsonld` face (bus-free) — the LD view an external
|
|
637
|
+
* linked-data client gets. See `.plans/SIMPLER-JSON-LD.md` §5.
|
|
638
|
+
*/
|
|
639
|
+
resourceGraph(resourceId: ResourceId): Promise<GetResourceResponse>;
|
|
640
|
+
resourceRepresentation(resourceId: ResourceId): Promise<{
|
|
594
641
|
data: ArrayBuffer;
|
|
595
642
|
contentType: string;
|
|
596
643
|
}>;
|
|
597
|
-
resourceRepresentationStream(resourceId: ResourceId
|
|
598
|
-
accept?: string;
|
|
599
|
-
}): Promise<{
|
|
644
|
+
resourceRepresentationStream(resourceId: ResourceId): Promise<{
|
|
600
645
|
stream: ReadableStream<Uint8Array>;
|
|
601
646
|
contentType: string;
|
|
602
647
|
}>;
|
|
@@ -768,9 +813,9 @@ declare class JobNamespace implements JobNamespace$1 {
|
|
|
768
813
|
get queued$(): Observable<EventMap['job:queued']>;
|
|
769
814
|
/** Live stream of `job:report-progress` events. */
|
|
770
815
|
get progress$(): Observable<EventMap['job:report-progress']>;
|
|
771
|
-
/** Live stream of `job:complete` events. */
|
|
816
|
+
/** Live stream of `job:complete` events (global; filter by `jobId`). */
|
|
772
817
|
get complete$(): Observable<EventMap['job:complete']>;
|
|
773
|
-
/** Live stream of `job:fail` events. */
|
|
818
|
+
/** Live stream of `job:fail` events (global; filter by `jobId`). */
|
|
774
819
|
get fail$(): Observable<EventMap['job:fail']>;
|
|
775
820
|
status(jobId: JobId): Promise<JobStatusResponse>;
|
|
776
821
|
pollUntilComplete(jobId: JobId, options?: {
|
|
@@ -879,7 +924,6 @@ declare class SemiontClient {
|
|
|
879
924
|
constructor(transport: ITransport, content: IContentTransport, backend?: IBackendOperations);
|
|
880
925
|
/** Transport-level connection state. HTTP reflects SSE health; local is always 'connected'. */
|
|
881
926
|
get state$(): rxjs.Observable<_semiont_core.ConnectionState>;
|
|
882
|
-
subscribeToResource(resourceId: ResourceId): () => void;
|
|
883
927
|
dispose(): void;
|
|
884
928
|
/**
|
|
885
929
|
* Convenience factory for the default HTTP setup. Constructs a
|
|
@@ -1678,7 +1722,7 @@ declare function createSearchPipeline<T>(fetch: (query: string) => Observable<T[
|
|
|
1678
1722
|
* (e.g. `JobClaimAdapter` in `@semiont/jobs`, `SmelterActorStateUnit` in
|
|
1679
1723
|
* `@semiont/make-meaning`) need.
|
|
1680
1724
|
*
|
|
1681
|
-
* Transport-neutral by design. HTTP `ActorStateUnit` (from `@semiont/
|
|
1725
|
+
* Transport-neutral by design. HTTP `ActorStateUnit` (from `@semiont/http-transport`)
|
|
1682
1726
|
* satisfies it directly; an in-process worker can pass a small shim around
|
|
1683
1727
|
* an `EventBus` with a `() => Promise<void>` `emit` that calls into the
|
|
1684
1728
|
* actor system.
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { SemiontError, annotationId, resourceId, email, googleCredential, refreshToken, EventBus, baseUrl, accessToken, searchQuery } from '@semiont/core';
|
|
1
|
+
import { SemiontError, decodeWithCharset, annotationId, resourceId, email, googleCredential, refreshToken, EventBus, baseUrl, accessToken, searchQuery } from '@semiont/core';
|
|
2
2
|
export { SemiontError, accessToken, annotationId, baseUrl, entityType, refreshToken, resourceId, userId } from '@semiont/core';
|
|
3
3
|
import { Observable, lastValueFrom, firstValueFrom, merge, TimeoutError, throwError, map as map$1, BehaviorSubject, Subject, Subscription, of, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
4
4
|
export { firstValueFrom, lastValueFrom } from 'rxjs';
|
|
5
5
|
import { filter, map, take, timeout, catchError, takeUntil, startWith, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
|
|
6
|
-
import { HttpTransport, HttpContentTransport, APIError } from '@semiont/
|
|
7
|
-
export { APIError, HttpContentTransport, HttpTransport } from '@semiont/
|
|
6
|
+
import { HttpTransport, HttpContentTransport, APIError } from '@semiont/http-transport';
|
|
7
|
+
export { APIError, HttpContentTransport, HttpTransport } from '@semiont/http-transport';
|
|
8
8
|
|
|
9
9
|
// src/client.ts
|
|
10
10
|
var StreamObservable = class _StreamObservable extends Observable {
|
|
@@ -17,12 +17,27 @@ var StreamObservable = class _StreamObservable extends Observable {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
var CacheObservable = class _CacheObservable extends Observable {
|
|
20
|
+
/**
|
|
21
|
+
* Optional one-shot fresh-fetch action. When present, `then()` (the await
|
|
22
|
+
* path) resolves to a freshly fetched value and rejects on fetch failure —
|
|
23
|
+
* so a re-read reflects writes (#847). `.subscribe(...)` never uses it: it
|
|
24
|
+
* keeps the stale-while-revalidate cached view over `source`.
|
|
25
|
+
*/
|
|
26
|
+
fetchFresh;
|
|
20
27
|
then(onfulfilled, onrejected) {
|
|
28
|
+
if (this.fetchFresh) {
|
|
29
|
+
return this.fetchFresh().then(onfulfilled, onrejected);
|
|
30
|
+
}
|
|
21
31
|
return firstValueFrom(this.pipe(filter((v) => v !== void 0))).then(onfulfilled, onrejected);
|
|
22
32
|
}
|
|
23
33
|
/**
|
|
24
34
|
* Wrap an existing Observable's subscribe behavior in a `CacheObservable`.
|
|
25
35
|
*
|
|
36
|
+
* `fetchFresh`, when supplied, backs the await path: `await` resolves to a
|
|
37
|
+
* freshly fetched value (rejecting on failure), so a one-shot read reflects
|
|
38
|
+
* writes without a scoped subscription (#847). `.subscribe(...)` consumers
|
|
39
|
+
* keep the SWR view over `source`.
|
|
40
|
+
*
|
|
26
41
|
* Memoizes on source identity: passing the same `source` returns the same
|
|
27
42
|
* wrapper instance. The Browse cache primitive already returns a stable
|
|
28
43
|
* Observable per key (its B4 contract), so this preserves that contract
|
|
@@ -32,10 +47,11 @@ var CacheObservable = class _CacheObservable extends Observable {
|
|
|
32
47
|
*
|
|
33
48
|
* Backed by a `WeakMap`, so wrappers are GC'd when their source is.
|
|
34
49
|
*/
|
|
35
|
-
static from(source) {
|
|
50
|
+
static from(source, fetchFresh) {
|
|
36
51
|
let wrapper = wrapperCache.get(source);
|
|
37
52
|
if (!wrapper) {
|
|
38
53
|
wrapper = new _CacheObservable((subscriber) => source.subscribe(subscriber));
|
|
54
|
+
wrapper.fetchFresh = fetchFresh;
|
|
39
55
|
wrapperCache.set(source, wrapper);
|
|
40
56
|
}
|
|
41
57
|
return wrapper;
|
|
@@ -110,25 +126,31 @@ async function busRequest(bus, emitChannel, payload, resultChannel, failureChann
|
|
|
110
126
|
}
|
|
111
127
|
function createCache(fetchFn) {
|
|
112
128
|
const store$ = new BehaviorSubject(/* @__PURE__ */ new Map());
|
|
113
|
-
const inflight = /* @__PURE__ */ new
|
|
129
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
114
130
|
const obsCache = /* @__PURE__ */ new Map();
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
const runFetch = (key) => {
|
|
132
|
+
const existing = inflight.get(key);
|
|
133
|
+
if (existing) return existing;
|
|
134
|
+
let p;
|
|
135
|
+
p = (async () => {
|
|
136
|
+
try {
|
|
137
|
+
const value = await fetchFn(key);
|
|
138
|
+
const next = new Map(store$.value);
|
|
139
|
+
next.set(key, value);
|
|
140
|
+
store$.next(next);
|
|
141
|
+
return value;
|
|
142
|
+
} finally {
|
|
143
|
+
if (inflight.get(key) === p) inflight.delete(key);
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
inflight.set(key, p);
|
|
147
|
+
return p;
|
|
127
148
|
};
|
|
128
149
|
return {
|
|
129
150
|
observe(key) {
|
|
130
151
|
if (!store$.value.has(key) && !inflight.has(key)) {
|
|
131
|
-
void
|
|
152
|
+
void runFetch(key).catch(() => {
|
|
153
|
+
});
|
|
132
154
|
}
|
|
133
155
|
let obs = obsCache.get(key);
|
|
134
156
|
if (!obs) {
|
|
@@ -140,6 +162,9 @@ function createCache(fetchFn) {
|
|
|
140
162
|
}
|
|
141
163
|
return obs;
|
|
142
164
|
},
|
|
165
|
+
fetch(key) {
|
|
166
|
+
return runFetch(key);
|
|
167
|
+
},
|
|
143
168
|
get(key) {
|
|
144
169
|
return store$.value.get(key);
|
|
145
170
|
},
|
|
@@ -148,7 +173,8 @@ function createCache(fetchFn) {
|
|
|
148
173
|
},
|
|
149
174
|
invalidate(key) {
|
|
150
175
|
inflight.delete(key);
|
|
151
|
-
void
|
|
176
|
+
void runFetch(key).catch(() => {
|
|
177
|
+
});
|
|
152
178
|
},
|
|
153
179
|
remove(key) {
|
|
154
180
|
const next = new Map(store$.value);
|
|
@@ -164,7 +190,8 @@ function createCache(fetchFn) {
|
|
|
164
190
|
invalidateAll() {
|
|
165
191
|
for (const key of store$.value.keys()) {
|
|
166
192
|
inflight.delete(key);
|
|
167
|
-
void
|
|
193
|
+
void runFetch(key).catch(() => {
|
|
194
|
+
});
|
|
168
195
|
}
|
|
169
196
|
},
|
|
170
197
|
dispose() {
|
|
@@ -199,7 +226,13 @@ var BrowseNamespace = class {
|
|
|
199
226
|
const result = await busRequest(
|
|
200
227
|
this.transport,
|
|
201
228
|
"browse:resources-requested",
|
|
202
|
-
{
|
|
229
|
+
{
|
|
230
|
+
search,
|
|
231
|
+
archived: filters.archived,
|
|
232
|
+
entityType: filters.entityType,
|
|
233
|
+
limit: filters.limit ?? 100,
|
|
234
|
+
offset: 0
|
|
235
|
+
},
|
|
203
236
|
"browse:resources-result",
|
|
204
237
|
"browse:resources-failed"
|
|
205
238
|
);
|
|
@@ -270,6 +303,9 @@ var BrowseNamespace = class {
|
|
|
270
303
|
});
|
|
271
304
|
this.subscribeToEvents();
|
|
272
305
|
}
|
|
306
|
+
transport;
|
|
307
|
+
bus;
|
|
308
|
+
content;
|
|
273
309
|
// ── Caches, backed by the RxJS-native `Cache<K, V>` primitive ───────────
|
|
274
310
|
//
|
|
275
311
|
// Each cache encapsulates the BehaviorSubject store, in-flight guard,
|
|
@@ -306,18 +342,58 @@ var BrowseNamespace = class {
|
|
|
306
342
|
* `distinctUntilChanged` at a higher level — would misbehave.
|
|
307
343
|
*/
|
|
308
344
|
annotationListObs = /* @__PURE__ */ new Map();
|
|
345
|
+
/**
|
|
346
|
+
* Per-source memo for the scope-acquiring wrapper (#847 Phase 4), keyed by
|
|
347
|
+
* the underlying (stable, per-key) cache observable so the wrapped
|
|
348
|
+
* observable is itself stable per key — preserving B4/B11 referential
|
|
349
|
+
* identity through to `CacheObservable.from`'s own memo.
|
|
350
|
+
*/
|
|
351
|
+
scopedSources = /* @__PURE__ */ new WeakMap();
|
|
352
|
+
/**
|
|
353
|
+
* Wrap a resource-scoped live query's source so that *subscribing* acquires
|
|
354
|
+
* the resource's scope (via the transport's ref-counted
|
|
355
|
+
* `subscribeToResource`) and the last unsubscribe releases it (#847 Phase 4).
|
|
356
|
+
* Freshness follows observation: a `.subscribe()` keeps `rId`'s scoped
|
|
357
|
+
* events flowing — so `mark:*` / entity-tag invalidations reach this cache —
|
|
358
|
+
* with no separate `subscribeToResource` call from the consumer.
|
|
359
|
+
*
|
|
360
|
+
* The one-shot `await` path does NOT go through here (it resolves via the
|
|
361
|
+
* cache's `fetch` — see `CacheObservable.from`'s `fetchFresh`), so a
|
|
362
|
+
* one-shot read acquires no scope.
|
|
363
|
+
*
|
|
364
|
+
* Memoized per source so the wrapped observable is stable per key (B4/B11).
|
|
365
|
+
* Each subscription calls `subscribeToResource(rId)`; the transport
|
|
366
|
+
* ref-counts concurrent subscriptions for the same resource onto a single
|
|
367
|
+
* SSE scope. Single-scope model unchanged — multi-scope is deferred (see
|
|
368
|
+
* `.plans/MULTI-RESOURCE-SCOPE.md`).
|
|
369
|
+
*/
|
|
370
|
+
withScope(rId, source) {
|
|
371
|
+
let scoped = this.scopedSources.get(source);
|
|
372
|
+
if (!scoped) {
|
|
373
|
+
scoped = new Observable((subscriber) => {
|
|
374
|
+
const release = this.transport.subscribeToResource(rId);
|
|
375
|
+
const inner = source.subscribe(subscriber);
|
|
376
|
+
return () => {
|
|
377
|
+
inner.unsubscribe();
|
|
378
|
+
release();
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
this.scopedSources.set(source, scoped);
|
|
382
|
+
}
|
|
383
|
+
return scoped;
|
|
384
|
+
}
|
|
309
385
|
// ── Live queries ────────────────────────────────────────────────────────
|
|
310
386
|
//
|
|
311
387
|
// These return `CacheObservable<T>`: subscribers see `T | undefined`
|
|
312
388
|
// (with `undefined` during initial load), and `await` resolves to the
|
|
313
389
|
// first non-undefined value.
|
|
314
390
|
resource(resourceId2) {
|
|
315
|
-
return CacheObservable.from(this.resourceCache.observe(resourceId2));
|
|
391
|
+
return CacheObservable.from(this.withScope(resourceId2, this.resourceCache.observe(resourceId2)), () => this.resourceCache.fetch(resourceId2));
|
|
316
392
|
}
|
|
317
393
|
resources(filters) {
|
|
318
394
|
const key = JSON.stringify(filters ?? {});
|
|
319
395
|
this.resourceListFilters.set(key, filters ?? {});
|
|
320
|
-
return CacheObservable.from(this.resourceListCache.observe(key));
|
|
396
|
+
return CacheObservable.from(this.resourceListCache.observe(key), () => this.resourceListCache.fetch(key));
|
|
321
397
|
}
|
|
322
398
|
annotations(resourceId2) {
|
|
323
399
|
let obs = this.annotationListObs.get(resourceId2);
|
|
@@ -325,35 +401,43 @@ var BrowseNamespace = class {
|
|
|
325
401
|
obs = this.annotationListCache.observe(resourceId2).pipe(map$1((r) => r?.annotations));
|
|
326
402
|
this.annotationListObs.set(resourceId2, obs);
|
|
327
403
|
}
|
|
328
|
-
return CacheObservable.from(obs);
|
|
404
|
+
return CacheObservable.from(this.withScope(resourceId2, obs), () => this.annotationListCache.fetch(resourceId2).then((r) => r.annotations));
|
|
329
405
|
}
|
|
330
406
|
annotation(resourceId2, annotationId2) {
|
|
331
407
|
this.annotationResources.set(annotationId2, resourceId2);
|
|
332
|
-
return CacheObservable.from(this.annotationDetailCache.observe(annotationId2));
|
|
408
|
+
return CacheObservable.from(this.withScope(resourceId2, this.annotationDetailCache.observe(annotationId2)), () => this.annotationDetailCache.fetch(annotationId2));
|
|
333
409
|
}
|
|
334
410
|
entityTypes() {
|
|
335
|
-
return CacheObservable.from(this.entityTypesCache.observe(ENTITY_TYPES_KEY));
|
|
411
|
+
return CacheObservable.from(this.entityTypesCache.observe(ENTITY_TYPES_KEY), () => this.entityTypesCache.fetch(ENTITY_TYPES_KEY));
|
|
336
412
|
}
|
|
337
413
|
tagSchemas() {
|
|
338
|
-
return CacheObservable.from(this.tagSchemasCache.observe(TAG_SCHEMAS_KEY));
|
|
414
|
+
return CacheObservable.from(this.tagSchemasCache.observe(TAG_SCHEMAS_KEY), () => this.tagSchemasCache.fetch(TAG_SCHEMAS_KEY));
|
|
339
415
|
}
|
|
340
416
|
referencedBy(resourceId2) {
|
|
341
|
-
return CacheObservable.from(this.referencedByCache.observe(resourceId2));
|
|
417
|
+
return CacheObservable.from(this.withScope(resourceId2, this.referencedByCache.observe(resourceId2)), () => this.referencedByCache.fetch(resourceId2));
|
|
342
418
|
}
|
|
343
419
|
events(resourceId2) {
|
|
344
|
-
return CacheObservable.from(this.resourceEventsCache.observe(resourceId2));
|
|
420
|
+
return CacheObservable.from(this.withScope(resourceId2, this.resourceEventsCache.observe(resourceId2)), () => this.resourceEventsCache.fetch(resourceId2));
|
|
345
421
|
}
|
|
346
422
|
// ── One-shot reads ──────────────────────────────────────────────────────
|
|
347
423
|
async resourceContent(resourceId2) {
|
|
348
|
-
const result = await this.content.getBinary(resourceId2
|
|
349
|
-
|
|
350
|
-
|
|
424
|
+
const result = await this.content.getBinary(resourceId2);
|
|
425
|
+
return decodeWithCharset(result.data, result.contentType);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Fetch the resource's JSON-LD metadata graph (descriptor + annotations +
|
|
429
|
+
* inbound entity references). One-shot, uncached, dereferenced via the
|
|
430
|
+
* transport's HTTP `/jsonld` face (bus-free) — the LD view an external
|
|
431
|
+
* linked-data client gets. See `.plans/SIMPLER-JSON-LD.md` §5.
|
|
432
|
+
*/
|
|
433
|
+
async resourceGraph(resourceId2) {
|
|
434
|
+
return this.content.getResourceGraph(resourceId2);
|
|
351
435
|
}
|
|
352
|
-
async resourceRepresentation(resourceId2
|
|
353
|
-
return this.content.getBinary(resourceId2
|
|
436
|
+
async resourceRepresentation(resourceId2) {
|
|
437
|
+
return this.content.getBinary(resourceId2);
|
|
354
438
|
}
|
|
355
|
-
async resourceRepresentationStream(resourceId2
|
|
356
|
-
return this.content.getBinaryStream(resourceId2
|
|
439
|
+
async resourceRepresentationStream(resourceId2) {
|
|
440
|
+
return this.content.getBinaryStream(resourceId2);
|
|
357
441
|
}
|
|
358
442
|
async resourceEvents(resourceId2) {
|
|
359
443
|
const result = await busRequest(
|
|
@@ -544,6 +628,8 @@ var MarkNamespace = class {
|
|
|
544
628
|
this.transport = transport;
|
|
545
629
|
this.bus = bus;
|
|
546
630
|
}
|
|
631
|
+
transport;
|
|
632
|
+
bus;
|
|
547
633
|
async annotation(input) {
|
|
548
634
|
const resourceId2 = resourceId(input.target.source);
|
|
549
635
|
const result = await busRequest(
|
|
@@ -569,7 +655,6 @@ var MarkNamespace = class {
|
|
|
569
655
|
let done = false;
|
|
570
656
|
let pollTimer = null;
|
|
571
657
|
let pollInterval = null;
|
|
572
|
-
let unsubscribeResource = this.transport.subscribeToResource(resourceId2);
|
|
573
658
|
const cleanup = () => {
|
|
574
659
|
done = true;
|
|
575
660
|
if (pollTimer) {
|
|
@@ -580,10 +665,6 @@ var MarkNamespace = class {
|
|
|
580
665
|
clearInterval(pollInterval);
|
|
581
666
|
pollInterval = null;
|
|
582
667
|
}
|
|
583
|
-
if (unsubscribeResource) {
|
|
584
|
-
unsubscribeResource();
|
|
585
|
-
unsubscribeResource = null;
|
|
586
|
-
}
|
|
587
668
|
};
|
|
588
669
|
const resetPollTimer = (jobId) => {
|
|
589
670
|
if (pollTimer) clearTimeout(pollTimer);
|
|
@@ -742,6 +823,8 @@ var BindNamespace = class {
|
|
|
742
823
|
this.transport = transport;
|
|
743
824
|
this.bus = bus;
|
|
744
825
|
}
|
|
826
|
+
transport;
|
|
827
|
+
bus;
|
|
745
828
|
async body(resourceId2, annotationId2, operations) {
|
|
746
829
|
await this.transport.emit("bind:update-body", {
|
|
747
830
|
correlationId: crypto.randomUUID(),
|
|
@@ -759,6 +842,8 @@ var GatherNamespace = class {
|
|
|
759
842
|
this.transport = transport;
|
|
760
843
|
this.bus = bus;
|
|
761
844
|
}
|
|
845
|
+
transport;
|
|
846
|
+
bus;
|
|
762
847
|
annotation(resourceId2, annotationId2, options) {
|
|
763
848
|
return new StreamObservable((subscriber) => {
|
|
764
849
|
const correlationId = crypto.randomUUID();
|
|
@@ -810,6 +895,8 @@ var MatchNamespace = class {
|
|
|
810
895
|
this.transport = transport;
|
|
811
896
|
this.bus = bus;
|
|
812
897
|
}
|
|
898
|
+
transport;
|
|
899
|
+
bus;
|
|
813
900
|
requestSearch(input) {
|
|
814
901
|
this.bus.get("match:search-requested").next(input);
|
|
815
902
|
}
|
|
@@ -853,6 +940,9 @@ var YieldNamespace = class {
|
|
|
853
940
|
this.bus = bus;
|
|
854
941
|
this.content = content;
|
|
855
942
|
}
|
|
943
|
+
transport;
|
|
944
|
+
bus;
|
|
945
|
+
content;
|
|
856
946
|
resource(data) {
|
|
857
947
|
const totalBytes = typeof Buffer !== "undefined" && data.file instanceof Buffer ? data.file.length : data.file.size;
|
|
858
948
|
return new UploadObservable((subscriber) => {
|
|
@@ -905,7 +995,6 @@ var YieldNamespace = class {
|
|
|
905
995
|
let done = false;
|
|
906
996
|
let pollTimer = null;
|
|
907
997
|
let pollInterval = null;
|
|
908
|
-
let unsubscribeResource = this.transport.subscribeToResource(resourceId2);
|
|
909
998
|
const cleanup = () => {
|
|
910
999
|
done = true;
|
|
911
1000
|
if (pollTimer) {
|
|
@@ -916,10 +1005,6 @@ var YieldNamespace = class {
|
|
|
916
1005
|
clearInterval(pollInterval);
|
|
917
1006
|
pollInterval = null;
|
|
918
1007
|
}
|
|
919
|
-
if (unsubscribeResource) {
|
|
920
|
-
unsubscribeResource();
|
|
921
|
-
unsubscribeResource = null;
|
|
922
|
-
}
|
|
923
1008
|
};
|
|
924
1009
|
const resetPollTimer = (jid) => {
|
|
925
1010
|
if (pollTimer) clearTimeout(pollTimer);
|
|
@@ -1062,6 +1147,8 @@ var BeckonNamespace = class {
|
|
|
1062
1147
|
this.transport = transport;
|
|
1063
1148
|
this.bus = bus;
|
|
1064
1149
|
}
|
|
1150
|
+
transport;
|
|
1151
|
+
bus;
|
|
1065
1152
|
attention(resourceId2, annotationId2) {
|
|
1066
1153
|
void this.transport.emit("beckon:focus", { annotationId: annotationId2, resourceId: resourceId2 });
|
|
1067
1154
|
}
|
|
@@ -1078,6 +1165,7 @@ var FrameNamespace = class {
|
|
|
1078
1165
|
constructor(transport) {
|
|
1079
1166
|
this.transport = transport;
|
|
1080
1167
|
}
|
|
1168
|
+
transport;
|
|
1081
1169
|
async addEntityType(type) {
|
|
1082
1170
|
await this.transport.emit("frame:add-entity-type", { tag: type });
|
|
1083
1171
|
}
|
|
@@ -1097,6 +1185,8 @@ var JobNamespace = class {
|
|
|
1097
1185
|
this.transport = transport;
|
|
1098
1186
|
this.bus = bus;
|
|
1099
1187
|
}
|
|
1188
|
+
transport;
|
|
1189
|
+
bus;
|
|
1100
1190
|
/**
|
|
1101
1191
|
* Live stream of `job:queued` events. Surfaces a typed view onto the
|
|
1102
1192
|
* underlying bus channel for consumers (CLIs, MCP handlers, widgets)
|
|
@@ -1109,11 +1199,11 @@ var JobNamespace = class {
|
|
|
1109
1199
|
get progress$() {
|
|
1110
1200
|
return this.bus.get("job:report-progress");
|
|
1111
1201
|
}
|
|
1112
|
-
/** Live stream of `job:complete` events. */
|
|
1202
|
+
/** Live stream of `job:complete` events (global; filter by `jobId`). */
|
|
1113
1203
|
get complete$() {
|
|
1114
1204
|
return this.bus.get("job:complete");
|
|
1115
1205
|
}
|
|
1116
|
-
/** Live stream of `job:fail` events. */
|
|
1206
|
+
/** Live stream of `job:fail` events (global; filter by `jobId`). */
|
|
1117
1207
|
get fail$() {
|
|
1118
1208
|
return this.bus.get("job:fail");
|
|
1119
1209
|
}
|
|
@@ -1153,6 +1243,7 @@ var AuthNamespace = class {
|
|
|
1153
1243
|
constructor(backend) {
|
|
1154
1244
|
this.backend = backend;
|
|
1155
1245
|
}
|
|
1246
|
+
backend;
|
|
1156
1247
|
async password(emailStr, passwordStr) {
|
|
1157
1248
|
return this.backend.authenticatePassword(email(emailStr), passwordStr);
|
|
1158
1249
|
}
|
|
@@ -1184,6 +1275,7 @@ var AdminNamespace = class {
|
|
|
1184
1275
|
constructor(backend) {
|
|
1185
1276
|
this.backend = backend;
|
|
1186
1277
|
}
|
|
1278
|
+
backend;
|
|
1187
1279
|
async users() {
|
|
1188
1280
|
const result = await this.backend.listUsers();
|
|
1189
1281
|
return result.users;
|
|
@@ -1306,9 +1398,6 @@ var SemiontClient = class _SemiontClient {
|
|
|
1306
1398
|
get state$() {
|
|
1307
1399
|
return this.transport.state$;
|
|
1308
1400
|
}
|
|
1309
|
-
subscribeToResource(resourceId2) {
|
|
1310
|
-
return this.transport.subscribeToResource(resourceId2);
|
|
1311
|
-
}
|
|
1312
1401
|
dispose() {
|
|
1313
1402
|
this.transport.dispose();
|
|
1314
1403
|
this.content.dispose();
|