@ny4n/client 0.0.0-stage → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mario Zechner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,72 @@
1
- # Temporary Holding Version
1
+ # @ny4n/client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Transport-neutral client for the experimental Nyan service protocol.
4
+
5
+ ```ts
6
+ import { Client, type ByteTransportFactory } from "@ny4n/client";
7
+
8
+ const transportFactory: ByteTransportFactory = async (handlers) => {
9
+ // Connect using WebSocket, Unix socket, or another ordered byte transport.
10
+ return {
11
+ async send(chunk) {
12
+ // Deliver bytes in invocation order and honor backpressure.
13
+ },
14
+ close() {},
15
+ };
16
+ };
17
+
18
+ const client = await Client.connect({
19
+ serverId: "01234567-89ab-4def-8123-456789abcdef",
20
+ transportFactory,
21
+ });
22
+ const result = await client.request(
23
+ { serverId: client.hello.serverId },
24
+ { serviceId: "example.service", member: "read", args: [] },
25
+ );
26
+ ```
27
+
28
+ The client verifies that the physical endpoint reports the expected logical `serverId`. Server-wide requests carry that ID, and every Session request carries the full live target `{ serverId, sessionId, attachmentId }`. The combined durable address prevents cross-server or cross-session misrouting; the server-generated attachment ID rejects delayed frames after switching or reattaching.
29
+
30
+ Typed server and Session APIs are provided by Chord service bindings owned by the application. `createClientServiceTransport()` adapts a lazily resolved server or Session route to a Chord transport; `request()` and `subscribeService()` remain its low-level primitives. The client uses Chord's service-control parsers and per-subscription state decoder; `@ny4n/protocol` only validates the routed envelope and strict-JSON boundary. A service subscription returns a complete provider snapshot; the binding installs it and then calls `start()` to release updates buffered during hydration. `Client` applies ordered out-of-band attachment changes but deliberately does not construct typed service proxies or interpret application contracts.
31
+
32
+ Application observation APIs such as the coding agent's `Transcript` are ordinary Chord services. The client does not interpret their snapshots or updates.
33
+
34
+ On disconnect or disposal, pending requests reject locally, but accepted work may still complete remotely before the attachment is released. The client clears its live attachment route. It never reconnects or replays requests automatically. After disconnection, call `reconnect()`, attach through the application's management service again, and explicitly repeat only operations known to be safe.
35
+
36
+ The experimental local coordinator only provides a stable endpoint and relays traffic. Replaceable server processes own Session and worker lifecycle outside the public client protocol.
37
+
38
+ Call transport handlers as follows:
39
+
40
+ - `handlers.onData(chunk)` for inbound bytes;
41
+ - `handlers.onClose()` for an orderly terminal close;
42
+ - `handlers.onError(error)` for transport failures.
43
+
44
+ A transport factory creates a fresh authenticated connection for each attempt. Requests are correlated by ID, and server failures are exposed as `ServerError`.
45
+
46
+ ## Unix-domain sockets
47
+
48
+ Node.js and Bun consumers can use the separate Unix transport:
49
+
50
+ ```ts
51
+ import { Client } from "@ny4n/client";
52
+ import { createUnixTransportFactory } from "@ny4n/client/unix";
53
+
54
+ const client = new Client({
55
+ serverId: "01234567-89ab-4def-8123-456789abcdef",
56
+ transportFactory: createUnixTransportFactory({ path: "/tmp/nyan.sock" }),
57
+ });
58
+ await client.connect();
59
+ ```
60
+
61
+ Unix discovery scans an explicit physical-route directory, derives each expected server ID from its filename, and verifies it through the existing handshake:
62
+
63
+ ```ts
64
+ import { discoverUnixServers } from "@ny4n/client/unix";
65
+
66
+ const routes = await discoverUnixServers({ directory: "/run/user/1000/nyan" });
67
+ // [{ serverId: "...", path: "/run/user/1000/nyan/<serverId>.sock" }]
68
+ ```
69
+
70
+ Malformed entries, non-sockets, stale or unresponsive endpoints, and server-ID mismatches are ignored. Discovery is read-only and probes at most 16 sockets concurrently. Unexpected filesystem and socket errors reject discovery. Pass `timeoutMs` to override the default probe timeout.
71
+
72
+ `ClientOptions.maxFrameLength` bounds protocol payloads. `maxPendingBytes` bounds queued Unix transport output. Configure matching limits on both peers.
@@ -0,0 +1,30 @@
1
+ import { type JsonValue, type RemoteServiceTransport, type ServiceCall, type ServiceCatalogueEntry, type ServiceMode, type ServiceProviderUpdate } from "@ny4n/chord";
2
+ import { type RpcTarget, type ServerHello, type SessionTarget } from "@ny4n/protocol";
3
+ import type { AttachmentChangeListener, ClientOptions, ConnectionState, ConnectionStateChange, ServiceSubscription, Unsubscribe } from "./types.ts";
4
+ type ServiceResult = JsonValue | undefined;
5
+ export declare class Client {
6
+ #private;
7
+ constructor(options: ClientOptions);
8
+ get disposed(): boolean;
9
+ get connectionState(): ConnectionState;
10
+ get connected(): boolean;
11
+ get serverId(): string;
12
+ get hello(): ServerHello | undefined;
13
+ get attachment(): SessionTarget | undefined;
14
+ static connect(options: ClientOptions): Promise<Client>;
15
+ connect(): Promise<ServerHello>;
16
+ reconnect(): Promise<ServerHello>;
17
+ disconnect(reason?: string): void;
18
+ onConnectionStateChange(listener: (change: ConnectionStateChange) => void): Unsubscribe;
19
+ onAttachmentChange(listener: AttachmentChangeListener): Unsubscribe;
20
+ /** Invoke one low-level protocol call against an explicit routed target. */
21
+ request(target: RpcTarget, call: ServiceCall, signal?: AbortSignal): Promise<ServiceResult>;
22
+ serviceCatalogue(target: RpcTarget, signal?: AbortSignal): Promise<readonly ServiceCatalogueEntry[]>;
23
+ subscribeService(target: RpcTarget, serviceId: string, mode: ServiceMode, listener: (update: ServiceProviderUpdate) => void | Promise<void>, signal?: AbortSignal): Promise<ServiceSubscription>;
24
+ dispose(): Promise<void>;
25
+ [Symbol.asyncDispose](): Promise<void>;
26
+ }
27
+ /** Adapts a lazily resolved routed client target to a Chord service transport. */
28
+ export declare function createClientServiceTransport(client: Client, getTarget: () => RpcTarget | undefined): RemoteServiceTransport;
29
+ export {};
30
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,SAAS,EAKd,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAG1B,MAAM,aAAa,CAAC;AAErB,OAAO,EAMN,KAAK,SAAS,EACd,KAAK,WAAW,EAEhB,KAAK,aAAa,EAClB,MAAM,gBAAgB,CAAC;AAIxB,OAAO,KAAK,EACX,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,EACX,MAAM,YAAY,CAAC;AAEpB,KAAK,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;AAmB3C,qBAAa,MAAM;;IAclB,YAAY,OAAO,EAAE,aAAa,EAejC;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,eAAe,IAAI,eAAe,CAErC;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,KAAK,IAAI,WAAW,GAAG,SAAS,CAEnC;IAED,IAAI,UAAU,IAAI,aAAa,GAAG,SAAS,CAE1C;IAED,OAAa,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5D;IAED,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,CAI9B;IAED,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,CAEhC;IAED,UAAU,CAAC,MAAM,SAAwB,GAAG,IAAI,CAE/C;IAED,uBAAuB,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,GAAG,WAAW,CAItF;IAED,kBAAkB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,WAAW,CAIlE;IAED,4EAA4E;IAC5E,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAE1F;IAEK,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAWzG;IAEK,gBAAgB,CACrB,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACjE,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA0D9B;IA+ID,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAavB;IAED,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAErC;CAiDD;AAED,kFAAkF;AAClF,wBAAgB,4BAA4B,CAC3C,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,SAAS,GAAG,SAAS,GACpC,sBAAsB,CAuBxB"}
package/dist/client.js ADDED
@@ -0,0 +1,388 @@
1
+ import { createServiceCatalogueCall, createServiceStateDecoder, createServiceSubscribeCall, createServiceUnsubscribeCall, parseServiceCall, parseServiceCatalogue, parseWireServiceProviderUpdate, parseWireServiceSubscriptionSnapshot, } from "@ny4n/chord";
2
+ import { BACKGROUND_CONTEXT } from "@ny4n/chord/context";
3
+ import { encodeClientMessage, isServerId, ProtocolValidationError, } from "@ny4n/protocol";
4
+ import { Connection } from "./connection.js";
5
+ import { ClientDisposedError, DisconnectedError, ServerError, toError } from "./errors.js";
6
+ import { createPromiseResolvers } from "./promise.js";
7
+ export class Client {
8
+ #options;
9
+ #connection;
10
+ #pendingRequests = new Map();
11
+ #connectionStateListeners = new Set();
12
+ #attachmentListeners = new Set();
13
+ #serviceListeners = new Map();
14
+ #requestSequence = 0;
15
+ #serviceSubscriptionSequence = 0;
16
+ #hello;
17
+ #attachment;
18
+ #disposed = false;
19
+ #disposePromise;
20
+ constructor(options) {
21
+ if (!isServerId(options.serverId)) {
22
+ throw new TypeError("serverId must be a canonical lowercase UUIDv4");
23
+ }
24
+ this.#options = options;
25
+ this.#connection = new Connection({
26
+ transportFactory: options.transportFactory,
27
+ serverId: options.serverId,
28
+ maxFrameLength: options.maxFrameLength,
29
+ onHandshake: (hello) => {
30
+ this.#hello = hello;
31
+ },
32
+ onMessage: (message) => this.#handleMessage(message),
33
+ onStateChange: (change) => this.#handleConnectionStateChange(change),
34
+ });
35
+ }
36
+ get disposed() {
37
+ return this.#disposed;
38
+ }
39
+ get connectionState() {
40
+ return this.#connection.state;
41
+ }
42
+ get connected() {
43
+ return this.#connection.state === "connected";
44
+ }
45
+ get serverId() {
46
+ return this.#options.serverId;
47
+ }
48
+ get hello() {
49
+ return this.#hello;
50
+ }
51
+ get attachment() {
52
+ return this.#attachment;
53
+ }
54
+ static async connect(options) {
55
+ const client = new Client(options);
56
+ try {
57
+ await client.connect();
58
+ return client;
59
+ }
60
+ catch (error) {
61
+ await client.dispose();
62
+ throw error;
63
+ }
64
+ }
65
+ connect() {
66
+ if (this.#disposed)
67
+ return Promise.reject(new ClientDisposedError());
68
+ this.#hello = undefined;
69
+ return this.#connection.connect();
70
+ }
71
+ reconnect() {
72
+ return this.connect();
73
+ }
74
+ disconnect(reason = "Client disconnected") {
75
+ this.#connection.disconnect(reason);
76
+ }
77
+ onConnectionStateChange(listener) {
78
+ this.#assertNotDisposed();
79
+ this.#connectionStateListeners.add(listener);
80
+ return () => this.#connectionStateListeners.delete(listener);
81
+ }
82
+ onAttachmentChange(listener) {
83
+ this.#assertNotDisposed();
84
+ this.#attachmentListeners.add(listener);
85
+ return () => this.#attachmentListeners.delete(listener);
86
+ }
87
+ /** Invoke one low-level protocol call against an explicit routed target. */
88
+ request(target, call, signal) {
89
+ return this.#request(target, call, signal);
90
+ }
91
+ async serviceCatalogue(target, signal) {
92
+ const result = await this.#request(target, createServiceCatalogueCall(), signal);
93
+ try {
94
+ return parseServiceCatalogue(result);
95
+ }
96
+ catch (error) {
97
+ const validationError = new ProtocolValidationError(error instanceof Error ? error.message : "Invalid service catalogue");
98
+ this.#connection.fail(validationError);
99
+ throw validationError;
100
+ }
101
+ }
102
+ async subscribeService(target, serviceId, mode, listener, signal) {
103
+ const subscriptionId = `service-${++this.#serviceSubscriptionSequence}`;
104
+ const active = {
105
+ target,
106
+ listener,
107
+ decoder: createServiceStateDecoder(),
108
+ queuedWireUpdates: [],
109
+ queued: [],
110
+ deliveryTail: Promise.resolve(),
111
+ hydrated: false,
112
+ ready: false,
113
+ };
114
+ this.#serviceListeners.set(subscriptionId, active);
115
+ let snapshot;
116
+ try {
117
+ snapshot = await this.#request(target, createServiceSubscribeCall(subscriptionId, serviceId, mode), signal, (result) => {
118
+ const decoded = active.decoder.decodeSnapshot(parseWireServiceSubscriptionSnapshot(result));
119
+ active.hydrated = true;
120
+ for (const update of active.queuedWireUpdates.splice(0)) {
121
+ active.queued.push(active.decoder.decodeUpdate(parseWireServiceProviderUpdate(update)));
122
+ }
123
+ return decoded;
124
+ });
125
+ }
126
+ catch (error) {
127
+ if (this.#serviceListeners.get(subscriptionId) === active)
128
+ this.#serviceListeners.delete(subscriptionId);
129
+ throw error;
130
+ }
131
+ if (this.#serviceListeners.get(subscriptionId) !== active)
132
+ throw new DisconnectedError();
133
+ let disposed = false;
134
+ return {
135
+ id: subscriptionId,
136
+ target,
137
+ snapshot,
138
+ start: () => {
139
+ if (disposed || active.ready)
140
+ return;
141
+ active.ready = true;
142
+ for (const update of active.queued.splice(0))
143
+ this.#deliverServiceUpdate(active, update);
144
+ },
145
+ dispose: async () => {
146
+ if (disposed)
147
+ return;
148
+ disposed = true;
149
+ if (this.#serviceListeners.get(subscriptionId) === active)
150
+ this.#serviceListeners.delete(subscriptionId);
151
+ try {
152
+ if (this.connected && this.#targetIsCurrent(target)) {
153
+ await this.#request(target, createServiceUnsubscribeCall(subscriptionId));
154
+ }
155
+ await active.deliveryTail;
156
+ }
157
+ finally {
158
+ active.queuedWireUpdates.length = 0;
159
+ active.queued.length = 0;
160
+ }
161
+ },
162
+ };
163
+ }
164
+ #request(target, call, signal, transform) {
165
+ if (this.#disposed)
166
+ return Promise.reject(new ClientDisposedError());
167
+ if (!this.connected)
168
+ return Promise.reject(new DisconnectedError());
169
+ if (signal?.aborted)
170
+ return Promise.reject(abortError(signal));
171
+ const id = `request-${++this.#requestSequence}`;
172
+ const { promise, resolve, reject } = createPromiseResolvers();
173
+ let sent = false;
174
+ let aborted = false;
175
+ let onAbort;
176
+ const sendCancel = () => {
177
+ if (!sent || !this.connected)
178
+ return;
179
+ try {
180
+ this.#connection.send(encodeClientMessage({ type: "cancel", id, target }, { maxFrameLength: this.#connection.maxFrameLength }));
181
+ }
182
+ catch (error) {
183
+ this.#connection.fail(toError(error));
184
+ }
185
+ };
186
+ if (signal !== undefined) {
187
+ onAbort = () => {
188
+ if (aborted)
189
+ return;
190
+ aborted = true;
191
+ reject(abortError(signal));
192
+ sendCancel();
193
+ };
194
+ signal.addEventListener("abort", onAbort, { once: true });
195
+ }
196
+ this.#pendingRequests.set(id, {
197
+ resolve: (result) => {
198
+ try {
199
+ resolve(transform === undefined ? result : transform(result));
200
+ }
201
+ catch (error) {
202
+ const validationError = new ProtocolValidationError(error instanceof Error ? error.message : "Invalid service operation stream");
203
+ this.#connection.fail(validationError);
204
+ reject(validationError);
205
+ }
206
+ },
207
+ reject,
208
+ cleanup: () => {
209
+ if (signal !== undefined && onAbort !== undefined)
210
+ signal.removeEventListener("abort", onAbort);
211
+ },
212
+ });
213
+ let frame;
214
+ try {
215
+ frame = encodeClientMessage({ type: "request", id, target, call: parseServiceCall(call) }, { maxFrameLength: this.#connection.maxFrameLength });
216
+ }
217
+ catch (error) {
218
+ this.#takePendingRequest(id)?.reject(toError(error));
219
+ return promise;
220
+ }
221
+ this.#connection.send(frame);
222
+ sent = true;
223
+ if (aborted)
224
+ sendCancel();
225
+ return promise;
226
+ }
227
+ #handleMessage(message) {
228
+ if (message.type === "attachment") {
229
+ if (message.attachment !== null && message.attachment.serverId !== this.#options.serverId) {
230
+ this.#connection.fail(new ProtocolValidationError("Attachment update belongs to another server"));
231
+ return;
232
+ }
233
+ this.#setAttachment(message.attachment ?? undefined);
234
+ return;
235
+ }
236
+ if (message.type === "service_update") {
237
+ const active = this.#serviceListeners.get(message.subscriptionId);
238
+ if (active === undefined)
239
+ return;
240
+ if (!active.hydrated) {
241
+ active.queuedWireUpdates.push(message.update);
242
+ return;
243
+ }
244
+ let update;
245
+ try {
246
+ update = active.decoder.decodeUpdate(parseWireServiceProviderUpdate(message.update));
247
+ }
248
+ catch (error) {
249
+ this.#connection.fail(new ProtocolValidationError(error instanceof Error ? error.message : "Invalid service operation stream"));
250
+ return;
251
+ }
252
+ if (active.ready)
253
+ this.#deliverServiceUpdate(active, update);
254
+ else
255
+ active.queued.push(update);
256
+ return;
257
+ }
258
+ const pending = this.#takePendingRequest(message.id);
259
+ if (!pending) {
260
+ this.#connection.fail(new ProtocolValidationError("Response has no matching request"));
261
+ return;
262
+ }
263
+ if (!message.ok) {
264
+ pending.reject(new ServerError(message.error));
265
+ return;
266
+ }
267
+ pending.resolve(message.result);
268
+ }
269
+ #handleConnectionStateChange(change) {
270
+ if (change.state === "disconnected") {
271
+ this.#hello = undefined;
272
+ this.#setAttachment(undefined);
273
+ this.#rejectPendingRequests(change.error ?? new DisconnectedError());
274
+ this.#serviceListeners.clear();
275
+ }
276
+ for (const listener of this.#connectionStateListeners) {
277
+ try {
278
+ listener(change);
279
+ }
280
+ catch (error) {
281
+ this.#reportListenerError(error);
282
+ }
283
+ }
284
+ }
285
+ #takePendingRequest(id) {
286
+ const request = this.#pendingRequests.get(id);
287
+ if (request) {
288
+ this.#pendingRequests.delete(id);
289
+ request.cleanup();
290
+ }
291
+ return request;
292
+ }
293
+ #rejectPendingRequests(error) {
294
+ const requests = [...this.#pendingRequests.values()];
295
+ this.#pendingRequests.clear();
296
+ for (const request of requests) {
297
+ request.cleanup();
298
+ request.reject(error);
299
+ }
300
+ }
301
+ dispose() {
302
+ if (this.#disposePromise)
303
+ return this.#disposePromise;
304
+ this.#disposed = true;
305
+ this.#disposePromise = Promise.resolve();
306
+ const error = new ClientDisposedError();
307
+ this.#rejectPendingRequests(error);
308
+ this.#connection.disconnect(error);
309
+ this.#hello = undefined;
310
+ this.#setAttachment(undefined);
311
+ this.#connectionStateListeners.clear();
312
+ this.#attachmentListeners.clear();
313
+ this.#serviceListeners.clear();
314
+ return this.#disposePromise;
315
+ }
316
+ [Symbol.asyncDispose]() {
317
+ return this.dispose();
318
+ }
319
+ #setAttachment(attachment) {
320
+ const previous = this.#attachment;
321
+ if (previous?.serverId === attachment?.serverId &&
322
+ previous?.sessionId === attachment?.sessionId &&
323
+ previous?.attachmentId === attachment?.attachmentId) {
324
+ return;
325
+ }
326
+ this.#attachment = attachment;
327
+ for (const listener of this.#attachmentListeners) {
328
+ try {
329
+ listener(attachment);
330
+ }
331
+ catch (error) {
332
+ this.#reportListenerError(error);
333
+ }
334
+ }
335
+ }
336
+ #deliverServiceUpdate(active, update) {
337
+ active.deliveryTail = active.deliveryTail
338
+ .then(() => active.listener(update))
339
+ .catch((error) => this.#reportListenerError(error));
340
+ }
341
+ #targetIsCurrent(target) {
342
+ if (!("sessionId" in target))
343
+ return this.#hello?.serverId === target.serverId;
344
+ const attachment = this.#attachment;
345
+ return (attachment?.serverId === target.serverId &&
346
+ attachment.sessionId === target.sessionId &&
347
+ attachment.attachmentId === target.attachmentId);
348
+ }
349
+ #assertNotDisposed() {
350
+ if (this.#disposed)
351
+ throw new ClientDisposedError();
352
+ }
353
+ #reportListenerError(error) {
354
+ if (!this.#options.onListenerError)
355
+ return;
356
+ try {
357
+ this.#options.onListenerError(toError(error));
358
+ }
359
+ catch {
360
+ // Diagnostics cannot affect protocol or transport state.
361
+ }
362
+ }
363
+ }
364
+ /** Adapts a lazily resolved routed client target to a Chord service transport. */
365
+ export function createClientServiceTransport(client, getTarget) {
366
+ const target = () => {
367
+ const resolved = getTarget();
368
+ if (resolved === undefined)
369
+ throw new Error("Remote service target is unavailable");
370
+ return resolved;
371
+ };
372
+ return {
373
+ invoke: async (call, context) => client.request(target(), call, context.abortSignal),
374
+ async subscribe(serviceId, mode, listener, context) {
375
+ const subscription = await client.subscribeService(target(), serviceId, mode, (update) => listener(update, BACKGROUND_CONTEXT), context.abortSignal);
376
+ return {
377
+ snapshot: subscription.snapshot,
378
+ activate: () => subscription.start(),
379
+ close: () => subscription.dispose(),
380
+ };
381
+ },
382
+ };
383
+ }
384
+ function abortError(signal) {
385
+ const reason = signal.reason;
386
+ return reason instanceof Error ? reason : new DOMException("The operation was aborted", "AbortError");
387
+ }
388
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcreateServiceCatalogueCall,\n\tcreateServiceStateDecoder,\n\tcreateServiceSubscribeCall,\n\tcreateServiceUnsubscribeCall,\n\ttype JsonValue,\n\tparseServiceCall,\n\tparseServiceCatalogue,\n\tparseWireServiceProviderUpdate,\n\tparseWireServiceSubscriptionSnapshot,\n\ttype RemoteServiceTransport,\n\ttype ServiceCall,\n\ttype ServiceCatalogueEntry,\n\ttype ServiceMode,\n\ttype ServiceProviderUpdate,\n\ttype ServiceStateDecoder,\n\ttype ServiceSubscriptionSnapshot,\n} from \"@ny4n/chord\";\nimport { BACKGROUND_CONTEXT } from \"@ny4n/chord/context\";\nimport {\n\ttype AttachmentEnvelope,\n\tencodeClientMessage,\n\tisServerId,\n\tProtocolValidationError,\n\ttype ResponseEnvelope,\n\ttype RpcTarget,\n\ttype ServerHello,\n\ttype ServiceEventEnvelope,\n\ttype SessionTarget,\n} from \"@ny4n/protocol\";\nimport { Connection } from \"./connection.ts\";\nimport { ClientDisposedError, DisconnectedError, ServerError, toError } from \"./errors.ts\";\nimport { createPromiseResolvers } from \"./promise.ts\";\nimport type {\n\tAttachmentChangeListener,\n\tClientOptions,\n\tConnectionState,\n\tConnectionStateChange,\n\tServiceSubscription,\n\tUnsubscribe,\n} from \"./types.ts\";\n\ntype ServiceResult = JsonValue | undefined;\n\ninterface PendingRequest {\n\tresolve(result: ServiceResult): void;\n\treject(error: Error): void;\n\tcleanup(): void;\n}\n\ninterface ActiveServiceListener {\n\treadonly target: RpcTarget;\n\treadonly listener: (update: ServiceProviderUpdate) => void | Promise<void>;\n\treadonly decoder: ServiceStateDecoder;\n\treadonly queuedWireUpdates: JsonValue[];\n\treadonly queued: ServiceProviderUpdate[];\n\tdeliveryTail: Promise<void>;\n\thydrated: boolean;\n\tready: boolean;\n}\n\nexport class Client {\n\treadonly #options: ClientOptions;\n\treadonly #connection: Connection;\n\treadonly #pendingRequests = new Map<string, PendingRequest>();\n\treadonly #connectionStateListeners = new Set<(change: ConnectionStateChange) => void>();\n\treadonly #attachmentListeners = new Set<AttachmentChangeListener>();\n\treadonly #serviceListeners = new Map<string, ActiveServiceListener>();\n\t#requestSequence = 0;\n\t#serviceSubscriptionSequence = 0;\n\t#hello: ServerHello | undefined;\n\t#attachment: SessionTarget | undefined;\n\t#disposed = false;\n\t#disposePromise: Promise<void> | undefined;\n\n\tconstructor(options: ClientOptions) {\n\t\tif (!isServerId(options.serverId)) {\n\t\t\tthrow new TypeError(\"serverId must be a canonical lowercase UUIDv4\");\n\t\t}\n\t\tthis.#options = options;\n\t\tthis.#connection = new Connection({\n\t\t\ttransportFactory: options.transportFactory,\n\t\t\tserverId: options.serverId,\n\t\t\tmaxFrameLength: options.maxFrameLength,\n\t\t\tonHandshake: (hello) => {\n\t\t\t\tthis.#hello = hello;\n\t\t\t},\n\t\t\tonMessage: (message) => this.#handleMessage(message),\n\t\t\tonStateChange: (change) => this.#handleConnectionStateChange(change),\n\t\t});\n\t}\n\n\tget disposed(): boolean {\n\t\treturn this.#disposed;\n\t}\n\n\tget connectionState(): ConnectionState {\n\t\treturn this.#connection.state;\n\t}\n\n\tget connected(): boolean {\n\t\treturn this.#connection.state === \"connected\";\n\t}\n\n\tget serverId(): string {\n\t\treturn this.#options.serverId;\n\t}\n\n\tget hello(): ServerHello | undefined {\n\t\treturn this.#hello;\n\t}\n\n\tget attachment(): SessionTarget | undefined {\n\t\treturn this.#attachment;\n\t}\n\n\tstatic async connect(options: ClientOptions): Promise<Client> {\n\t\tconst client = new Client(options);\n\t\ttry {\n\t\t\tawait client.connect();\n\t\t\treturn client;\n\t\t} catch (error) {\n\t\t\tawait client.dispose();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tconnect(): Promise<ServerHello> {\n\t\tif (this.#disposed) return Promise.reject(new ClientDisposedError());\n\t\tthis.#hello = undefined;\n\t\treturn this.#connection.connect();\n\t}\n\n\treconnect(): Promise<ServerHello> {\n\t\treturn this.connect();\n\t}\n\n\tdisconnect(reason = \"Client disconnected\"): void {\n\t\tthis.#connection.disconnect(reason);\n\t}\n\n\tonConnectionStateChange(listener: (change: ConnectionStateChange) => void): Unsubscribe {\n\t\tthis.#assertNotDisposed();\n\t\tthis.#connectionStateListeners.add(listener);\n\t\treturn () => this.#connectionStateListeners.delete(listener);\n\t}\n\n\tonAttachmentChange(listener: AttachmentChangeListener): Unsubscribe {\n\t\tthis.#assertNotDisposed();\n\t\tthis.#attachmentListeners.add(listener);\n\t\treturn () => this.#attachmentListeners.delete(listener);\n\t}\n\n\t/** Invoke one low-level protocol call against an explicit routed target. */\n\trequest(target: RpcTarget, call: ServiceCall, signal?: AbortSignal): Promise<ServiceResult> {\n\t\treturn this.#request(target, call, signal);\n\t}\n\n\tasync serviceCatalogue(target: RpcTarget, signal?: AbortSignal): Promise<readonly ServiceCatalogueEntry[]> {\n\t\tconst result = await this.#request(target, createServiceCatalogueCall(), signal);\n\t\ttry {\n\t\t\treturn parseServiceCatalogue(result);\n\t\t} catch (error) {\n\t\t\tconst validationError = new ProtocolValidationError(\n\t\t\t\terror instanceof Error ? error.message : \"Invalid service catalogue\",\n\t\t\t);\n\t\t\tthis.#connection.fail(validationError);\n\t\t\tthrow validationError;\n\t\t}\n\t}\n\n\tasync subscribeService(\n\t\ttarget: RpcTarget,\n\t\tserviceId: string,\n\t\tmode: ServiceMode,\n\t\tlistener: (update: ServiceProviderUpdate) => void | Promise<void>,\n\t\tsignal?: AbortSignal,\n\t): Promise<ServiceSubscription> {\n\t\tconst subscriptionId = `service-${++this.#serviceSubscriptionSequence}`;\n\t\tconst active: ActiveServiceListener = {\n\t\t\ttarget,\n\t\t\tlistener,\n\t\t\tdecoder: createServiceStateDecoder(),\n\t\t\tqueuedWireUpdates: [],\n\t\t\tqueued: [],\n\t\t\tdeliveryTail: Promise.resolve(),\n\t\t\thydrated: false,\n\t\t\tready: false,\n\t\t};\n\t\tthis.#serviceListeners.set(subscriptionId, active);\n\t\tlet snapshot: ServiceSubscriptionSnapshot;\n\t\ttry {\n\t\t\tsnapshot = await this.#request(\n\t\t\t\ttarget,\n\t\t\t\tcreateServiceSubscribeCall(subscriptionId, serviceId, mode),\n\t\t\t\tsignal,\n\t\t\t\t(result) => {\n\t\t\t\t\tconst decoded = active.decoder.decodeSnapshot(parseWireServiceSubscriptionSnapshot(result));\n\t\t\t\t\tactive.hydrated = true;\n\t\t\t\t\tfor (const update of active.queuedWireUpdates.splice(0)) {\n\t\t\t\t\t\tactive.queued.push(active.decoder.decodeUpdate(parseWireServiceProviderUpdate(update)));\n\t\t\t\t\t}\n\t\t\t\t\treturn decoded;\n\t\t\t\t},\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (this.#serviceListeners.get(subscriptionId) === active) this.#serviceListeners.delete(subscriptionId);\n\t\t\tthrow error;\n\t\t}\n\t\tif (this.#serviceListeners.get(subscriptionId) !== active) throw new DisconnectedError();\n\t\tlet disposed = false;\n\t\treturn {\n\t\t\tid: subscriptionId,\n\t\t\ttarget,\n\t\t\tsnapshot,\n\t\t\tstart: () => {\n\t\t\t\tif (disposed || active.ready) return;\n\t\t\t\tactive.ready = true;\n\t\t\t\tfor (const update of active.queued.splice(0)) this.#deliverServiceUpdate(active, update);\n\t\t\t},\n\t\t\tdispose: async () => {\n\t\t\t\tif (disposed) return;\n\t\t\t\tdisposed = true;\n\t\t\t\tif (this.#serviceListeners.get(subscriptionId) === active) this.#serviceListeners.delete(subscriptionId);\n\t\t\t\ttry {\n\t\t\t\t\tif (this.connected && this.#targetIsCurrent(target)) {\n\t\t\t\t\t\tawait this.#request(target, createServiceUnsubscribeCall(subscriptionId));\n\t\t\t\t\t}\n\t\t\t\t\tawait active.deliveryTail;\n\t\t\t\t} finally {\n\t\t\t\t\tactive.queuedWireUpdates.length = 0;\n\t\t\t\t\tactive.queued.length = 0;\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\t#request<T = ServiceResult>(\n\t\ttarget: RpcTarget,\n\t\tcall: ServiceCall,\n\t\tsignal?: AbortSignal,\n\t\ttransform?: (result: ServiceResult) => T,\n\t): Promise<T> {\n\t\tif (this.#disposed) return Promise.reject(new ClientDisposedError());\n\t\tif (!this.connected) return Promise.reject(new DisconnectedError());\n\t\tif (signal?.aborted) return Promise.reject(abortError(signal));\n\t\tconst id = `request-${++this.#requestSequence}`;\n\t\tconst { promise, resolve, reject } = createPromiseResolvers<T>();\n\t\tlet sent = false;\n\t\tlet aborted = false;\n\t\tlet onAbort: (() => void) | undefined;\n\t\tconst sendCancel = (): void => {\n\t\t\tif (!sent || !this.connected) return;\n\t\t\ttry {\n\t\t\t\tthis.#connection.send(\n\t\t\t\t\tencodeClientMessage({ type: \"cancel\", id, target }, { maxFrameLength: this.#connection.maxFrameLength }),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#connection.fail(toError(error));\n\t\t\t}\n\t\t};\n\t\tif (signal !== undefined) {\n\t\t\tonAbort = () => {\n\t\t\t\tif (aborted) return;\n\t\t\t\taborted = true;\n\t\t\t\treject(abortError(signal));\n\t\t\t\tsendCancel();\n\t\t\t};\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t\tthis.#pendingRequests.set(id, {\n\t\t\tresolve: (result) => {\n\t\t\t\ttry {\n\t\t\t\t\tresolve(transform === undefined ? (result as T) : transform(result));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst validationError = new ProtocolValidationError(\n\t\t\t\t\t\terror instanceof Error ? error.message : \"Invalid service operation stream\",\n\t\t\t\t\t);\n\t\t\t\t\tthis.#connection.fail(validationError);\n\t\t\t\t\treject(validationError);\n\t\t\t\t}\n\t\t\t},\n\t\t\treject,\n\t\t\tcleanup: () => {\n\t\t\t\tif (signal !== undefined && onAbort !== undefined) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t},\n\t\t});\n\t\tlet frame: Uint8Array;\n\t\ttry {\n\t\t\tframe = encodeClientMessage(\n\t\t\t\t{ type: \"request\", id, target, call: parseServiceCall(call) as unknown as JsonValue },\n\t\t\t\t{ maxFrameLength: this.#connection.maxFrameLength },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthis.#takePendingRequest(id)?.reject(toError(error));\n\t\t\treturn promise;\n\t\t}\n\t\tthis.#connection.send(frame);\n\t\tsent = true;\n\t\tif (aborted) sendCancel();\n\t\treturn promise;\n\t}\n\n\t#handleMessage(message: ResponseEnvelope | ServiceEventEnvelope | AttachmentEnvelope): void {\n\t\tif (message.type === \"attachment\") {\n\t\t\tif (message.attachment !== null && message.attachment.serverId !== this.#options.serverId) {\n\t\t\t\tthis.#connection.fail(new ProtocolValidationError(\"Attachment update belongs to another server\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.#setAttachment(message.attachment ?? undefined);\n\t\t\treturn;\n\t\t}\n\t\tif (message.type === \"service_update\") {\n\t\t\tconst active = this.#serviceListeners.get(message.subscriptionId);\n\t\t\tif (active === undefined) return;\n\t\t\tif (!active.hydrated) {\n\t\t\t\tactive.queuedWireUpdates.push(message.update);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet update: ServiceProviderUpdate;\n\t\t\ttry {\n\t\t\t\tupdate = active.decoder.decodeUpdate(parseWireServiceProviderUpdate(message.update));\n\t\t\t} catch (error) {\n\t\t\t\tthis.#connection.fail(\n\t\t\t\t\tnew ProtocolValidationError(error instanceof Error ? error.message : \"Invalid service operation stream\"),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (active.ready) this.#deliverServiceUpdate(active, update);\n\t\t\telse active.queued.push(update);\n\t\t\treturn;\n\t\t}\n\t\tconst pending = this.#takePendingRequest(message.id);\n\t\tif (!pending) {\n\t\t\tthis.#connection.fail(new ProtocolValidationError(\"Response has no matching request\"));\n\t\t\treturn;\n\t\t}\n\t\tif (!message.ok) {\n\t\t\tpending.reject(new ServerError(message.error));\n\t\t\treturn;\n\t\t}\n\t\tpending.resolve(message.result);\n\t}\n\n\t#handleConnectionStateChange(change: ConnectionStateChange): void {\n\t\tif (change.state === \"disconnected\") {\n\t\t\tthis.#hello = undefined;\n\t\t\tthis.#setAttachment(undefined);\n\t\t\tthis.#rejectPendingRequests(change.error ?? new DisconnectedError());\n\t\t\tthis.#serviceListeners.clear();\n\t\t}\n\t\tfor (const listener of this.#connectionStateListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(change);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#reportListenerError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\t#takePendingRequest(id: string): PendingRequest | undefined {\n\t\tconst request = this.#pendingRequests.get(id);\n\t\tif (request) {\n\t\t\tthis.#pendingRequests.delete(id);\n\t\t\trequest.cleanup();\n\t\t}\n\t\treturn request;\n\t}\n\n\t#rejectPendingRequests(error: Error): void {\n\t\tconst requests = [...this.#pendingRequests.values()];\n\t\tthis.#pendingRequests.clear();\n\t\tfor (const request of requests) {\n\t\t\trequest.cleanup();\n\t\t\trequest.reject(error);\n\t\t}\n\t}\n\n\tdispose(): Promise<void> {\n\t\tif (this.#disposePromise) return this.#disposePromise;\n\t\tthis.#disposed = true;\n\t\tthis.#disposePromise = Promise.resolve();\n\t\tconst error = new ClientDisposedError();\n\t\tthis.#rejectPendingRequests(error);\n\t\tthis.#connection.disconnect(error);\n\t\tthis.#hello = undefined;\n\t\tthis.#setAttachment(undefined);\n\t\tthis.#connectionStateListeners.clear();\n\t\tthis.#attachmentListeners.clear();\n\t\tthis.#serviceListeners.clear();\n\t\treturn this.#disposePromise;\n\t}\n\n\t[Symbol.asyncDispose](): Promise<void> {\n\t\treturn this.dispose();\n\t}\n\n\t#setAttachment(attachment: SessionTarget | undefined): void {\n\t\tconst previous = this.#attachment;\n\t\tif (\n\t\t\tprevious?.serverId === attachment?.serverId &&\n\t\t\tprevious?.sessionId === attachment?.sessionId &&\n\t\t\tprevious?.attachmentId === attachment?.attachmentId\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#attachment = attachment;\n\t\tfor (const listener of this.#attachmentListeners) {\n\t\t\ttry {\n\t\t\t\tlistener(attachment);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#reportListenerError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\t#deliverServiceUpdate(active: ActiveServiceListener, update: ServiceProviderUpdate): void {\n\t\tactive.deliveryTail = active.deliveryTail\n\t\t\t.then(() => active.listener(update))\n\t\t\t.catch((error: unknown) => this.#reportListenerError(error));\n\t}\n\n\t#targetIsCurrent(target: RpcTarget): boolean {\n\t\tif (!(\"sessionId\" in target)) return this.#hello?.serverId === target.serverId;\n\t\tconst attachment = this.#attachment;\n\t\treturn (\n\t\t\tattachment?.serverId === target.serverId &&\n\t\t\tattachment.sessionId === target.sessionId &&\n\t\t\tattachment.attachmentId === target.attachmentId\n\t\t);\n\t}\n\n\t#assertNotDisposed(): void {\n\t\tif (this.#disposed) throw new ClientDisposedError();\n\t}\n\n\t#reportListenerError(error: unknown): void {\n\t\tif (!this.#options.onListenerError) return;\n\t\ttry {\n\t\t\tthis.#options.onListenerError(toError(error));\n\t\t} catch {\n\t\t\t// Diagnostics cannot affect protocol or transport state.\n\t\t}\n\t}\n}\n\n/** Adapts a lazily resolved routed client target to a Chord service transport. */\nexport function createClientServiceTransport(\n\tclient: Client,\n\tgetTarget: () => RpcTarget | undefined,\n): RemoteServiceTransport {\n\tconst target = (): RpcTarget => {\n\t\tconst resolved = getTarget();\n\t\tif (resolved === undefined) throw new Error(\"Remote service target is unavailable\");\n\t\treturn resolved;\n\t};\n\treturn {\n\t\tinvoke: async (call, context) => client.request(target(), call, context.abortSignal),\n\t\tasync subscribe(serviceId, mode, listener, context) {\n\t\t\tconst subscription = await client.subscribeService(\n\t\t\t\ttarget(),\n\t\t\t\tserviceId,\n\t\t\t\tmode,\n\t\t\t\t(update) => listener(update, BACKGROUND_CONTEXT),\n\t\t\t\tcontext.abortSignal,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tsnapshot: subscription.snapshot,\n\t\t\t\tactivate: () => subscription.start(),\n\t\t\t\tclose: () => subscription.dispose(),\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction abortError(signal: AbortSignal): Error {\n\tconst reason: unknown = signal.reason;\n\treturn reason instanceof Error ? reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { type ServerHello, type ServerMessage } from "@ny4n/protocol";
2
+ import type { ByteTransportFactory } from "./transport.ts";
3
+ import type { ConnectionState, ConnectionStateChange } from "./types.ts";
4
+ interface ConnectionOptions {
5
+ transportFactory: ByteTransportFactory;
6
+ serverId: string;
7
+ maxFrameLength?: number;
8
+ onHandshake(hello: ServerHello): void;
9
+ onMessage(message: Exclude<ServerMessage, {
10
+ type: "hello" | "hello_error";
11
+ }>): void;
12
+ onStateChange(change: ConnectionStateChange): void;
13
+ }
14
+ export declare class Connection {
15
+ #private;
16
+ constructor(options: ConnectionOptions);
17
+ get state(): ConnectionState;
18
+ get maxFrameLength(): number;
19
+ connect(): Promise<ServerHello>;
20
+ disconnect(reason?: string | Error): void;
21
+ fail(error: Error): void;
22
+ send(frame: Uint8Array): void;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,WAAW,EAChB,KAAK,aAAa,EAElB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAiB,oBAAoB,EAAyB,MAAM,gBAAgB,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAmBzE,UAAU,iBAAiB;IAC1B,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACtC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,aAAa,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IACpF,aAAa,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACnD;AAED,qBAAa,UAAU;;IAMtB,YAAY,OAAO,EAAE,iBAAiB,EAUrC;IAED,IAAI,KAAK,IAAI,eAAe,CAE3B;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,CAwB9B;IAED,UAAU,CAAC,MAAM,GAAE,MAAM,GAAG,KAA6B,GAAG,IAAI,CAG/D;IAED,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAEvB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAgB5B;CA+HD"}