@ably/ai-transport 0.1.0 → 0.3.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/README.md +93 -111
- package/dist/ably-ai-transport.js +2401 -1387
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +116 -42
- package/dist/core/agent.d.ts +44 -0
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +24 -24
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +10 -12
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -2
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +470 -119
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-session.d.ts +10 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/client-session.d.ts +13 -0
- package/dist/core/transport/decode-fold.d.ts +55 -0
- package/dist/core/transport/headers.d.ts +121 -14
- package/dist/core/transport/index.d.ts +5 -6
- package/dist/core/transport/internal/bounded-map.d.ts +20 -0
- package/dist/core/transport/invocation.d.ts +74 -0
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +44 -0
- package/dist/core/transport/pipe-stream.d.ts +9 -9
- package/dist/core/transport/run-manager.d.ts +76 -0
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +523 -109
- package/dist/core/transport/types/agent.d.ts +375 -0
- package/dist/core/transport/types/client.d.ts +201 -0
- package/dist/core/transport/types/shared.d.ts +24 -0
- package/dist/core/transport/types/tree.d.ts +357 -0
- package/dist/core/transport/types/view.d.ts +249 -0
- package/dist/core/transport/types.d.ts +13 -553
- package/dist/core/transport/view.d.ts +390 -84
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +27 -10
- package/dist/index.d.ts +8 -9
- package/dist/logger.d.ts +12 -0
- package/dist/react/ably-ai-transport-react.js +1365 -1010
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/client-session-context.d.ts +37 -0
- package/dist/react/contexts/client-session-provider.d.ts +56 -0
- package/dist/react/create-session-hooks.d.ts +116 -0
- package/dist/react/index.d.ts +13 -12
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/internal/use-resolved-session.d.ts +36 -0
- package/dist/react/use-ably-messages.d.ts +17 -14
- package/dist/react/use-client-session.d.ts +81 -0
- package/dist/react/use-create-view.d.ts +14 -13
- package/dist/react/use-tree.d.ts +30 -15
- package/dist/react/use-view.d.ts +81 -50
- package/dist/utils.d.ts +48 -71
- package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +50 -0
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +7 -20
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +62 -0
- package/dist/vercel/codec/tool-transitions.d.ts +2 -8
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +5 -5
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
- package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
- package/dist/vercel/react/index.d.ts +1 -2
- package/dist/vercel/react/use-chat-transport.d.ts +30 -26
- package/dist/vercel/react/use-message-sync.d.ts +17 -30
- package/dist/vercel/run-end-reason.d.ts +84 -0
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +41 -24
- package/dist/vercel/transport/index.d.ts +24 -20
- package/dist/vercel/transport/run-output-stream.d.ts +54 -0
- package/dist/version.d.ts +2 -0
- package/package.json +31 -24
- package/src/constants.ts +124 -51
- package/src/core/agent.ts +92 -0
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +202 -105
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +114 -107
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +56 -6
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/lifecycle-tracker.ts +10 -9
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +505 -126
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +1085 -0
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +780 -0
- package/src/core/transport/decode-fold.ts +101 -0
- package/src/core/transport/headers.ts +234 -22
- package/src/core/transport/index.ts +27 -27
- package/src/core/transport/internal/bounded-map.ts +27 -0
- package/src/core/transport/invocation.ts +98 -0
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +271 -0
- package/src/core/transport/pipe-stream.ts +63 -39
- package/src/core/transport/run-manager.ts +243 -0
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +1293 -308
- package/src/core/transport/types/agent.ts +434 -0
- package/src/core/transport/types/client.ts +247 -0
- package/src/core/transport/types/shared.ts +27 -0
- package/src/core/transport/types/tree.ts +393 -0
- package/src/core/transport/types/view.ts +288 -0
- package/src/core/transport/types.ts +13 -706
- package/src/core/transport/view.ts +1229 -450
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +29 -9
- package/src/event-emitter.ts +3 -2
- package/src/index.ts +86 -42
- package/src/logger.ts +14 -1
- package/src/react/contexts/client-session-context.ts +41 -0
- package/src/react/contexts/client-session-provider.tsx +222 -0
- package/src/react/create-session-hooks.ts +141 -0
- package/src/react/index.ts +24 -13
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/internal/use-resolved-session.ts +63 -0
- package/src/react/use-ably-messages.ts +32 -22
- package/src/react/use-client-session.ts +178 -0
- package/src/react/use-create-view.ts +33 -29
- package/src/react/use-tree.ts +61 -30
- package/src/react/use-view.ts +138 -96
- package/src/utils.ts +83 -131
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +85 -0
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +28 -21
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +191 -0
- package/src/vercel/codec/tool-transitions.ts +3 -14
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +7 -19
- package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
- package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
- package/src/vercel/react/index.ts +3 -5
- package/src/vercel/react/use-chat-transport.ts +44 -66
- package/src/vercel/react/use-message-sync.ts +75 -39
- package/src/vercel/run-end-reason.ts +157 -0
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +380 -98
- package/src/vercel/transport/index.ts +38 -37
- package/src/vercel/transport/run-output-stream.ts +169 -0
- package/src/version.ts +2 -0
- package/dist/core/transport/client-transport.d.ts +0 -10
- package/dist/core/transport/decode-history.d.ts +0 -43
- package/dist/core/transport/server-transport.d.ts +0 -7
- package/dist/core/transport/stream-router.d.ts +0 -29
- package/dist/core/transport/turn-manager.d.ts +0 -37
- package/dist/react/contexts/transport-context.d.ts +0 -31
- package/dist/react/contexts/transport-provider.d.ts +0 -49
- package/dist/react/create-transport-hooks.d.ts +0 -124
- package/dist/react/use-active-turns.d.ts +0 -12
- package/dist/react/use-client-transport.d.ts +0 -80
- package/dist/vercel/codec/accumulator.d.ts +0 -21
- package/dist/vercel/codec/decoder.d.ts +0 -22
- package/dist/vercel/codec/encoder.d.ts +0 -41
- package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
- package/dist/vercel/tool-approvals.d.ts +0 -124
- package/dist/vercel/tool-events.d.ts +0 -26
- package/src/core/transport/client-transport.ts +0 -977
- package/src/core/transport/decode-history.ts +0 -485
- package/src/core/transport/server-transport.ts +0 -612
- package/src/core/transport/stream-router.ts +0 -136
- package/src/core/transport/turn-manager.ts +0 -165
- package/src/react/contexts/transport-context.ts +0 -37
- package/src/react/contexts/transport-provider.tsx +0 -164
- package/src/react/create-transport-hooks.ts +0 -144
- package/src/react/use-active-turns.ts +0 -72
- package/src/react/use-client-transport.ts +0 -197
- package/src/vercel/codec/accumulator.ts +0 -588
- package/src/vercel/codec/decoder.ts +0 -618
- package/src/vercel/codec/encoder.ts +0 -410
- package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
- package/src/vercel/tool-approvals.ts +0 -380
- package/src/vercel/tool-events.ts +0 -53
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClientSessionProvider: creates a ClientSession and makes it available to
|
|
3
|
+
* descendants via ClientSessionContext.
|
|
4
|
+
*
|
|
5
|
+
* Reads the Ably Realtime client from the surrounding `<AblyProvider>` and
|
|
6
|
+
* forwards it to `createClientSession` along with the supplied `channelName`.
|
|
7
|
+
*
|
|
8
|
+
* The session is created on first render (via useRef) and recreated when
|
|
9
|
+
* `channelName` changes; the previous session is queued for disposal.
|
|
10
|
+
* `connect()` is invoked from a `useEffect` so the session is
|
|
11
|
+
* subscribed/attached before the first descendant operation. If
|
|
12
|
+
* `createClientSession` throws,
|
|
13
|
+
* the error is stored in the ClientSessionSlot (alongside an undefined
|
|
14
|
+
* session) so that useClientSession can surface it as `sessionError`
|
|
15
|
+
* without crashing the component tree.
|
|
16
|
+
*
|
|
17
|
+
* The session is closed when the provider truly unmounts. The close is
|
|
18
|
+
* scheduled as a microtask so that React Strict Mode's synchronous
|
|
19
|
+
* remount cycle (mount → fake-unmount → remount) can cancel it before it
|
|
20
|
+
* fires, avoiding unnecessary session teardown in development.
|
|
21
|
+
*
|
|
22
|
+
* Multiple ClientSessionProviders can be nested using distinct channelNames.
|
|
23
|
+
* Each provider merges its slot into the parent record so descendants
|
|
24
|
+
* can access all registered sessions via useClientSession(channelName).
|
|
25
|
+
*
|
|
26
|
+
* The provider also wraps its children in ably-js's `<ChannelProvider>` for the
|
|
27
|
+
* session's channel, so descendants can use ably-js channel hooks
|
|
28
|
+
* (`usePresence`, `useChannel`, etc.) against it without adding their own. It
|
|
29
|
+
* seeds the ChannelProvider's `options` with this SDK's channel agent so the
|
|
30
|
+
* hooks' agent is appended rather than overwriting it (ably-js >= 2.22).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import * as Ably from 'ably';
|
|
34
|
+
import { ChannelProvider, useAbly } from 'ably/react';
|
|
35
|
+
import { type PropsWithChildren, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react';
|
|
36
|
+
|
|
37
|
+
import { channelAgent } from '../../core/agent.js';
|
|
38
|
+
import { resolveChannelModes } from '../../core/channel-options.js';
|
|
39
|
+
import type { CodecInputEvent, CodecOutputEvent } from '../../core/codec/types.js';
|
|
40
|
+
import { createClientSession } from '../../core/transport/client-session.js';
|
|
41
|
+
import type { ClientSession, ClientSessionOptions } from '../../core/transport/types.js';
|
|
42
|
+
import { ErrorCode } from '../../errors.js';
|
|
43
|
+
import type { ClientSessionSlot } from './client-session-context.js';
|
|
44
|
+
import { ClientSessionContext } from './client-session-context.js';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Props for {@link ClientSessionProvider}.
|
|
48
|
+
*
|
|
49
|
+
* All {@link ClientSessionOptions} except `client` (read from the surrounding
|
|
50
|
+
* `<AblyProvider>`).
|
|
51
|
+
*/
|
|
52
|
+
export interface ClientSessionProviderProps<
|
|
53
|
+
TInput extends CodecInputEvent,
|
|
54
|
+
TOutput extends CodecOutputEvent,
|
|
55
|
+
TProjection,
|
|
56
|
+
TMessage,
|
|
57
|
+
>
|
|
58
|
+
extends Omit<ClientSessionOptions<TInput, TOutput, TProjection, TMessage>, 'client'>, PropsWithChildren {}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Provide a {@link ClientSession} to descendant components.
|
|
62
|
+
*
|
|
63
|
+
* Reads the Ably Realtime client from the surrounding `<AblyProvider>`,
|
|
64
|
+
* creates a session bound to `channelName`, calls `connect()` on mount,
|
|
65
|
+
* and registers it in `ClientSessionContext` under `channelName`.
|
|
66
|
+
* Descendants call {@link useClientSession} with the same `channelName` to
|
|
67
|
+
* access the session.
|
|
68
|
+
*
|
|
69
|
+
* If `createClientSession` throws during construction, the error is surfaced
|
|
70
|
+
* through `useClientSession` as `sessionError` — the component tree does not
|
|
71
|
+
* crash and children are still rendered.
|
|
72
|
+
*
|
|
73
|
+
* ```tsx
|
|
74
|
+
* <AblyProvider client={ably}>
|
|
75
|
+
* <ClientSessionProvider channelName="ai:demo" codec={UIMessageCodec}>
|
|
76
|
+
* <Chat />
|
|
77
|
+
* </ClientSessionProvider>
|
|
78
|
+
* </AblyProvider>
|
|
79
|
+
*
|
|
80
|
+
* // Inside Chat:
|
|
81
|
+
* const { session, sessionError } = useClientSession({ channelName: 'ai:demo' });
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* For multiple sessions, nest providers with distinct channelNames:
|
|
85
|
+
*
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <ClientSessionProvider channelName="ai:main" codec={UIMessageCodec}>
|
|
88
|
+
* <ClientSessionProvider channelName="ai:aux" codec={UIMessageCodec}>
|
|
89
|
+
* <App />
|
|
90
|
+
* </ClientSessionProvider>
|
|
91
|
+
* </ClientSessionProvider>
|
|
92
|
+
*
|
|
93
|
+
* // Inside App:
|
|
94
|
+
* const { session: main } = useClientSession({ channelName: 'ai:main' });
|
|
95
|
+
* const { session: aux } = useClientSession({ channelName: 'ai:aux' });
|
|
96
|
+
* ```
|
|
97
|
+
* `channelModes` must stay constant for the provider's lifetime: the session is
|
|
98
|
+
* only recreated when `channelName` changes, and removing the modes after mount
|
|
99
|
+
* silently reverts the channel's mode set without a reattach.
|
|
100
|
+
* @param props - Provider configuration including `channelName`, `codec`, and all other {@link ClientSessionOptions} except `client`.
|
|
101
|
+
* @param props.children - Descendant components that consume the session via {@link useClientSession}.
|
|
102
|
+
* @returns A React element wrapping children with ClientSessionContext.
|
|
103
|
+
*/
|
|
104
|
+
export const ClientSessionProvider = <
|
|
105
|
+
TInput extends CodecInputEvent,
|
|
106
|
+
TOutput extends CodecOutputEvent,
|
|
107
|
+
TProjection,
|
|
108
|
+
TMessage,
|
|
109
|
+
>({
|
|
110
|
+
children,
|
|
111
|
+
...sessionOptions
|
|
112
|
+
}: ClientSessionProviderProps<TInput, TOutput, TProjection, TMessage>): ReactNode => {
|
|
113
|
+
const client = useAbly();
|
|
114
|
+
const { channelName } = sessionOptions;
|
|
115
|
+
|
|
116
|
+
// Seed the ChannelProvider with this SDK's channel agent so ably-js's React
|
|
117
|
+
// hooks append their agent (`channelOptionsForReactHooks`) rather than
|
|
118
|
+
// overwriting it. Memoised on the codec, which determines the agent string.
|
|
119
|
+
//
|
|
120
|
+
// Spec: AIT-CT23 — resolve the channel modes through the same helper the
|
|
121
|
+
// session uses so the provider and the session request an identical,
|
|
122
|
+
// identically-ordered mode set. ably-js compares modes order- and
|
|
123
|
+
// duplicate-sensitively, so matching arrays mean the provider's setOptions
|
|
124
|
+
// never triggers a reattach and never silently reverts the session's modes.
|
|
125
|
+
const channelOptions = useMemo<Ably.ChannelOptions>(() => {
|
|
126
|
+
const options: Ably.ChannelOptions = { params: { agent: channelAgent(sessionOptions.codec) } };
|
|
127
|
+
const modes = resolveChannelModes(sessionOptions.channelModes);
|
|
128
|
+
if (modes) options.modes = modes;
|
|
129
|
+
return options;
|
|
130
|
+
}, [sessionOptions.codec, sessionOptions.channelModes]);
|
|
131
|
+
const sessionRef = useRef<ClientSession<TInput, TOutput, TProjection, TMessage> | undefined>(undefined);
|
|
132
|
+
const sessionChannelRef = useRef<string>(channelName);
|
|
133
|
+
const sessionsToDisposeRef = useRef<ClientSession<CodecInputEvent, CodecOutputEvent, unknown, unknown>[]>([]);
|
|
134
|
+
const pendingCloseRef = useRef(false);
|
|
135
|
+
const constructionErrorRef = useRef<Ably.ErrorInfo | undefined>(undefined);
|
|
136
|
+
|
|
137
|
+
const alreadyCreatedOrFailed = !!sessionRef.current || !!constructionErrorRef.current;
|
|
138
|
+
|
|
139
|
+
if (!alreadyCreatedOrFailed || sessionChannelRef.current !== channelName) {
|
|
140
|
+
sessionChannelRef.current = channelName;
|
|
141
|
+
if (sessionRef.current) sessionsToDisposeRef.current.push(sessionRef.current);
|
|
142
|
+
try {
|
|
143
|
+
sessionRef.current = createClientSession({ ...sessionOptions, client });
|
|
144
|
+
constructionErrorRef.current = undefined;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
sessionRef.current = undefined;
|
|
147
|
+
constructionErrorRef.current =
|
|
148
|
+
error instanceof Ably.ErrorInfo
|
|
149
|
+
? error
|
|
150
|
+
: new Ably.ErrorInfo('Unknown error while creating client session', ErrorCode.BadRequest, 400);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const parentContext = useContext(ClientSessionContext);
|
|
155
|
+
|
|
156
|
+
// Capture ref values as locals so useMemo deps track changes correctly.
|
|
157
|
+
// CAST: ClientSessionContext stores sessions with erased generics.
|
|
158
|
+
// The generic types are fixed at the ClientSessionProvider<TInput, TOutput, TProjection, TMessage> boundary.
|
|
159
|
+
const currentSession = sessionRef.current as
|
|
160
|
+
| ClientSession<CodecInputEvent, CodecOutputEvent, unknown, unknown>
|
|
161
|
+
| undefined;
|
|
162
|
+
const currentError = constructionErrorRef.current;
|
|
163
|
+
|
|
164
|
+
const slot = useMemo<ClientSessionSlot>(
|
|
165
|
+
() => ({ session: currentSession, sessionError: currentError }),
|
|
166
|
+
[currentSession, currentError],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const contextValue = useMemo(
|
|
170
|
+
() => ({ nearest: slot, providers: { ...parentContext.providers, [channelName]: slot } }),
|
|
171
|
+
[channelName, parentContext, slot],
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Dispose sessions superseded by a channelName change. When channelName
|
|
175
|
+
// changes, the render path above pushes the now-stale session into
|
|
176
|
+
// sessionsToDisposeRef and creates a replacement. This effect's cleanup —
|
|
177
|
+
// which runs on the next channelName change or on unmount — closes every
|
|
178
|
+
// queued session.
|
|
179
|
+
useEffect(
|
|
180
|
+
() => () => {
|
|
181
|
+
for (const session of sessionsToDisposeRef.current) void session.close();
|
|
182
|
+
},
|
|
183
|
+
[channelName],
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Trigger connect() once the session is created. Re-runs when channelName
|
|
187
|
+
// changes so the freshly-recreated session connects too. Any error is
|
|
188
|
+
// stored on the session's emitter and surfaced via on('error');
|
|
189
|
+
// useClientSession doesn't need to await this.
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
void sessionRef.current?.connect();
|
|
192
|
+
}, [channelName]);
|
|
193
|
+
|
|
194
|
+
// Close the session when the component truly unmounts. The close is
|
|
195
|
+
// scheduled as a microtask: in React Strict Mode (dev) the component
|
|
196
|
+
// remounts synchronously before any microtask can drain, so the remount's
|
|
197
|
+
// effect setup resets pendingCloseRef.current = false and cancels the
|
|
198
|
+
// close. On a real unmount no remount follows, the microtask fires, and
|
|
199
|
+
// the session is closed.
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
pendingCloseRef.current = false;
|
|
202
|
+
return () => {
|
|
203
|
+
pendingCloseRef.current = true;
|
|
204
|
+
void Promise.resolve().then(() => {
|
|
205
|
+
if (pendingCloseRef.current) {
|
|
206
|
+
void sessionRef.current?.close();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<ClientSessionContext.Provider value={contextValue}>
|
|
214
|
+
<ChannelProvider
|
|
215
|
+
channelName={channelName}
|
|
216
|
+
options={channelOptions}
|
|
217
|
+
>
|
|
218
|
+
{children}
|
|
219
|
+
</ChannelProvider>
|
|
220
|
+
</ClientSessionContext.Provider>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createSessionHooks: factory that captures the codec's type parameters once and
|
|
3
|
+
* returns a bundle of type-safe hooks + ClientSessionProvider. Hook call sites need
|
|
4
|
+
* no type parameters at every use — just call the hooks directly.
|
|
5
|
+
* @example
|
|
6
|
+
* // Once per app (e.g. in a shared session.ts):
|
|
7
|
+
* export const {
|
|
8
|
+
* ClientSessionProvider,
|
|
9
|
+
* useClientSession,
|
|
10
|
+
* useView,
|
|
11
|
+
* } = createSessionHooks<VercelInput, VercelOutput, VercelProjection, UIMessage>();
|
|
12
|
+
*
|
|
13
|
+
* // In page:
|
|
14
|
+
* <ClientSessionProvider channelName="ai:demo" codec={UIMessageCodec}>
|
|
15
|
+
* <Chat />
|
|
16
|
+
* </ClientSessionProvider>
|
|
17
|
+
*
|
|
18
|
+
* // In Chat — no type params needed, session is implicit from nearest provider:
|
|
19
|
+
* const { nodes } = useView({ limit: 30 });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type * as Ably from 'ably';
|
|
23
|
+
import type { ComponentType } from 'react';
|
|
24
|
+
|
|
25
|
+
import type { CodecInputEvent, CodecOutputEvent } from '../core/codec/types.js';
|
|
26
|
+
import type { ClientSession, View } from '../core/transport/types.js';
|
|
27
|
+
import type { ClientSessionProviderProps } from './contexts/client-session-provider.js';
|
|
28
|
+
import { ClientSessionProvider as _ClientSessionProvider } from './contexts/client-session-provider.js';
|
|
29
|
+
import { useAblyMessages as _useAblyMessages } from './use-ably-messages.js';
|
|
30
|
+
import type { ClientSessionHandle } from './use-client-session.js';
|
|
31
|
+
import { useClientSession as _useClientSession } from './use-client-session.js';
|
|
32
|
+
import { useCreateView as _useCreateView } from './use-create-view.js';
|
|
33
|
+
import type { TreeHandle } from './use-tree.js';
|
|
34
|
+
import { useTree as _useTree } from './use-tree.js';
|
|
35
|
+
import type { ViewHandle } from './use-view.js';
|
|
36
|
+
import { useView as _useView } from './use-view.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Bundle of type-safe hooks and provider returned by {@link createSessionHooks}.
|
|
40
|
+
*
|
|
41
|
+
* The codec's `TInput`, `TOutput`, `TProjection`, and `TMessage` are baked in at
|
|
42
|
+
* factory creation time so no type params are needed at hook call sites.
|
|
43
|
+
*/
|
|
44
|
+
export interface SessionHooks<TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage> {
|
|
45
|
+
/**
|
|
46
|
+
* `ClientSessionProvider` narrowed to the codec's `TInput`/`TOutput`/`TMessage`. No JSX type params needed.
|
|
47
|
+
*/
|
|
48
|
+
ClientSessionProvider: ComponentType<ClientSessionProviderProps<TInput, TOutput, TProjection, TMessage>>;
|
|
49
|
+
/**
|
|
50
|
+
* Read the session from context. No type params needed.
|
|
51
|
+
*
|
|
52
|
+
* Returns `{ session, sessionError }`. When no provider is found (or session
|
|
53
|
+
* construction failed), `sessionError` is set and `session` is a stub that
|
|
54
|
+
* throws on access — the hook never throws during render.
|
|
55
|
+
*
|
|
56
|
+
* Pass `onError` to subscribe to post-construction session errors
|
|
57
|
+
* (e.g. send failures, channel continuity loss) without wiring
|
|
58
|
+
* `session.on('error', …)` manually.
|
|
59
|
+
*/
|
|
60
|
+
useClientSession: (props?: {
|
|
61
|
+
/** Channel name to look up; omit to use the nearest {@link ClientSessionProvider}. */
|
|
62
|
+
channelName?: string;
|
|
63
|
+
/** When `true`, return a stub session that throws on any access. */
|
|
64
|
+
skip?: boolean;
|
|
65
|
+
/** Called whenever the resolved session emits an error event. */
|
|
66
|
+
onError?: (error: Ably.ErrorInfo) => void;
|
|
67
|
+
}) => ClientSessionHandle<TInput, TOutput, TProjection, TMessage>;
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe to the nearest session's view and return the visible message list with pagination.
|
|
70
|
+
* Pass `session` to use a session's default view, `view` to subscribe to a specific view
|
|
71
|
+
* directly. Pass `limit` to auto-load on mount. Pass `skip: true` for an empty handle.
|
|
72
|
+
*/
|
|
73
|
+
useView: (props?: {
|
|
74
|
+
/** Client session whose default view to subscribe to; defaults to the nearest {@link ClientSessionProvider}. */
|
|
75
|
+
session?: ClientSession<TInput, TOutput, TProjection, TMessage> | null;
|
|
76
|
+
/** A specific {@link View} to subscribe to directly. Takes priority over `session`. */
|
|
77
|
+
view?: View<TInput, TMessage> | null;
|
|
78
|
+
/** When provided, auto-loads the first page on mount. */
|
|
79
|
+
limit?: number;
|
|
80
|
+
/** When `true`, skip all subscriptions and return an empty handle. */
|
|
81
|
+
skip?: boolean;
|
|
82
|
+
}) => ViewHandle<TInput, TMessage>;
|
|
83
|
+
/**
|
|
84
|
+
* Navigate conversation branches in the session tree.
|
|
85
|
+
* Pass `session` to override; defaults to the nearest {@link ClientSessionProvider}.
|
|
86
|
+
*/
|
|
87
|
+
useTree: (props?: {
|
|
88
|
+
/** Override session; defaults to the nearest {@link ClientSessionProvider}. */
|
|
89
|
+
session?: ClientSession<TInput, TOutput, TProjection, TMessage>;
|
|
90
|
+
}) => TreeHandle<TProjection>;
|
|
91
|
+
/**
|
|
92
|
+
* Subscribe to raw Ably messages on the session channel.
|
|
93
|
+
* Pass `session` to override; defaults to the nearest {@link ClientSessionProvider}.
|
|
94
|
+
* Pass `skip: true` to return an empty array without subscribing.
|
|
95
|
+
*/
|
|
96
|
+
useAblyMessages: (props?: {
|
|
97
|
+
/** Override session; defaults to the nearest {@link ClientSessionProvider}. */
|
|
98
|
+
session?: ClientSession<TInput, TOutput, TProjection, TMessage>;
|
|
99
|
+
/** When `true`, skip all subscriptions and return an empty array. */
|
|
100
|
+
skip?: boolean;
|
|
101
|
+
}) => Ably.InboundMessage[];
|
|
102
|
+
/**
|
|
103
|
+
* Create an independent view over the same tree.
|
|
104
|
+
* Pass `session` to override; defaults to the nearest {@link ClientSessionProvider}.
|
|
105
|
+
* Pass `skip: true` to return an empty handle without creating a view.
|
|
106
|
+
*/
|
|
107
|
+
useCreateView: (props?: {
|
|
108
|
+
/** Override session; defaults to the nearest {@link ClientSessionProvider}. */
|
|
109
|
+
session?: ClientSession<TInput, TOutput, TProjection, TMessage> | null;
|
|
110
|
+
/** When provided, auto-loads the first page on mount. */
|
|
111
|
+
limit?: number;
|
|
112
|
+
/** When `true`, skip view creation and return an empty handle. */
|
|
113
|
+
skip?: boolean;
|
|
114
|
+
}) => ViewHandle<TInput, TMessage>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create a bundle of type-safe hooks and provider for a given codec's
|
|
119
|
+
* `TInput`/`TOutput`/`TProjection`/`TMessage`.
|
|
120
|
+
*
|
|
121
|
+
* These type parameters are captured at factory creation time; hook call sites need
|
|
122
|
+
* no type parameters. The returned hooks are thin wrappers around the standalone hooks
|
|
123
|
+
* with the types resolved.
|
|
124
|
+
* @returns A {@link SessionHooks} bundle.
|
|
125
|
+
*/
|
|
126
|
+
export const createSessionHooks = <
|
|
127
|
+
TInput extends CodecInputEvent,
|
|
128
|
+
TOutput extends CodecOutputEvent,
|
|
129
|
+
TProjection,
|
|
130
|
+
TMessage,
|
|
131
|
+
>(): SessionHooks<TInput, TOutput, TProjection, TMessage> => ({
|
|
132
|
+
// CAST: ClientSessionProvider is generic; factory narrows it to the codec's TInput/TOutput/TProjection/TMessage.
|
|
133
|
+
ClientSessionProvider: _ClientSessionProvider as ComponentType<
|
|
134
|
+
ClientSessionProviderProps<TInput, TOutput, TProjection, TMessage>
|
|
135
|
+
>,
|
|
136
|
+
useClientSession: (props) => _useClientSession<TInput, TOutput, TProjection, TMessage>(props ?? {}),
|
|
137
|
+
useView: (props) => _useView<TInput, TOutput, TProjection, TMessage>(props ?? {}),
|
|
138
|
+
useTree: (props) => _useTree<TInput, TOutput, TProjection, TMessage>(props ?? {}),
|
|
139
|
+
useAblyMessages: (props) => _useAblyMessages<TInput, TOutput, TProjection, TMessage>(props ?? {}),
|
|
140
|
+
useCreateView: (props) => _useCreateView<TInput, TOutput, TProjection, TMessage>(props ?? {}),
|
|
141
|
+
});
|
package/src/react/index.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
export
|
|
2
|
-
export type {
|
|
3
|
-
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
export { OBJECT_MODES } from '../core/channel-options.js';
|
|
2
|
+
export type { CodecMessage } from '../core/codec/types.js';
|
|
3
|
+
export type {
|
|
4
|
+
ActiveRun,
|
|
5
|
+
BranchSelection,
|
|
6
|
+
ClientSession,
|
|
7
|
+
ConversationNode,
|
|
8
|
+
InputNode,
|
|
9
|
+
MessageNode,
|
|
10
|
+
RunInfo,
|
|
11
|
+
RunNode,
|
|
12
|
+
RunNodeState,
|
|
13
|
+
SendOptions,
|
|
14
|
+
} from '../core/transport/types.js';
|
|
15
|
+
export type { ClientSessionSlot } from './contexts/client-session-context.js';
|
|
16
|
+
export type { ClientSessionProviderProps } from './contexts/client-session-provider.js';
|
|
17
|
+
export { ClientSessionProvider } from './contexts/client-session-provider.js';
|
|
18
|
+
export type { SessionHooks } from './create-session-hooks.js';
|
|
19
|
+
export { createSessionHooks } from './create-session-hooks.js';
|
|
20
|
+
export type { UseAblyMessagesOptions } from './use-ably-messages.js';
|
|
10
21
|
export { useAblyMessages } from './use-ably-messages.js';
|
|
11
|
-
export {
|
|
12
|
-
export
|
|
13
|
-
export {
|
|
22
|
+
export type { ClientSessionHandle } from './use-client-session.js';
|
|
23
|
+
export { useClientSession } from './use-client-session.js';
|
|
24
|
+
export type { UseCreateViewOptions } from './use-create-view.js';
|
|
14
25
|
export { useCreateView } from './use-create-view.js';
|
|
15
|
-
export type { TreeHandle } from './use-tree.js';
|
|
26
|
+
export type { TreeHandle, UseTreeOptions } from './use-tree.js';
|
|
16
27
|
export { useTree } from './use-tree.js';
|
|
17
28
|
export type { UseViewOptions, ViewHandle } from './use-view.js';
|
|
18
29
|
export { useView } from './use-view.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stub {@link ClientSession} returned by the context-reader hooks when they
|
|
3
|
+
* are skipped, when no provider is found, or when session construction failed.
|
|
4
|
+
* Every member throws so a held-but-unusable session fails loudly rather than
|
|
5
|
+
* silently no-ops. Generic so each consumer gets a session typed to its own
|
|
6
|
+
* codec parameters without a cast — the throwing bodies satisfy any instantiation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as Ably from 'ably';
|
|
10
|
+
import type * as AblyObjects from 'ably/liveobjects';
|
|
11
|
+
|
|
12
|
+
import type { CodecInputEvent, CodecOutputEvent } from '../../core/codec/types.js';
|
|
13
|
+
import type { ClientSession, Tree, View } from '../../core/transport/types.js';
|
|
14
|
+
import { ErrorCode } from '../../errors.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build the `hook is skipped` error for a stub member.
|
|
18
|
+
* @param operation - The attempted operation, phrased for the `unable to <operation>; hook is skipped` message.
|
|
19
|
+
* @returns The skipped-hook error.
|
|
20
|
+
*/
|
|
21
|
+
const skipped = (operation: string): Ably.ErrorInfo =>
|
|
22
|
+
new Ably.ErrorInfo(`unable to ${operation}; hook is skipped`, ErrorCode.InvalidArgument, 400);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a throwing stub {@link ClientSession}. Held safely in state before a
|
|
26
|
+
* provider resolves; any access throws {@link Ably.ErrorInfo}.
|
|
27
|
+
* @returns A stub session whose every member throws.
|
|
28
|
+
*/
|
|
29
|
+
export const makeSkippedClientSession = <
|
|
30
|
+
TInput extends CodecInputEvent,
|
|
31
|
+
TOutput extends CodecOutputEvent,
|
|
32
|
+
TProjection,
|
|
33
|
+
TMessage,
|
|
34
|
+
>(): ClientSession<TInput, TOutput, TProjection, TMessage> => ({
|
|
35
|
+
get tree(): Tree<TOutput, TProjection> {
|
|
36
|
+
throw skipped('access tree');
|
|
37
|
+
},
|
|
38
|
+
get view(): View<TInput, TMessage> {
|
|
39
|
+
throw skipped('access view');
|
|
40
|
+
},
|
|
41
|
+
get presence(): Ably.RealtimePresence {
|
|
42
|
+
throw skipped('access presence');
|
|
43
|
+
},
|
|
44
|
+
get object(): AblyObjects.RealtimeObject {
|
|
45
|
+
throw skipped('access object');
|
|
46
|
+
},
|
|
47
|
+
connect: () => {
|
|
48
|
+
throw skipped('connect');
|
|
49
|
+
},
|
|
50
|
+
createView: (): View<TInput, TMessage> => {
|
|
51
|
+
throw skipped('create view');
|
|
52
|
+
},
|
|
53
|
+
cancel: () => {
|
|
54
|
+
throw skipped('cancel');
|
|
55
|
+
},
|
|
56
|
+
on: () => {
|
|
57
|
+
throw skipped('subscribe');
|
|
58
|
+
},
|
|
59
|
+
close: () => {
|
|
60
|
+
throw skipped('close');
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { CodecInputEvent, CodecOutputEvent } from '../../core/codec/types.js';
|
|
4
|
+
import type { ClientSession } from '../../core/transport/types.js';
|
|
5
|
+
import { ClientSessionContext } from '../contexts/client-session-context.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared base for hook options that accept an explicit session override.
|
|
9
|
+
* Extend this interface for any hook whose `session` option defaults to the
|
|
10
|
+
* nearest {@link ClientSessionProvider} when omitted. Pass `null` to defer
|
|
11
|
+
* resolution (e.g. when a split pane is collapsed) — the helper returns
|
|
12
|
+
* `undefined` rather than falling back to the nearest provider.
|
|
13
|
+
*/
|
|
14
|
+
export interface BaseSessionOption<
|
|
15
|
+
TInput extends CodecInputEvent,
|
|
16
|
+
TOutput extends CodecOutputEvent,
|
|
17
|
+
TProjection,
|
|
18
|
+
TMessage,
|
|
19
|
+
> {
|
|
20
|
+
/**
|
|
21
|
+
* Session to operate on; defaults to the nearest {@link ClientSessionProvider}.
|
|
22
|
+
* Pass `null` to defer (returns undefined; nearest provider is not used).
|
|
23
|
+
*/
|
|
24
|
+
session?: ClientSession<TInput, TOutput, TProjection, TMessage> | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the active `ClientSession` for a hook.
|
|
29
|
+
*
|
|
30
|
+
* Reads `ClientSessionContext` and applies the standard three-way
|
|
31
|
+
* priority: explicit `session` argument → nearest provider → `undefined`.
|
|
32
|
+
* When `skip` is `true`, returns `undefined` regardless of context.
|
|
33
|
+
* When `session` is `null`, returns `undefined` (caller is deferring).
|
|
34
|
+
*
|
|
35
|
+
* Internal — not part of the public API.
|
|
36
|
+
* @param root0 - Options.
|
|
37
|
+
* @param root0.session - Explicit session; takes priority over the nearest provider. `null` to defer.
|
|
38
|
+
* @param root0.skip - When `true`, returns `undefined` immediately; context is still read but its value is ignored.
|
|
39
|
+
* @returns The resolved session, or `undefined` if none is available or `skip` is `true`.
|
|
40
|
+
*/
|
|
41
|
+
export const useResolvedSession = <
|
|
42
|
+
TInput extends CodecInputEvent,
|
|
43
|
+
TOutput extends CodecOutputEvent,
|
|
44
|
+
TProjection,
|
|
45
|
+
TMessage,
|
|
46
|
+
>({
|
|
47
|
+
session,
|
|
48
|
+
skip,
|
|
49
|
+
}: {
|
|
50
|
+
/** Explicit session; takes priority over the nearest provider. `null` to defer. */
|
|
51
|
+
session?: ClientSession<TInput, TOutput, TProjection, TMessage> | null;
|
|
52
|
+
/** When `true`, return `undefined` immediately (context is still read, but ignored). */
|
|
53
|
+
skip?: boolean;
|
|
54
|
+
} = {}): ClientSession<TInput, TOutput, TProjection, TMessage> | undefined => {
|
|
55
|
+
const { nearest } = useContext(ClientSessionContext);
|
|
56
|
+
// CAST: ClientSessionContext stores session with erased generics; types fixed at call site.
|
|
57
|
+
const nearestSession = nearest?.session as unknown as
|
|
58
|
+
| ClientSession<TInput, TOutput, TProjection, TMessage>
|
|
59
|
+
| undefined;
|
|
60
|
+
if (skip) return undefined;
|
|
61
|
+
if (session === null) return undefined;
|
|
62
|
+
return session ?? nearestSession;
|
|
63
|
+
};
|
|
@@ -1,43 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useAblyMessages — reactive raw Ably message log from a
|
|
2
|
+
* useAblyMessages — reactive raw Ably message log from a ClientSession.
|
|
3
3
|
*
|
|
4
|
-
* Accumulates raw Ably InboundMessages from the
|
|
4
|
+
* Accumulates raw Ably InboundMessages from the session's tree
|
|
5
5
|
* 'ably-message' event. Messages are appended in arrival order.
|
|
6
6
|
*
|
|
7
|
-
* When `
|
|
8
|
-
* {@link
|
|
7
|
+
* When `session` is omitted, defaults to the nearest
|
|
8
|
+
* {@link ClientSessionProvider}'s session via context.
|
|
9
9
|
* Pass `skip: true` to bypass all subscriptions and return an empty array.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type * as Ably from 'ably';
|
|
13
|
-
import {
|
|
13
|
+
import { useEffect, useRef, useState } from 'react';
|
|
14
14
|
|
|
15
|
-
import type {
|
|
16
|
-
import {
|
|
15
|
+
import type { CodecInputEvent, CodecOutputEvent } from '../core/codec/types.js';
|
|
16
|
+
import type { BaseSessionOption } from './internal/use-resolved-session.js';
|
|
17
|
+
import { useResolvedSession } from './internal/use-resolved-session.js';
|
|
18
|
+
|
|
19
|
+
/** Options for {@link useAblyMessages}. */
|
|
20
|
+
export interface UseAblyMessagesOptions<
|
|
21
|
+
TInput extends CodecInputEvent,
|
|
22
|
+
TOutput extends CodecOutputEvent,
|
|
23
|
+
TProjection,
|
|
24
|
+
TMessage,
|
|
25
|
+
> extends BaseSessionOption<TInput, TOutput, TProjection, TMessage> {
|
|
26
|
+
/** When `true`, skip all subscriptions and return an empty array. */
|
|
27
|
+
skip?: boolean;
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
/**
|
|
19
|
-
* Subscribe to raw Ably message updates from a client
|
|
20
|
-
* When `
|
|
21
|
-
* @param props - Options including optional `
|
|
22
|
-
* @param props.
|
|
31
|
+
* Subscribe to raw Ably message updates from a client session's tree.
|
|
32
|
+
* When `session` is omitted, uses the nearest {@link ClientSessionProvider}'s session via context.
|
|
33
|
+
* @param props - Options including optional `session` and `skip`.
|
|
34
|
+
* @param props.session - Session to subscribe to; defaults to the nearest provider.
|
|
23
35
|
* @param props.skip - When `true`, skip all subscriptions and return an empty array.
|
|
24
|
-
* @returns The accumulated raw Ably messages in
|
|
36
|
+
* @returns The accumulated raw Ably messages in event-arrival order — older history messages loaded later are appended after the live messages already present, so this is not strictly chronological.
|
|
25
37
|
*/
|
|
26
|
-
export const useAblyMessages = <
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const resolved = skip
|
|
33
|
-
? undefined
|
|
34
|
-
: ((transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | undefined);
|
|
38
|
+
export const useAblyMessages = <
|
|
39
|
+
TInput extends CodecInputEvent,
|
|
40
|
+
TOutput extends CodecOutputEvent,
|
|
41
|
+
TProjection,
|
|
42
|
+
TMessage,
|
|
43
|
+
>({ session, skip }: UseAblyMessagesOptions<TInput, TOutput, TProjection, TMessage> = {}): Ably.InboundMessage[] => {
|
|
44
|
+
const resolved = useResolvedSession({ session, skip });
|
|
35
45
|
|
|
36
46
|
const [messages, setMessages] = useState<Ably.InboundMessage[]>([]);
|
|
37
47
|
const messagesRef = useRef<Ably.InboundMessage[]>([]);
|
|
38
48
|
|
|
39
49
|
useEffect(() => {
|
|
40
|
-
// Reset on
|
|
50
|
+
// Reset on session change
|
|
41
51
|
messagesRef.current = [];
|
|
42
52
|
setMessages([]);
|
|
43
53
|
|