@ontahi/react 1.0.0-alpha.12 → 1.0.0-alpha.14

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 (58) hide show
  1. package/README.md +90 -5
  2. package/dist/actions/fetch-operation-bridge-adapter.d.ts.map +1 -1
  3. package/dist/actions/fetch-operation-bridge-adapter.js +30 -13
  4. package/dist/actions/fetch-operation-bridge-adapter.js.map +1 -1
  5. package/dist/actions/operation-bridge.d.ts +2 -0
  6. package/dist/actions/operation-bridge.d.ts.map +1 -1
  7. package/dist/actions/operation-bridge.js.map +1 -1
  8. package/dist/graph/canonical-request-invalidation.d.ts +14 -0
  9. package/dist/graph/canonical-request-invalidation.d.ts.map +1 -0
  10. package/dist/graph/canonical-request-invalidation.js +42 -0
  11. package/dist/graph/canonical-request-invalidation.js.map +1 -0
  12. package/dist/graph/context.d.ts +9 -2
  13. package/dist/graph/context.d.ts.map +1 -1
  14. package/dist/graph/context.js +38 -3
  15. package/dist/graph/context.js.map +1 -1
  16. package/dist/graph/executor.d.ts +18 -1
  17. package/dist/graph/executor.d.ts.map +1 -1
  18. package/dist/graph/executor.js.map +1 -1
  19. package/dist/graph/fetch-graph-client.d.ts +2 -1
  20. package/dist/graph/fetch-graph-client.d.ts.map +1 -1
  21. package/dist/graph/fetch-graph-client.js.map +1 -1
  22. package/dist/graph/fetch-graph-runtime.d.ts.map +1 -1
  23. package/dist/graph/fetch-graph-runtime.js +53 -1
  24. package/dist/graph/fetch-graph-runtime.js.map +1 -1
  25. package/dist/graph/fetch-runtime-transport.d.ts.map +1 -1
  26. package/dist/graph/fetch-runtime-transport.js +1 -0
  27. package/dist/graph/fetch-runtime-transport.js.map +1 -1
  28. package/dist/graph/graph-hooks.d.ts +2 -1
  29. package/dist/graph/graph-hooks.d.ts.map +1 -1
  30. package/dist/graph/graph-hooks.js +161 -62
  31. package/dist/graph/graph-hooks.js.map +1 -1
  32. package/dist/graph/index.d.ts +3 -0
  33. package/dist/graph/index.d.ts.map +1 -1
  34. package/dist/graph/index.js +3 -0
  35. package/dist/graph/index.js.map +1 -1
  36. package/dist/graph/model-command-hooks.d.ts +2 -0
  37. package/dist/graph/model-command-hooks.d.ts.map +1 -0
  38. package/dist/graph/model-command-hooks.js +9 -0
  39. package/dist/graph/model-command-hooks.js.map +1 -0
  40. package/dist/graph/operation-hooks.d.ts.map +1 -1
  41. package/dist/graph/operation-hooks.js +91 -9
  42. package/dist/graph/operation-hooks.js.map +1 -1
  43. package/dist/graph/operation-types.d.ts +1 -0
  44. package/dist/graph/operation-types.d.ts.map +1 -1
  45. package/dist/graph/operation-types.js.map +1 -1
  46. package/dist/graph/runtime-graph-client.d.ts +3 -2
  47. package/dist/graph/runtime-graph-client.d.ts.map +1 -1
  48. package/dist/graph/runtime-graph-client.js +2 -1
  49. package/dist/graph/runtime-graph-client.js.map +1 -1
  50. package/dist/graph/semantic-invalidation.d.ts +27 -0
  51. package/dist/graph/semantic-invalidation.d.ts.map +1 -0
  52. package/dist/graph/semantic-invalidation.js +55 -0
  53. package/dist/graph/semantic-invalidation.js.map +1 -0
  54. package/dist/graph/websocket-runtime-transport.d.ts +5 -2
  55. package/dist/graph/websocket-runtime-transport.d.ts.map +1 -1
  56. package/dist/graph/websocket-runtime-transport.js +160 -46
  57. package/dist/graph/websocket-runtime-transport.js.map +1 -1
  58. package/package.json +4 -4
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+ import { mayAffectGraphRead, } from '@ontahi/core/data-graph';
3
+ export const ONTAHI_GRAPH_READ_META_KEY = 'ontahi.graphRead';
4
+ export const withCanonicalGraphReadMeta = (meta, request, lifecycle = 'once') => ({
5
+ ...meta,
6
+ [ONTAHI_GRAPH_READ_META_KEY]: { kind: 'canonical', request, lifecycle },
7
+ });
8
+ export const withUnknownGraphReadMeta = (meta, lifecycle = 'once') => ({
9
+ ...meta,
10
+ [ONTAHI_GRAPH_READ_META_KEY]: { kind: 'unknown', lifecycle },
11
+ });
12
+ export const getCanonicalGraphReadFromMeta = (meta) => {
13
+ const dependency = meta?.[ONTAHI_GRAPH_READ_META_KEY];
14
+ return dependency &&
15
+ typeof dependency === 'object' &&
16
+ 'kind' in dependency &&
17
+ dependency.kind === 'canonical' &&
18
+ 'request' in dependency
19
+ ? dependency.request
20
+ : undefined;
21
+ };
22
+ export const invalidateSemanticGraphReads = async (queryClient, committed, options) => {
23
+ const affected = queryClient
24
+ .getQueryCache()
25
+ .findAll()
26
+ .flatMap(query => {
27
+ const dependency = query.meta?.[ONTAHI_GRAPH_READ_META_KEY];
28
+ if (dependency &&
29
+ typeof dependency === 'object' &&
30
+ 'lifecycle' in dependency &&
31
+ dependency.lifecycle === 'live' &&
32
+ query.state.fetchStatus === 'fetching') {
33
+ // Its observation already receives committed revisions from the runtime. Restarting the
34
+ // query here would create a second subscription and a redundant initial snapshot.
35
+ return [];
36
+ }
37
+ if (dependency &&
38
+ typeof dependency === 'object' &&
39
+ 'kind' in dependency &&
40
+ dependency.kind === 'unknown') {
41
+ return committed.mutations.length > 0 ? [{ queryKey: query.queryKey }] : [];
42
+ }
43
+ const read = getCanonicalGraphReadFromMeta(query.meta);
44
+ return read && committed.mutations.some(mutation => mayAffectGraphRead(mutation, read))
45
+ ? [{ queryKey: query.queryKey, request: read }]
46
+ : [];
47
+ });
48
+ const queryKeys = affected.map(item => item.queryKey);
49
+ if (options?.causedBy && options.associateRefreshes) {
50
+ options.associateRefreshes(affected.flatMap(item => (item.request ? [item.request] : [])), options.causedBy);
51
+ }
52
+ await Promise.all(queryKeys.map(queryKey => queryClient.invalidateQueries({ queryKey, exact: true })));
53
+ return { queryKeys };
54
+ };
55
+ //# sourceMappingURL=semantic-invalidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-invalidation.js","sourceRoot":"","sources":["../../src/graph/semantic-invalidation.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n mayAffectGraphRead,\n type CommittedMutationSet,\n type GraphReadRequest,\n} from '@ontahi/core/data-graph';\nimport type { RuntimeProtocolGraphRefreshCause } from '@ontahi/core/runtime/protocol';\nimport type { QueryClient, QueryKey, QueryMeta } from '@tanstack/react-query';\n\nexport const ONTAHI_GRAPH_READ_META_KEY = 'ontahi.graphRead' as const;\n\ntype OntahiGraphReadMeta = QueryMeta & {\n readonly [ONTAHI_GRAPH_READ_META_KEY]:\n | {\n readonly kind: 'canonical';\n readonly request: GraphReadRequest;\n readonly lifecycle: 'once' | 'live';\n }\n | { readonly kind: 'unknown'; readonly lifecycle: 'once' | 'live' };\n};\n\nexport type SemanticGraphReadInvalidation = {\n readonly queryKeys: readonly QueryKey[];\n};\n\nexport type SemanticGraphReadInvalidationOptions = {\n readonly causedBy?: RuntimeProtocolGraphRefreshCause;\n readonly associateRefreshes?: (\n requests: readonly GraphReadRequest[],\n causedBy: RuntimeProtocolGraphRefreshCause,\n ) => void;\n};\n\nexport const withCanonicalGraphReadMeta = (\n meta: QueryMeta | undefined,\n request: GraphReadRequest,\n lifecycle: 'once' | 'live' = 'once',\n): OntahiGraphReadMeta => ({\n ...meta,\n [ONTAHI_GRAPH_READ_META_KEY]: { kind: 'canonical', request, lifecycle },\n});\n\nexport const withUnknownGraphReadMeta = (\n meta: QueryMeta | undefined,\n lifecycle: 'once' | 'live' = 'once',\n): OntahiGraphReadMeta => ({\n ...meta,\n [ONTAHI_GRAPH_READ_META_KEY]: { kind: 'unknown', lifecycle },\n});\n\nexport const getCanonicalGraphReadFromMeta = (\n meta: QueryMeta | undefined,\n): GraphReadRequest | undefined => {\n const dependency = meta?.[ONTAHI_GRAPH_READ_META_KEY];\n return dependency &&\n typeof dependency === 'object' &&\n 'kind' in dependency &&\n dependency.kind === 'canonical' &&\n 'request' in dependency\n ? (dependency.request as GraphReadRequest)\n : undefined;\n};\n\nexport const invalidateSemanticGraphReads = async (\n queryClient: QueryClient,\n committed: CommittedMutationSet,\n options?: SemanticGraphReadInvalidationOptions,\n): Promise<SemanticGraphReadInvalidation> => {\n const affected: Array<{ readonly queryKey: QueryKey; readonly request?: GraphReadRequest }> =\n queryClient\n .getQueryCache()\n .findAll()\n .flatMap(query => {\n const dependency = query.meta?.[ONTAHI_GRAPH_READ_META_KEY];\n if (\n dependency &&\n typeof dependency === 'object' &&\n 'lifecycle' in dependency &&\n dependency.lifecycle === 'live' &&\n query.state.fetchStatus === 'fetching'\n ) {\n // Its observation already receives committed revisions from the runtime. Restarting the\n // query here would create a second subscription and a redundant initial snapshot.\n return [];\n }\n if (\n dependency &&\n typeof dependency === 'object' &&\n 'kind' in dependency &&\n dependency.kind === 'unknown'\n ) {\n return committed.mutations.length > 0 ? [{ queryKey: query.queryKey }] : [];\n }\n const read = getCanonicalGraphReadFromMeta(query.meta);\n return read && committed.mutations.some(mutation => mayAffectGraphRead(mutation, read))\n ? [{ queryKey: query.queryKey, request: read }]\n : [];\n });\n const queryKeys = affected.map(item => item.queryKey);\n if (options?.causedBy && options.associateRefreshes) {\n options.associateRefreshes(\n affected.flatMap(item => (item.request ? [item.request] : [])),\n options.causedBy,\n );\n }\n\n await Promise.all(\n queryKeys.map(queryKey => queryClient.invalidateQueries({ queryKey, exact: true })),\n );\n\n return { queryKeys };\n};\n"]}
@@ -1,4 +1,4 @@
1
- import { type DurableOperationObservationCapability, type GraphObservationCapability, type RuntimeTransport } from '@ontahi/core/runtime/protocol';
1
+ import { type DurableOperationObservationCapability, type EventObservationCapability, type GraphObservationCapability, type RuntimeTransport } from '@ontahi/core/runtime/protocol';
2
2
  type WebSocketEvent = {
3
3
  readonly data?: unknown;
4
4
  readonly code?: number;
@@ -17,13 +17,16 @@ export type WebSocketRuntimeTransportOptions = {
17
17
  readonly createWebSocket?: (url: string, protocols?: string | string[]) => RuntimeWebSocket;
18
18
  readonly observationId?: () => string;
19
19
  readonly handshakeTimeoutMs?: number;
20
+ /** Delay before restoring a durable Operation observation after a lost session. */
21
+ readonly reconnectDelayMs?: number;
20
22
  readonly reportError?: (error: Error) => void;
21
23
  };
22
24
  export type WebSocketRuntimeTransport = RuntimeTransport<never> & {
23
25
  readonly durableOperation: DurableOperationObservationCapability;
24
26
  readonly graph: GraphObservationCapability<never>;
27
+ readonly events: EventObservationCapability<never>;
25
28
  close(): void;
26
29
  };
27
- export declare const createWebSocketRuntimeTransport: ({ url, protocols, createWebSocket, observationId, handshakeTimeoutMs, reportError, }?: WebSocketRuntimeTransportOptions) => WebSocketRuntimeTransport;
30
+ export declare const createWebSocketRuntimeTransport: ({ url, protocols, createWebSocket, observationId, handshakeTimeoutMs, reconnectDelayMs, reportError, }?: WebSocketRuntimeTransportOptions) => WebSocketRuntimeTransport;
28
31
  export {};
29
32
  //# sourceMappingURL=websocket-runtime-transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-runtime-transport.d.ts","sourceRoot":"","sources":["../../src/graph/websocket-runtime-transport.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,qCAAqC,EAC1C,KAAK,0BAA0B,EAK/B,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAEvC,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAC;IAChF,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAC;CACpF,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,gBAAgB,CAAC;IAC5F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IACtC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,CAAC,KAAK,CAAC,GAAG;IAChE,QAAQ,CAAC,gBAAgB,EAAE,qCAAqC,CAAC;IACjE,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAClD,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAqIF,eAAO,MAAM,+BAA+B,GAAI,uFAO7C,gCAAqC,KAAG,yBAob1C,CAAC"}
1
+ {"version":3,"file":"websocket-runtime-transport.d.ts","sourceRoot":"","sources":["../../src/graph/websocket-runtime-transport.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,qCAAqC,EAC1C,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAK/B,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAGvC,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAC;IAChF,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAC;CACpF,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,gBAAgB,CAAC;IAC5F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IACtC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,mFAAmF;IACnF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,CAAC,KAAK,CAAC,GAAG;IAChE,QAAQ,CAAC,gBAAgB,EAAE,qCAAqC,CAAC;IACjE,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC,KAAK,CAAC,CAAC;IACnD,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAkJF,eAAO,MAAM,+BAA+B,GAAI,yGAQ7C,gCAAqC,KAAG,yBA2iB1C,CAAC"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { parseRuntimeProtocolResponse, parseRuntimeProtocolSessionServerFrame, RUNTIME_PROTOCOL_SESSION_NAME, RUNTIME_PROTOCOL_SESSION_VERSION, } from '@ontahi/core/runtime/protocol';
3
- const createAsyncQueue = () => {
3
+ const createAsyncQueue = (capacity = Number.POSITIVE_INFINITY) => {
4
4
  const values = [];
5
5
  const waiters = [];
6
6
  let ended = false;
@@ -31,6 +31,10 @@ const createAsyncQueue = () => {
31
31
  const waiter = waiters.shift();
32
32
  if (waiter)
33
33
  waiter.resolve({ done: false, value });
34
+ else if (values.length >= capacity) {
35
+ failure = new Error('Runtime Protocol observation queue overflowed.');
36
+ settleWaiters();
37
+ }
34
38
  else
35
39
  values.push(value);
36
40
  },
@@ -51,6 +55,9 @@ const createAsyncQueue = () => {
51
55
  const SOCKET_OPEN = 1;
52
56
  const DEFAULT_RUNTIME_PATH = '/runtime';
53
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;
54
61
  const terminalTaskStatuses = new Set(['completed', 'failed', 'cancelled']);
55
62
  let fallbackObservationSequence = 0;
56
63
  const defaultObservationId = () => globalThis.crypto?.randomUUID?.() ??
@@ -72,6 +79,8 @@ const createDefaultWebSocket = (url, protocols) => {
72
79
  return new globalThis.WebSocket(url, protocols);
73
80
  };
74
81
  const abortError = (message) => new Error(message);
82
+ class RuntimeWebSocketSessionLostError extends Error {
83
+ }
75
84
  const awaitWithSignal = async (promise, signal, message) => {
76
85
  if (!signal)
77
86
  return promise;
@@ -92,10 +101,13 @@ const awaitWithSignal = async (promise, signal, message) => {
92
101
  });
93
102
  });
94
103
  };
95
- export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocket = createDefaultWebSocket, observationId = defaultObservationId, handshakeTimeoutMs = DEFAULT_HANDSHAKE_TIMEOUT_MS, reportError, } = {}) => {
104
+ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocket = createDefaultWebSocket, observationId = defaultObservationId, handshakeTimeoutMs = DEFAULT_HANDSHAKE_TIMEOUT_MS, reconnectDelayMs = DEFAULT_RECONNECT_DELAY_MS, reportError, } = {}) => {
96
105
  if (!Number.isFinite(handshakeTimeoutMs) || handshakeTimeoutMs < 0) {
97
106
  throw new TypeError('Runtime Protocol WebSocket handshake timeout must be non-negative.');
98
107
  }
108
+ if (!Number.isFinite(reconnectDelayMs) || reconnectDelayMs < 0) {
109
+ throw new TypeError('Runtime Protocol WebSocket reconnect delay must be non-negative.');
110
+ }
99
111
  let socket;
100
112
  let connection;
101
113
  let capabilities;
@@ -104,6 +116,17 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
104
116
  const pendingRequests = new Map();
105
117
  const durableObservations = new Map();
106
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);
107
130
  const send = (frame) => {
108
131
  if (disposed)
109
132
  throw new Error('Runtime Protocol WebSocket transport is closed.');
@@ -124,6 +147,9 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
124
147
  for (const observation of graphObservations.values())
125
148
  observation.queue.fail(error);
126
149
  graphObservations.clear();
150
+ for (const observation of eventObservations.values())
151
+ observation.queue.fail(error);
152
+ eventObservations.clear();
127
153
  };
128
154
  const failConnection = (currentSocket, error) => {
129
155
  if (socket !== currentSocket)
@@ -200,6 +226,12 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
200
226
  graphObservation.queue.fail(error);
201
227
  return;
202
228
  }
229
+ const eventObservation = eventObservations.get(frame.id);
230
+ if (eventObservation) {
231
+ eventObservations.delete(frame.id);
232
+ eventObservation.queue.fail(error);
233
+ return;
234
+ }
203
235
  }
204
236
  reportError?.(error);
205
237
  return;
@@ -238,6 +270,14 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
238
270
  }
239
271
  return;
240
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
+ }
241
281
  const observation = graphObservations.get(frame.id);
242
282
  if (!observation || observation.terminal || frame.sequence <= observation.lastSequence)
243
283
  return;
@@ -263,7 +303,7 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
263
303
  if (connection)
264
304
  return connection.promise;
265
305
  if (socket) {
266
- failConnection(socket, new Error('Runtime Protocol WebSocket session was replaced; active work was not resumed.'));
306
+ failConnection(socket, new RuntimeWebSocketSessionLostError('Runtime Protocol WebSocket session was replaced; active work was not resumed.'));
267
307
  }
268
308
  const currentSocket = createWebSocket(resolveWebSocketUrl(url), protocols);
269
309
  socket = currentSocket;
@@ -274,7 +314,7 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
274
314
  rejectConnection = reject;
275
315
  });
276
316
  const timeout = setTimeout(() => {
277
- const error = new Error('Runtime Protocol WebSocket session handshake timed out.');
317
+ const error = new RuntimeWebSocketSessionLostError('Runtime Protocol WebSocket session handshake timed out.');
278
318
  failConnection(currentSocket, error);
279
319
  currentSocket.close(1002, 'Runtime Protocol handshake timeout');
280
320
  }, handshakeTimeoutMs);
@@ -294,8 +334,8 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
294
334
  }
295
335
  handleFrame(currentSocket, input);
296
336
  };
297
- const onError = () => failConnection(currentSocket, new Error('Runtime Protocol WebSocket session failed.'));
298
- const onClose = (event) => failConnection(currentSocket, new Error(`Runtime Protocol WebSocket session disconnected${event.code === undefined
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
299
339
  ? ''
300
340
  : ` (${event.code}${event.reason ? `: ${event.reason}` : ''})`}; active work was not resumed.`));
301
341
  currentSocket.addEventListener('message', onMessage);
@@ -352,32 +392,108 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
352
392
  const observeDurable = async function* (run, options) {
353
393
  if (options?.signal?.aborted)
354
394
  return;
355
- const id = observationId();
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;
356
473
  const queue = createAsyncQueue();
474
+ const prepared = prepareObservation(queue, options?.signal);
475
+ const { id } = prepared;
357
476
  const observation = {
358
- run,
359
477
  queue,
360
478
  lastSequence: 0,
361
479
  terminal: false,
362
480
  };
363
481
  let subscribed = false;
364
- const abort = () => queue.end();
365
- options?.signal?.addEventListener('abort', abort, { once: true });
366
482
  try {
367
- const connectedCapabilities = await awaitWithSignal(ensureConnection(), options?.signal, 'Durable Operation WebSocket observation was aborted.');
368
- if (!connectedCapabilities.has('durable-operation-push')) {
369
- throw new Error('Durable Operation push is unavailable in this WebSocket session.');
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.');
370
486
  }
371
- if (durableObservations.has(id) || graphObservations.has(id)) {
372
- throw new Error(`Durable observation id ${id} is already active.`);
487
+ if (observationIdIsActive(id)) {
488
+ throw new Error(`Graph observation id ${id} is already active.`);
373
489
  }
374
- durableObservations.set(id, observation);
490
+ graphObservations.set(id, observation);
375
491
  send({
376
492
  protocol: RUNTIME_PROTOCOL_SESSION_NAME,
377
493
  version: RUNTIME_PROTOCOL_SESSION_VERSION,
378
- kind: 'durable-observe',
494
+ kind: 'graph-observe',
379
495
  id,
380
- run: { taskId: run.taskId, runId: run.runId },
496
+ request,
381
497
  });
382
498
  subscribed = true;
383
499
  while (true) {
@@ -392,15 +508,15 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
392
508
  throw error;
393
509
  }
394
510
  finally {
395
- options?.signal?.removeEventListener('abort', abort);
396
- if (durableObservations.get(id) === observation)
397
- durableObservations.delete(id);
511
+ prepared.release();
512
+ if (graphObservations.get(id) === observation)
513
+ graphObservations.delete(id);
398
514
  if (subscribed && !observation.terminal) {
399
515
  try {
400
516
  send({
401
517
  protocol: RUNTIME_PROTOCOL_SESSION_NAME,
402
518
  version: RUNTIME_PROTOCOL_SESSION_VERSION,
403
- kind: 'durable-unobserve',
519
+ kind: 'graph-unobserve',
404
520
  id,
405
521
  });
406
522
  }
@@ -410,34 +526,30 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
410
526
  }
411
527
  }
412
528
  };
413
- const observeGraph = async function* (request, options) {
529
+ const observeEvent = async function* (event, options) {
414
530
  if (options?.signal?.aborted)
415
531
  return;
416
- const id = observationId();
417
- const queue = createAsyncQueue();
418
- const observation = {
419
- queue,
420
- lastSequence: 0,
421
- terminal: false,
422
- };
532
+ const queue = createAsyncQueue(256);
533
+ const prepared = prepareObservation(queue, options?.signal);
534
+ const { id } = prepared;
535
+ const observation = { queue, lastSequence: 0 };
423
536
  let subscribed = false;
424
- const abort = () => queue.end();
425
- options?.signal?.addEventListener('abort', abort, { once: true });
426
537
  try {
427
- const connectedCapabilities = await awaitWithSignal(ensureConnection(), options?.signal, 'Data graph WebSocket observation was aborted.');
428
- if (!connectedCapabilities.has('graph-observation-push')) {
429
- throw new Error('Data graph push observation is unavailable in this WebSocket session.');
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.');
430
541
  }
431
- if (durableObservations.has(id) || graphObservations.has(id)) {
432
- throw new Error(`Graph observation id ${id} is already active.`);
542
+ if (observationIdIsActive(id)) {
543
+ throw new Error(`Event observation id ${id} is already active.`);
433
544
  }
434
- graphObservations.set(id, observation);
545
+ eventObservations.set(id, observation);
435
546
  send({
436
547
  protocol: RUNTIME_PROTOCOL_SESSION_NAME,
437
548
  version: RUNTIME_PROTOCOL_SESSION_VERSION,
438
- kind: 'graph-observe',
549
+ kind: 'event-observe',
439
550
  id,
440
- request,
551
+ eventId: event.id,
552
+ eventVersion: event.version,
441
553
  });
442
554
  subscribed = true;
443
555
  while (true) {
@@ -452,28 +564,30 @@ export const createWebSocketRuntimeTransport = ({ url, protocols, createWebSocke
452
564
  throw error;
453
565
  }
454
566
  finally {
455
- options?.signal?.removeEventListener('abort', abort);
456
- if (graphObservations.get(id) === observation)
457
- graphObservations.delete(id);
458
- if (subscribed && !observation.terminal) {
567
+ prepared.release();
568
+ if (eventObservations.get(id) === observation)
569
+ eventObservations.delete(id);
570
+ if (subscribed) {
459
571
  try {
460
572
  send({
461
573
  protocol: RUNTIME_PROTOCOL_SESSION_NAME,
462
574
  version: RUNTIME_PROTOCOL_SESSION_VERSION,
463
- kind: 'graph-unobserve',
575
+ kind: 'event-unobserve',
464
576
  id,
465
577
  });
466
578
  }
467
579
  catch {
468
- // Disconnect already released the server-side session resources.
580
+ // Disconnect already released the server-side subscription.
469
581
  }
470
582
  }
471
583
  }
472
584
  };
473
585
  return {
586
+ descriptor: { kind: 'websocket' },
474
587
  request,
475
588
  durableOperation: { observe: observeDurable },
476
589
  graph: { observe: observeGraph },
590
+ events: { observe: observeEvent },
477
591
  close: () => {
478
592
  if (disposed)
479
593
  return;