@ontahi/react 1.0.0-alpha.11 → 1.0.0-alpha.13

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.
Files changed (63) hide show
  1. package/README.md +154 -5
  2. package/dist/actions/fetch-operation-bridge-adapter.d.ts +10 -3
  3. package/dist/actions/fetch-operation-bridge-adapter.d.ts.map +1 -1
  4. package/dist/actions/fetch-operation-bridge-adapter.js +92 -29
  5. package/dist/actions/fetch-operation-bridge-adapter.js.map +1 -1
  6. package/dist/actions/operation-bridge.d.ts +2 -0
  7. package/dist/actions/operation-bridge.d.ts.map +1 -1
  8. package/dist/actions/operation-bridge.js.map +1 -1
  9. package/dist/graph/canonical-request-invalidation.d.ts +14 -0
  10. package/dist/graph/canonical-request-invalidation.d.ts.map +1 -0
  11. package/dist/graph/canonical-request-invalidation.js +42 -0
  12. package/dist/graph/canonical-request-invalidation.js.map +1 -0
  13. package/dist/graph/context.d.ts +10 -3
  14. package/dist/graph/context.d.ts.map +1 -1
  15. package/dist/graph/context.js +39 -4
  16. package/dist/graph/context.js.map +1 -1
  17. package/dist/graph/executor.d.ts +19 -1
  18. package/dist/graph/executor.d.ts.map +1 -1
  19. package/dist/graph/executor.js.map +1 -1
  20. package/dist/graph/fetch-graph-client.d.ts +20 -4
  21. package/dist/graph/fetch-graph-client.d.ts.map +1 -1
  22. package/dist/graph/fetch-graph-client.js +87 -17
  23. package/dist/graph/fetch-graph-client.js.map +1 -1
  24. package/dist/graph/fetch-graph-runtime.d.ts +9 -4
  25. package/dist/graph/fetch-graph-runtime.d.ts.map +1 -1
  26. package/dist/graph/fetch-graph-runtime.js +83 -6
  27. package/dist/graph/fetch-graph-runtime.js.map +1 -1
  28. package/dist/graph/fetch-runtime-transport.d.ts +4 -4
  29. package/dist/graph/fetch-runtime-transport.d.ts.map +1 -1
  30. package/dist/graph/fetch-runtime-transport.js +11 -14
  31. package/dist/graph/fetch-runtime-transport.js.map +1 -1
  32. package/dist/graph/graph-hooks.d.ts +3 -1
  33. package/dist/graph/graph-hooks.d.ts.map +1 -1
  34. package/dist/graph/graph-hooks.js +167 -40
  35. package/dist/graph/graph-hooks.js.map +1 -1
  36. package/dist/graph/index.d.ts +5 -0
  37. package/dist/graph/index.d.ts.map +1 -1
  38. package/dist/graph/index.js +5 -0
  39. package/dist/graph/index.js.map +1 -1
  40. package/dist/graph/model-command-hooks.d.ts +2 -0
  41. package/dist/graph/model-command-hooks.d.ts.map +1 -0
  42. package/dist/graph/model-command-hooks.js +9 -0
  43. package/dist/graph/model-command-hooks.js.map +1 -0
  44. package/dist/graph/operation-hooks.d.ts +4 -2
  45. package/dist/graph/operation-hooks.d.ts.map +1 -1
  46. package/dist/graph/operation-hooks.js +95 -11
  47. package/dist/graph/operation-hooks.js.map +1 -1
  48. package/dist/graph/operation-types.d.ts +5 -1
  49. package/dist/graph/operation-types.d.ts.map +1 -1
  50. package/dist/graph/operation-types.js.map +1 -1
  51. package/dist/graph/runtime-graph-client.d.ts +13 -0
  52. package/dist/graph/runtime-graph-client.d.ts.map +1 -0
  53. package/dist/graph/runtime-graph-client.js +35 -0
  54. package/dist/graph/runtime-graph-client.js.map +1 -0
  55. package/dist/graph/semantic-invalidation.d.ts +27 -0
  56. package/dist/graph/semantic-invalidation.d.ts.map +1 -0
  57. package/dist/graph/semantic-invalidation.js +55 -0
  58. package/dist/graph/semantic-invalidation.js.map +1 -0
  59. package/dist/graph/websocket-runtime-transport.d.ts +32 -0
  60. package/dist/graph/websocket-runtime-transport.d.ts.map +1 -0
  61. package/dist/graph/websocket-runtime-transport.js +605 -0
  62. package/dist/graph/websocket-runtime-transport.js.map +1 -0
  63. package/package.json +4 -4
@@ -0,0 +1,605 @@
1
+ 'use client';
2
+ import { parseRuntimeProtocolResponse, parseRuntimeProtocolSessionServerFrame, RUNTIME_PROTOCOL_SESSION_NAME, RUNTIME_PROTOCOL_SESSION_VERSION, } from '@ontahi/core/runtime/protocol';
3
+ const createAsyncQueue = (capacity = Number.POSITIVE_INFINITY) => {
4
+ const values = [];
5
+ const waiters = [];
6
+ let ended = false;
7
+ let failure;
8
+ const settleWaiters = () => {
9
+ while (waiters.length > 0) {
10
+ const waiter = waiters.shift();
11
+ if (failure)
12
+ waiter.reject(failure);
13
+ else
14
+ waiter.resolve({ done: true, value: undefined });
15
+ }
16
+ };
17
+ return {
18
+ next: () => {
19
+ const value = values.shift();
20
+ if (value !== undefined)
21
+ return Promise.resolve({ done: false, value });
22
+ if (failure)
23
+ return Promise.reject(failure);
24
+ if (ended)
25
+ return Promise.resolve({ done: true, value: undefined });
26
+ return new Promise((resolve, reject) => waiters.push({ resolve, reject }));
27
+ },
28
+ push: value => {
29
+ if (ended || failure)
30
+ return;
31
+ const waiter = waiters.shift();
32
+ if (waiter)
33
+ waiter.resolve({ done: false, value });
34
+ else if (values.length >= capacity) {
35
+ failure = new Error('Runtime Protocol observation queue overflowed.');
36
+ settleWaiters();
37
+ }
38
+ else
39
+ values.push(value);
40
+ },
41
+ end: () => {
42
+ if (ended || failure)
43
+ return;
44
+ ended = true;
45
+ settleWaiters();
46
+ },
47
+ fail: error => {
48
+ if (ended || failure)
49
+ return;
50
+ failure = error;
51
+ settleWaiters();
52
+ },
53
+ };
54
+ };
55
+ const SOCKET_OPEN = 1;
56
+ const DEFAULT_RUNTIME_PATH = '/runtime';
57
+ const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10000;
58
+ const DEFAULT_RECONNECT_DELAY_MS = 250;
59
+ const MAX_DURABLE_OBSERVATION_RECONNECT_ATTEMPTS = 5;
60
+ const MAX_DURABLE_OBSERVATION_RECONNECT_DELAY_MS = 5000;
61
+ const terminalTaskStatuses = new Set(['completed', 'failed', 'cancelled']);
62
+ let fallbackObservationSequence = 0;
63
+ const defaultObservationId = () => globalThis.crypto?.randomUUID?.() ??
64
+ `ontahi-observation-${Date.now()}-${(fallbackObservationSequence += 1)}`;
65
+ const resolveWebSocketUrl = (configuredUrl = DEFAULT_RUNTIME_PATH) => {
66
+ if (/^wss?:\/\//.test(configuredUrl))
67
+ return configuredUrl;
68
+ if (!globalThis.location?.href) {
69
+ throw new Error('A WebSocket Runtime Transport url is required outside a browser.');
70
+ }
71
+ const url = new URL(configuredUrl, globalThis.location.href);
72
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
73
+ return url.toString();
74
+ };
75
+ const createDefaultWebSocket = (url, protocols) => {
76
+ if (!globalThis.WebSocket) {
77
+ throw new Error('WebSocket is unavailable in this runtime.');
78
+ }
79
+ return new globalThis.WebSocket(url, protocols);
80
+ };
81
+ const abortError = (message) => new Error(message);
82
+ class RuntimeWebSocketSessionLostError extends Error {
83
+ }
84
+ const awaitWithSignal = async (promise, signal, message) => {
85
+ if (!signal)
86
+ return promise;
87
+ if (signal.aborted)
88
+ throw abortError(message);
89
+ return new Promise((resolve, reject) => {
90
+ const abort = () => {
91
+ signal.removeEventListener('abort', abort);
92
+ reject(abortError(message));
93
+ };
94
+ signal.addEventListener('abort', abort, { once: true });
95
+ void promise.then(value => {
96
+ signal.removeEventListener('abort', abort);
97
+ resolve(value);
98
+ }, error => {
99
+ signal.removeEventListener('abort', abort);
100
+ reject(error);
101
+ });
102
+ });
103
+ };
104
+ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocket = createDefaultWebSocket, observationId = defaultObservationId, handshakeTimeoutMs = DEFAULT_HANDSHAKE_TIMEOUT_MS, reconnectDelayMs = DEFAULT_RECONNECT_DELAY_MS, reportError, } = {}) => {
105
+ if (!Number.isFinite(handshakeTimeoutMs) || handshakeTimeoutMs < 0) {
106
+ throw new TypeError('Runtime Protocol WebSocket handshake timeout must be non-negative.');
107
+ }
108
+ if (!Number.isFinite(reconnectDelayMs) || reconnectDelayMs < 0) {
109
+ throw new TypeError('Runtime Protocol WebSocket reconnect delay must be non-negative.');
110
+ }
111
+ let socket;
112
+ let connection;
113
+ let capabilities;
114
+ let removeSocketListeners;
115
+ let disposed = false;
116
+ const pendingRequests = new Map();
117
+ const durableObservations = new Map();
118
+ const graphObservations = new Map();
119
+ const eventObservations = new Map();
120
+ const prepareObservation = (queue, signal) => {
121
+ const id = observationId();
122
+ const abort = () => queue.end();
123
+ signal?.addEventListener('abort', abort, { once: true });
124
+ return {
125
+ id,
126
+ release: () => signal?.removeEventListener('abort', abort),
127
+ };
128
+ };
129
+ const observationIdIsActive = (id) => durableObservations.has(id) || graphObservations.has(id) || eventObservations.has(id);
130
+ const send = (frame) => {
131
+ if (disposed)
132
+ throw new Error('Runtime Protocol WebSocket transport is closed.');
133
+ if (!socket || socket.readyState !== SOCKET_OPEN) {
134
+ throw new Error('Runtime Protocol WebSocket session is not open.');
135
+ }
136
+ socket.send(JSON.stringify(frame));
137
+ };
138
+ const failActiveWork = (error) => {
139
+ for (const pending of pendingRequests.values()) {
140
+ pending.removeAbortListener();
141
+ pending.reject(error);
142
+ }
143
+ pendingRequests.clear();
144
+ for (const observation of durableObservations.values())
145
+ observation.queue.fail(error);
146
+ durableObservations.clear();
147
+ for (const observation of graphObservations.values())
148
+ observation.queue.fail(error);
149
+ graphObservations.clear();
150
+ for (const observation of eventObservations.values())
151
+ observation.queue.fail(error);
152
+ eventObservations.clear();
153
+ };
154
+ const failConnection = (currentSocket, error) => {
155
+ if (socket !== currentSocket)
156
+ return;
157
+ removeSocketListeners?.();
158
+ removeSocketListeners = undefined;
159
+ connection?.reject(error);
160
+ if (connection)
161
+ clearTimeout(connection.timeout);
162
+ connection = undefined;
163
+ capabilities = undefined;
164
+ socket = undefined;
165
+ failActiveWork(error);
166
+ };
167
+ const handleFrame = (currentSocket, input) => {
168
+ const parsed = parseRuntimeProtocolSessionServerFrame(input);
169
+ if (!parsed.success) {
170
+ const error = new Error(parsed.error.error.message);
171
+ reportError?.(error);
172
+ failConnection(currentSocket, error);
173
+ currentSocket.close(1002, 'Invalid Runtime Protocol session frame');
174
+ return;
175
+ }
176
+ const frame = parsed.frame;
177
+ if (frame.kind === 'ready') {
178
+ if (!connection || capabilities) {
179
+ const error = new Error('Runtime Protocol WebSocket session sent an unexpected ready frame.');
180
+ reportError?.(error);
181
+ failConnection(currentSocket, error);
182
+ currentSocket.close(1002, 'Unexpected Runtime Protocol ready frame');
183
+ return;
184
+ }
185
+ capabilities = new Set(frame.capabilities);
186
+ clearTimeout(connection.timeout);
187
+ connection.resolve(capabilities);
188
+ connection = undefined;
189
+ return;
190
+ }
191
+ if (frame.kind === 'response') {
192
+ const id = frame.response.id;
193
+ if (!id)
194
+ return;
195
+ const pending = pendingRequests.get(id);
196
+ if (!pending)
197
+ return;
198
+ pendingRequests.delete(id);
199
+ pending.removeAbortListener();
200
+ const response = parseRuntimeProtocolResponse(frame.response, pending.request);
201
+ if (!response.success)
202
+ pending.reject(new Error(response.error.error.message));
203
+ else
204
+ pending.resolve(response.response);
205
+ return;
206
+ }
207
+ if (frame.kind === 'session-error') {
208
+ const error = new Error(frame.error.message);
209
+ if (frame.id) {
210
+ const pending = pendingRequests.get(frame.id);
211
+ if (pending) {
212
+ pendingRequests.delete(frame.id);
213
+ pending.removeAbortListener();
214
+ pending.reject(error);
215
+ return;
216
+ }
217
+ const durableObservation = durableObservations.get(frame.id);
218
+ if (durableObservation) {
219
+ durableObservations.delete(frame.id);
220
+ durableObservation.queue.fail(error);
221
+ return;
222
+ }
223
+ const graphObservation = graphObservations.get(frame.id);
224
+ if (graphObservation) {
225
+ graphObservations.delete(frame.id);
226
+ graphObservation.queue.fail(error);
227
+ return;
228
+ }
229
+ const eventObservation = eventObservations.get(frame.id);
230
+ if (eventObservation) {
231
+ eventObservations.delete(frame.id);
232
+ eventObservation.queue.fail(error);
233
+ return;
234
+ }
235
+ }
236
+ reportError?.(error);
237
+ return;
238
+ }
239
+ if (frame.kind === 'durable-observation') {
240
+ const observation = durableObservations.get(frame.id);
241
+ if (!observation || observation.terminal || frame.sequence <= observation.lastSequence)
242
+ return;
243
+ observation.lastSequence = frame.sequence;
244
+ if (frame.body.kind === 'protocol-error') {
245
+ durableObservations.delete(frame.id);
246
+ observation.queue.fail(new Error(frame.body.error.message));
247
+ return;
248
+ }
249
+ const snapshot = frame.body.snapshot;
250
+ if (snapshot.taskId !== observation.run.taskId || snapshot.runId !== observation.run.runId) {
251
+ durableObservations.delete(frame.id);
252
+ observation.queue.fail(new Error('Durable Operation snapshot identity does not match the observed run.'));
253
+ try {
254
+ send({
255
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
256
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
257
+ kind: 'durable-unobserve',
258
+ id: frame.id,
259
+ });
260
+ }
261
+ catch {
262
+ // The failed observation is already detached locally.
263
+ }
264
+ return;
265
+ }
266
+ observation.queue.push(snapshot);
267
+ if (terminalTaskStatuses.has(snapshot.status)) {
268
+ observation.terminal = true;
269
+ observation.queue.end();
270
+ }
271
+ return;
272
+ }
273
+ if (frame.kind === 'event-delivery') {
274
+ const observation = eventObservations.get(frame.id);
275
+ if (!observation || frame.sequence <= observation.lastSequence)
276
+ return;
277
+ observation.lastSequence = frame.sequence;
278
+ observation.queue.push(frame.body);
279
+ return;
280
+ }
281
+ const observation = graphObservations.get(frame.id);
282
+ if (!observation || observation.terminal || frame.sequence <= observation.lastSequence)
283
+ return;
284
+ observation.lastSequence = frame.sequence;
285
+ if (frame.kind === 'graph-observation-complete') {
286
+ observation.terminal = true;
287
+ graphObservations.delete(frame.id);
288
+ observation.queue.end();
289
+ return;
290
+ }
291
+ observation.queue.push(frame.body);
292
+ if (frame.body.kind === 'protocol-error') {
293
+ observation.terminal = true;
294
+ graphObservations.delete(frame.id);
295
+ observation.queue.end();
296
+ }
297
+ };
298
+ const ensureConnection = () => {
299
+ if (disposed)
300
+ return Promise.reject(new Error('Runtime Protocol WebSocket transport is closed.'));
301
+ if (capabilities && socket?.readyState === SOCKET_OPEN)
302
+ return Promise.resolve(capabilities);
303
+ if (connection)
304
+ return connection.promise;
305
+ if (socket) {
306
+ failConnection(socket, new RuntimeWebSocketSessionLostError('Runtime Protocol WebSocket session was replaced; active work was not resumed.'));
307
+ }
308
+ const currentSocket = createWebSocket(resolveWebSocketUrl(url), protocols);
309
+ socket = currentSocket;
310
+ let resolveConnection;
311
+ let rejectConnection;
312
+ const promise = new Promise((resolve, reject) => {
313
+ resolveConnection = resolve;
314
+ rejectConnection = reject;
315
+ });
316
+ const timeout = setTimeout(() => {
317
+ const error = new RuntimeWebSocketSessionLostError('Runtime Protocol WebSocket session handshake timed out.');
318
+ failConnection(currentSocket, error);
319
+ currentSocket.close(1002, 'Runtime Protocol handshake timeout');
320
+ }, handshakeTimeoutMs);
321
+ connection = {
322
+ promise,
323
+ resolve: resolveConnection,
324
+ reject: rejectConnection,
325
+ timeout,
326
+ };
327
+ const onMessage = (event) => {
328
+ let input;
329
+ try {
330
+ input = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
331
+ }
332
+ catch {
333
+ input = undefined;
334
+ }
335
+ handleFrame(currentSocket, input);
336
+ };
337
+ const onError = () => failConnection(currentSocket, new RuntimeWebSocketSessionLostError('Runtime Protocol WebSocket session failed.'));
338
+ const onClose = (event) => failConnection(currentSocket, new RuntimeWebSocketSessionLostError(`Runtime Protocol WebSocket session disconnected${event.code === undefined
339
+ ? ''
340
+ : ` (${event.code}${event.reason ? `: ${event.reason}` : ''})`}; active work was not resumed.`));
341
+ currentSocket.addEventListener('message', onMessage);
342
+ currentSocket.addEventListener('error', onError);
343
+ currentSocket.addEventListener('close', onClose);
344
+ removeSocketListeners = () => {
345
+ currentSocket.removeEventListener('message', onMessage);
346
+ currentSocket.removeEventListener('error', onError);
347
+ currentSocket.removeEventListener('close', onClose);
348
+ };
349
+ return promise;
350
+ };
351
+ const request = async (runtimeRequest, options) => {
352
+ const signal = options?.signal;
353
+ const connectedCapabilities = await awaitWithSignal(ensureConnection(), signal, 'Runtime Protocol WebSocket request was aborted.');
354
+ if (!connectedCapabilities.has('request-response')) {
355
+ throw new Error('Runtime Protocol request/response is unavailable in this WebSocket session.');
356
+ }
357
+ if (pendingRequests.has(runtimeRequest.id)) {
358
+ throw new Error(`Runtime Protocol request id ${runtimeRequest.id} is already pending.`);
359
+ }
360
+ return new Promise((resolve, reject) => {
361
+ const abort = () => {
362
+ const pending = pendingRequests.get(runtimeRequest.id);
363
+ if (!pending)
364
+ return;
365
+ pendingRequests.delete(runtimeRequest.id);
366
+ pending.removeAbortListener();
367
+ reject(abortError('Runtime Protocol WebSocket request was aborted.'));
368
+ };
369
+ const removeAbortListener = () => signal?.removeEventListener('abort', abort);
370
+ pendingRequests.set(runtimeRequest.id, {
371
+ request: runtimeRequest,
372
+ resolve,
373
+ reject,
374
+ removeAbortListener,
375
+ });
376
+ signal?.addEventListener('abort', abort, { once: true });
377
+ try {
378
+ send({
379
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
380
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
381
+ kind: 'request',
382
+ request: runtimeRequest,
383
+ });
384
+ }
385
+ catch (error) {
386
+ pendingRequests.delete(runtimeRequest.id);
387
+ removeAbortListener();
388
+ reject(error instanceof Error ? error : new Error('Runtime Protocol WebSocket send failed.'));
389
+ }
390
+ });
391
+ };
392
+ const observeDurable = async function* (run, options) {
393
+ if (options?.signal?.aborted)
394
+ return;
395
+ let failedAttempts = 0;
396
+ while (!options?.signal?.aborted) {
397
+ const queue = createAsyncQueue();
398
+ const prepared = prepareObservation(queue, options?.signal);
399
+ const { id } = prepared;
400
+ const observation = {
401
+ run,
402
+ queue,
403
+ lastSequence: 0,
404
+ terminal: false,
405
+ };
406
+ let subscribed = false;
407
+ try {
408
+ const connectedCapabilities = await awaitWithSignal(ensureConnection(), options?.signal, 'Durable Operation WebSocket observation was aborted.');
409
+ if (!connectedCapabilities.has('durable-operation-push')) {
410
+ throw new Error('Durable Operation push is unavailable in this WebSocket session.');
411
+ }
412
+ if (observationIdIsActive(id)) {
413
+ throw new Error(`Durable observation id ${id} is already active.`);
414
+ }
415
+ durableObservations.set(id, observation);
416
+ send({
417
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
418
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
419
+ kind: 'durable-observe',
420
+ id,
421
+ run: { taskId: run.taskId, runId: run.runId },
422
+ });
423
+ subscribed = true;
424
+ while (true) {
425
+ const next = await queue.next();
426
+ if (next.done)
427
+ return;
428
+ failedAttempts = 0;
429
+ yield next.value;
430
+ }
431
+ }
432
+ catch (error) {
433
+ if (options?.signal?.aborted)
434
+ return;
435
+ if (disposed || !(error instanceof RuntimeWebSocketSessionLostError))
436
+ throw error;
437
+ failedAttempts += 1;
438
+ if (failedAttempts > MAX_DURABLE_OBSERVATION_RECONNECT_ATTEMPTS)
439
+ throw error;
440
+ const delay = Math.min(reconnectDelayMs * 2 ** (failedAttempts - 1), MAX_DURABLE_OBSERVATION_RECONNECT_DELAY_MS);
441
+ try {
442
+ await awaitWithSignal(new Promise(resolve => setTimeout(resolve, delay)), options?.signal, 'Durable Operation WebSocket observation was aborted.');
443
+ }
444
+ catch (delayError) {
445
+ if (options?.signal?.aborted)
446
+ return;
447
+ throw delayError;
448
+ }
449
+ }
450
+ finally {
451
+ prepared.release();
452
+ if (durableObservations.get(id) === observation)
453
+ durableObservations.delete(id);
454
+ if (subscribed && !observation.terminal) {
455
+ try {
456
+ send({
457
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
458
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
459
+ kind: 'durable-unobserve',
460
+ id,
461
+ });
462
+ }
463
+ catch {
464
+ // Disconnect already released the server-side session resources.
465
+ }
466
+ }
467
+ }
468
+ }
469
+ };
470
+ const observeGraph = async function* (request, options) {
471
+ if (options?.signal?.aborted)
472
+ return;
473
+ const queue = createAsyncQueue();
474
+ const prepared = prepareObservation(queue, options?.signal);
475
+ const { id } = prepared;
476
+ const observation = {
477
+ queue,
478
+ lastSequence: 0,
479
+ terminal: false,
480
+ };
481
+ let subscribed = false;
482
+ try {
483
+ const connectedCapabilities = await awaitWithSignal(ensureConnection(), options?.signal, 'Data graph WebSocket observation was aborted.');
484
+ if (!connectedCapabilities.has('graph-observation-push')) {
485
+ throw new Error('Data graph push observation is unavailable in this WebSocket session.');
486
+ }
487
+ if (observationIdIsActive(id)) {
488
+ throw new Error(`Graph observation id ${id} is already active.`);
489
+ }
490
+ graphObservations.set(id, observation);
491
+ send({
492
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
493
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
494
+ kind: 'graph-observe',
495
+ id,
496
+ request,
497
+ });
498
+ subscribed = true;
499
+ while (true) {
500
+ const next = await queue.next();
501
+ if (next.done)
502
+ return;
503
+ yield next.value;
504
+ }
505
+ }
506
+ catch (error) {
507
+ if (!options?.signal?.aborted)
508
+ throw error;
509
+ }
510
+ finally {
511
+ prepared.release();
512
+ if (graphObservations.get(id) === observation)
513
+ graphObservations.delete(id);
514
+ if (subscribed && !observation.terminal) {
515
+ try {
516
+ send({
517
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
518
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
519
+ kind: 'graph-unobserve',
520
+ id,
521
+ });
522
+ }
523
+ catch {
524
+ // Disconnect already released the server-side session resources.
525
+ }
526
+ }
527
+ }
528
+ };
529
+ const observeEvent = async function* (event, options) {
530
+ if (options?.signal?.aborted)
531
+ return;
532
+ const queue = createAsyncQueue(256);
533
+ const prepared = prepareObservation(queue, options?.signal);
534
+ const { id } = prepared;
535
+ const observation = { queue, lastSequence: 0 };
536
+ let subscribed = false;
537
+ try {
538
+ const connectedCapabilities = await awaitWithSignal(ensureConnection(), options?.signal, 'Event WebSocket observation was aborted.');
539
+ if (!connectedCapabilities.has('event-subscription-push')) {
540
+ throw new Error('Event push subscription is unavailable in this WebSocket session.');
541
+ }
542
+ if (observationIdIsActive(id)) {
543
+ throw new Error(`Event observation id ${id} is already active.`);
544
+ }
545
+ eventObservations.set(id, observation);
546
+ send({
547
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
548
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
549
+ kind: 'event-observe',
550
+ id,
551
+ eventId: event.id,
552
+ eventVersion: event.version,
553
+ });
554
+ subscribed = true;
555
+ while (true) {
556
+ const next = await queue.next();
557
+ if (next.done)
558
+ return;
559
+ yield next.value;
560
+ }
561
+ }
562
+ catch (error) {
563
+ if (!options?.signal?.aborted)
564
+ throw error;
565
+ }
566
+ finally {
567
+ prepared.release();
568
+ if (eventObservations.get(id) === observation)
569
+ eventObservations.delete(id);
570
+ if (subscribed) {
571
+ try {
572
+ send({
573
+ protocol: RUNTIME_PROTOCOL_SESSION_NAME,
574
+ version: RUNTIME_PROTOCOL_SESSION_VERSION,
575
+ kind: 'event-unobserve',
576
+ id,
577
+ });
578
+ }
579
+ catch {
580
+ // Disconnect already released the server-side subscription.
581
+ }
582
+ }
583
+ }
584
+ };
585
+ return {
586
+ descriptor: { kind: 'websocket' },
587
+ request,
588
+ durableOperation: { observe: observeDurable },
589
+ graph: { observe: observeGraph },
590
+ events: { observe: observeEvent },
591
+ close: () => {
592
+ if (disposed)
593
+ return;
594
+ disposed = true;
595
+ const currentSocket = socket;
596
+ const error = new Error('Runtime Protocol WebSocket transport was closed.');
597
+ if (currentSocket)
598
+ failConnection(currentSocket, error);
599
+ else
600
+ failActiveWork(error);
601
+ currentSocket?.close(1000, 'Runtime Protocol transport closed');
602
+ },
603
+ };
604
+ };
605
+ //# sourceMappingURL=websocket-runtime-transport.js.map