@ai-sdk/rsc 2.0.45 → 2.0.46

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 (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +3 -2
  3. package/src/ai-state.test.ts +146 -0
  4. package/src/ai-state.tsx +210 -0
  5. package/src/index.ts +20 -0
  6. package/src/provider.tsx +149 -0
  7. package/src/rsc-client.ts +8 -0
  8. package/src/rsc-server.ts +5 -0
  9. package/src/rsc-shared.mts +11 -0
  10. package/src/shared-client/context.tsx +226 -0
  11. package/src/shared-client/index.ts +11 -0
  12. package/src/stream-ui/__snapshots__/render.ui.test.tsx.snap +91 -0
  13. package/src/stream-ui/__snapshots__/stream-ui.ui.test.tsx.snap +213 -0
  14. package/src/stream-ui/index.tsx +1 -0
  15. package/src/stream-ui/stream-ui.tsx +419 -0
  16. package/src/stream-ui/stream-ui.ui.test.tsx +321 -0
  17. package/src/streamable-ui/create-streamable-ui.tsx +148 -0
  18. package/src/streamable-ui/create-streamable-ui.ui.test.tsx +354 -0
  19. package/src/streamable-ui/create-suspended-chunk.tsx +84 -0
  20. package/src/streamable-value/create-streamable-value.test.tsx +179 -0
  21. package/src/streamable-value/create-streamable-value.ts +296 -0
  22. package/src/streamable-value/is-streamable-value.ts +10 -0
  23. package/src/streamable-value/read-streamable-value.tsx +113 -0
  24. package/src/streamable-value/read-streamable-value.ui.test.tsx +165 -0
  25. package/src/streamable-value/streamable-value.ts +37 -0
  26. package/src/streamable-value/use-streamable-value.tsx +91 -0
  27. package/src/types/index.ts +1 -0
  28. package/src/types.test-d.ts +17 -0
  29. package/src/types.ts +71 -0
  30. package/src/util/constants.ts +5 -0
  31. package/src/util/create-resolvable-promise.ts +28 -0
  32. package/src/util/is-async-generator.ts +7 -0
  33. package/src/util/is-function.ts +8 -0
  34. package/src/util/is-generator.ts +5 -0
@@ -0,0 +1,296 @@
1
+ import { HANGING_STREAM_WARNING_TIME_MS } from '../util/constants';
2
+ import { createResolvablePromise } from '../util/create-resolvable-promise';
3
+ import {
4
+ STREAMABLE_VALUE_TYPE,
5
+ StreamablePatch,
6
+ StreamableValue,
7
+ } from './streamable-value';
8
+
9
+ const STREAMABLE_VALUE_INTERNAL_LOCK = Symbol('streamable.value.lock');
10
+
11
+ /**
12
+ * Create a wrapped, changeable value that can be streamed to the client.
13
+ * On the client side, the value can be accessed via the readStreamableValue() API.
14
+ */
15
+ function createStreamableValue<T = any, E = any>(
16
+ initialValue?: T | ReadableStream<T>,
17
+ ) {
18
+ const isReadableStream =
19
+ initialValue instanceof ReadableStream ||
20
+ (typeof initialValue === 'object' &&
21
+ initialValue !== null &&
22
+ 'getReader' in initialValue &&
23
+ typeof initialValue.getReader === 'function' &&
24
+ 'locked' in initialValue &&
25
+ typeof initialValue.locked === 'boolean');
26
+
27
+ if (!isReadableStream) {
28
+ return createStreamableValueImpl<T, E>(initialValue);
29
+ }
30
+
31
+ const streamableValue = createStreamableValueImpl<T, E>();
32
+
33
+ // Since the streamable value will be from a readable stream, it's not allowed
34
+ // to update the value manually as that introduces race conditions and
35
+ // unexpected behavior.
36
+ // We lock the value to prevent any updates from the user.
37
+ streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = true;
38
+
39
+ (async () => {
40
+ try {
41
+ // Consume the readable stream and update the value.
42
+ const reader = initialValue.getReader();
43
+
44
+ while (true) {
45
+ const { value, done } = await reader.read();
46
+ if (done) {
47
+ break;
48
+ }
49
+
50
+ // Unlock the value to allow updates.
51
+ streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = false;
52
+ if (typeof value === 'string') {
53
+ streamableValue.append(value);
54
+ } else {
55
+ streamableValue.update(value);
56
+ }
57
+ // Lock the value again.
58
+ streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = true;
59
+ }
60
+
61
+ streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = false;
62
+ streamableValue.done();
63
+ } catch (e) {
64
+ streamableValue[STREAMABLE_VALUE_INTERNAL_LOCK] = false;
65
+ streamableValue.error(e);
66
+ }
67
+ })();
68
+
69
+ return streamableValue;
70
+ }
71
+
72
+ // It's necessary to define the type manually here, otherwise TypeScript compiler
73
+ // will not be able to infer the correct return type as it's circular.
74
+ type StreamableValueWrapper<T, E> = {
75
+ /**
76
+ * The value of the streamable. This can be returned from a Server Action and
77
+ * received by the client. To read the streamed values, use the
78
+ * `readStreamableValue` or `useStreamableValue` APIs.
79
+ */
80
+ readonly value: StreamableValue<T, E>;
81
+
82
+ /**
83
+ * This method updates the current value with a new one.
84
+ */
85
+ update(value: T): StreamableValueWrapper<T, E>;
86
+
87
+ /**
88
+ * This method is used to append a delta string to the current value. It
89
+ * requires the current value of the streamable to be a string.
90
+ *
91
+ * @example
92
+ * ```jsx
93
+ * const streamable = createStreamableValue('hello');
94
+ * streamable.append(' world');
95
+ *
96
+ * // The value will be 'hello world'
97
+ * ```
98
+ */
99
+ append(value: T): StreamableValueWrapper<T, E>;
100
+
101
+ /**
102
+ * This method is used to signal that there is an error in the value stream.
103
+ * It will be thrown on the client side when consumed via
104
+ * `readStreamableValue` or `useStreamableValue`.
105
+ */
106
+ error(error: any): StreamableValueWrapper<T, E>;
107
+
108
+ /**
109
+ * This method marks the value as finalized. You can either call it without
110
+ * any parameters or with a new value as the final state.
111
+ * Once called, the value cannot be updated or appended anymore.
112
+ *
113
+ * This method is always **required** to be called, otherwise the response
114
+ * will be stuck in a loading state.
115
+ */
116
+ done(...args: [T] | []): StreamableValueWrapper<T, E>;
117
+
118
+ /**
119
+ * @internal This is an internal lock to prevent the value from being
120
+ * updated by the user.
121
+ */
122
+ [STREAMABLE_VALUE_INTERNAL_LOCK]: boolean;
123
+ };
124
+
125
+ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
126
+ let closed = false;
127
+ let locked = false;
128
+ let resolvable = createResolvablePromise<StreamableValue<T, E>>();
129
+
130
+ let currentValue = initialValue;
131
+ let currentError: E | undefined;
132
+ let currentPromise: typeof resolvable.promise | undefined =
133
+ resolvable.promise;
134
+ let currentPatchValue: StreamablePatch;
135
+
136
+ function assertStream(method: string) {
137
+ if (closed) {
138
+ throw new Error(method + ': Value stream is already closed.');
139
+ }
140
+ if (locked) {
141
+ throw new Error(
142
+ method + ': Value stream is locked and cannot be updated.',
143
+ );
144
+ }
145
+ }
146
+
147
+ let warningTimeout: NodeJS.Timeout | undefined;
148
+ function warnUnclosedStream() {
149
+ if (process.env.NODE_ENV === 'development') {
150
+ if (warningTimeout) {
151
+ clearTimeout(warningTimeout);
152
+ }
153
+ warningTimeout = setTimeout(() => {
154
+ console.warn(
155
+ 'The streamable value has been slow to update. This may be a bug or a performance issue or you forgot to call `.done()`.',
156
+ );
157
+ }, HANGING_STREAM_WARNING_TIME_MS);
158
+ }
159
+ }
160
+ warnUnclosedStream();
161
+
162
+ function createWrapped(initialChunk?: boolean): StreamableValue<T, E> {
163
+ // This makes the payload much smaller if there're mutative updates before the first read.
164
+ let init: Partial<StreamableValue<T, E>>;
165
+
166
+ if (currentError !== undefined) {
167
+ init = { error: currentError };
168
+ } else {
169
+ if (currentPatchValue && !initialChunk) {
170
+ init = { diff: currentPatchValue };
171
+ } else {
172
+ init = { curr: currentValue };
173
+ }
174
+ }
175
+
176
+ if (currentPromise) {
177
+ init.next = currentPromise;
178
+ }
179
+
180
+ if (initialChunk) {
181
+ init.type = STREAMABLE_VALUE_TYPE;
182
+ }
183
+
184
+ return init;
185
+ }
186
+
187
+ // Update the internal `currentValue` and `currentPatchValue` if needed.
188
+ function updateValueStates(value: T) {
189
+ // If we can only send a patch over the wire, it's better to do so.
190
+ currentPatchValue = undefined;
191
+ if (typeof value === 'string') {
192
+ if (typeof currentValue === 'string') {
193
+ if (value.startsWith(currentValue)) {
194
+ currentPatchValue = [0, value.slice(currentValue.length)];
195
+ }
196
+ }
197
+ }
198
+
199
+ currentValue = value;
200
+ }
201
+
202
+ const streamable: StreamableValueWrapper<T, E> = {
203
+ set [STREAMABLE_VALUE_INTERNAL_LOCK](state: boolean) {
204
+ locked = state;
205
+ },
206
+ get value() {
207
+ return createWrapped(true);
208
+ },
209
+ update(value: T) {
210
+ assertStream('.update()');
211
+
212
+ const resolvePrevious = resolvable.resolve;
213
+ resolvable = createResolvablePromise();
214
+
215
+ updateValueStates(value);
216
+ currentPromise = resolvable.promise;
217
+ resolvePrevious(createWrapped());
218
+
219
+ warnUnclosedStream();
220
+
221
+ return streamable;
222
+ },
223
+ append(value: T) {
224
+ assertStream('.append()');
225
+
226
+ if (
227
+ typeof currentValue !== 'string' &&
228
+ typeof currentValue !== 'undefined'
229
+ ) {
230
+ throw new Error(
231
+ `.append(): The current value is not a string. Received: ${typeof currentValue}`,
232
+ );
233
+ }
234
+ if (typeof value !== 'string') {
235
+ throw new Error(
236
+ `.append(): The value is not a string. Received: ${typeof value}`,
237
+ );
238
+ }
239
+
240
+ const resolvePrevious = resolvable.resolve;
241
+ resolvable = createResolvablePromise();
242
+
243
+ if (typeof currentValue === 'string') {
244
+ currentPatchValue = [0, value];
245
+ (currentValue as string) = currentValue + value;
246
+ } else {
247
+ currentPatchValue = undefined;
248
+ currentValue = value;
249
+ }
250
+
251
+ currentPromise = resolvable.promise;
252
+ resolvePrevious(createWrapped());
253
+
254
+ warnUnclosedStream();
255
+
256
+ return streamable;
257
+ },
258
+ error(error: any) {
259
+ assertStream('.error()');
260
+
261
+ if (warningTimeout) {
262
+ clearTimeout(warningTimeout);
263
+ }
264
+ closed = true;
265
+ currentError = error;
266
+ currentPromise = undefined;
267
+
268
+ resolvable.resolve({ error });
269
+
270
+ return streamable;
271
+ },
272
+ done(...args: [] | [T]) {
273
+ assertStream('.done()');
274
+
275
+ if (warningTimeout) {
276
+ clearTimeout(warningTimeout);
277
+ }
278
+ closed = true;
279
+ currentPromise = undefined;
280
+
281
+ if (args.length) {
282
+ updateValueStates(args[0]);
283
+ resolvable.resolve(createWrapped());
284
+ return streamable;
285
+ }
286
+
287
+ resolvable.resolve({});
288
+
289
+ return streamable;
290
+ },
291
+ };
292
+
293
+ return streamable;
294
+ }
295
+
296
+ export { createStreamableValue };
@@ -0,0 +1,10 @@
1
+ import { STREAMABLE_VALUE_TYPE, StreamableValue } from './streamable-value';
2
+
3
+ export function isStreamableValue(value: unknown): value is StreamableValue {
4
+ return (
5
+ value != null &&
6
+ typeof value === 'object' &&
7
+ 'type' in value &&
8
+ value.type === STREAMABLE_VALUE_TYPE
9
+ );
10
+ }
@@ -0,0 +1,113 @@
1
+ import { isStreamableValue } from './is-streamable-value';
2
+ import { StreamableValue } from './streamable-value';
3
+
4
+ /**
5
+ * `readStreamableValue` takes a streamable value created via the `createStreamableValue().value` API,
6
+ * and returns an async iterator.
7
+ *
8
+ * ```js
9
+ * // Inside your AI action:
10
+ *
11
+ * async function action() {
12
+ * 'use server'
13
+ * const streamable = createStreamableValue();
14
+ *
15
+ * streamable.update(1);
16
+ * streamable.update(2);
17
+ * streamable.done(3);
18
+ * // ...
19
+ * return streamable.value;
20
+ * }
21
+ * ```
22
+ *
23
+ * And to read the value:
24
+ *
25
+ * ```js
26
+ * const streamableValue = await action()
27
+ * for await (const v of readStreamableValue(streamableValue)) {
28
+ * console.log(v)
29
+ * }
30
+ * ```
31
+ *
32
+ * This logs out 1, 2, 3 on console.
33
+ */
34
+ export function readStreamableValue<T = unknown>(
35
+ streamableValue: StreamableValue<T>,
36
+ ): AsyncIterable<T | undefined> {
37
+ if (!isStreamableValue(streamableValue)) {
38
+ throw new Error(
39
+ 'Invalid value: this hook only accepts values created via `createStreamableValue`.',
40
+ );
41
+ }
42
+
43
+ return {
44
+ [Symbol.asyncIterator]() {
45
+ let row: StreamableValue<T> | Promise<StreamableValue<T>> =
46
+ streamableValue;
47
+ let value = row.curr; // the current value
48
+ let isDone = false;
49
+ let isFirstIteration = true;
50
+
51
+ return {
52
+ async next() {
53
+ // the iteration is done already, return the last value:
54
+ if (isDone) return { value, done: true };
55
+
56
+ // resolve the promise at the beginning of each iteration:
57
+ row = await row;
58
+
59
+ // throw error if any:
60
+ if (row.error !== undefined) {
61
+ throw row.error;
62
+ }
63
+
64
+ // if there is a value or a patch, use it:
65
+ if ('curr' in row || row.diff) {
66
+ if (row.diff) {
67
+ // streamable patch (text only):
68
+ if (row.diff[0] === 0) {
69
+ if (typeof value !== 'string') {
70
+ throw new Error(
71
+ 'Invalid patch: can only append to string types. This is a bug in the AI SDK.',
72
+ );
73
+ }
74
+
75
+ // casting required to remove T & string limitation
76
+ (value as string) = value + row.diff[1];
77
+ }
78
+ } else {
79
+ // replace the value (full new value)
80
+ value = row.curr;
81
+ }
82
+
83
+ // The last emitted { done: true } won't be used as the value
84
+ // by the for await...of syntax.
85
+ if (!row.next) {
86
+ isDone = true;
87
+ return { value, done: false };
88
+ }
89
+ }
90
+
91
+ // there are no further rows to iterate over:
92
+ if (row.next === undefined) {
93
+ return { value, done: true };
94
+ }
95
+
96
+ row = row.next;
97
+
98
+ if (isFirstIteration) {
99
+ isFirstIteration = false; // TODO should this be set for every return?
100
+
101
+ if (value === undefined) {
102
+ // This is the initial chunk and there isn't an initial value yet.
103
+ // Let's skip this one.
104
+ return this.next();
105
+ }
106
+ }
107
+
108
+ return { value, done: false };
109
+ },
110
+ };
111
+ },
112
+ };
113
+ }
@@ -0,0 +1,165 @@
1
+ import { delay } from '@ai-sdk/provider-utils';
2
+ import { createStreamableValue } from './create-streamable-value';
3
+ import { readStreamableValue } from './read-streamable-value';
4
+ import { it, expect } from 'vitest';
5
+
6
+ it('should return an async iterable', () => {
7
+ const streamable = createStreamableValue();
8
+ const result = readStreamableValue(streamable.value);
9
+ streamable.done();
10
+
11
+ expect(result).toBeDefined();
12
+ expect(result[Symbol.asyncIterator]).toBeDefined();
13
+ });
14
+
15
+ it('should support reading streamed values and errors', async () => {
16
+ const streamable = createStreamableValue(1);
17
+
18
+ (async () => {
19
+ await delay();
20
+ streamable.update(2);
21
+ await delay();
22
+ streamable.update(3);
23
+ await delay();
24
+ streamable.error('This is an error');
25
+ })();
26
+
27
+ const values = [];
28
+
29
+ try {
30
+ for await (const v of readStreamableValue(streamable.value)) {
31
+ values.push(v);
32
+ }
33
+ expect.fail('should not be reached');
34
+ } catch (e) {
35
+ expect(e).toStrictEqual('This is an error');
36
+ }
37
+
38
+ expect(values).toStrictEqual([1, 2, 3]);
39
+ });
40
+
41
+ it('should be able to read values asynchronously with different value types', async () => {
42
+ const streamable = createStreamableValue({});
43
+
44
+ (async () => {
45
+ await delay();
46
+ streamable.update([1]);
47
+ streamable.update(['2']);
48
+ streamable.done({ 3: 3 });
49
+ })();
50
+
51
+ const values = [];
52
+ for await (const v of readStreamableValue(streamable.value)) {
53
+ values.push(v);
54
+ }
55
+
56
+ expect(values).toStrictEqual([{}, [1], ['2'], { '3': 3 }]);
57
+ });
58
+
59
+ it('should be able to replay errors', async () => {
60
+ const streamable = createStreamableValue(0);
61
+
62
+ (async () => {
63
+ await delay();
64
+ streamable.update(1);
65
+ streamable.update(2);
66
+ streamable.error({ customErrorMessage: 'this is an error' });
67
+ })();
68
+
69
+ const values = [];
70
+
71
+ try {
72
+ for await (const v of readStreamableValue(streamable.value)) {
73
+ values.push(v);
74
+ }
75
+
76
+ expect.fail('should not be reached');
77
+ } catch (e) {
78
+ expect(e).toStrictEqual({
79
+ customErrorMessage: 'this is an error',
80
+ });
81
+ }
82
+ expect(values).toStrictEqual([0, 1, 2]);
83
+ });
84
+
85
+ it('should be able to append strings as patch', async () => {
86
+ const streamable = createStreamableValue();
87
+ const value = streamable.value;
88
+
89
+ streamable.update('hello');
90
+ streamable.update('hello world');
91
+ streamable.update('hello world!');
92
+ streamable.update('new string');
93
+ streamable.done('new string with patch!');
94
+
95
+ const values = [];
96
+ for await (const v of readStreamableValue(value)) {
97
+ values.push(v);
98
+ }
99
+
100
+ expect(values).toStrictEqual([
101
+ 'hello',
102
+ 'hello world',
103
+ 'hello world!',
104
+ 'new string',
105
+ 'new string with patch!',
106
+ ]);
107
+ });
108
+
109
+ it('should be able to call .append() to send patches', async () => {
110
+ const streamable = createStreamableValue();
111
+ const value = streamable.value;
112
+
113
+ streamable.append('hello');
114
+ streamable.append(' world');
115
+ streamable.append('!');
116
+ streamable.done();
117
+
118
+ const values = [];
119
+ for await (const v of readStreamableValue(value)) {
120
+ values.push(v);
121
+ }
122
+
123
+ expect(values).toStrictEqual(['hello', 'hello world', 'hello world!']);
124
+ });
125
+
126
+ it('should be able to mix .update() and .append() with optimized payloads', async () => {
127
+ const streamable = createStreamableValue('hello');
128
+ const value = streamable.value;
129
+
130
+ streamable.append(' world');
131
+ streamable.update('hello world!!');
132
+ streamable.update('some new');
133
+ streamable.update('some new string');
134
+ streamable.append(' with patch!');
135
+ streamable.done();
136
+
137
+ const values = [];
138
+ for await (const v of readStreamableValue(value)) {
139
+ values.push(v);
140
+ }
141
+
142
+ expect(values).toStrictEqual([
143
+ 'hello',
144
+ 'hello world',
145
+ 'hello world!!',
146
+ 'some new',
147
+ 'some new string',
148
+ 'some new string with patch!',
149
+ ]);
150
+ });
151
+
152
+ it('should behave like .update() with .append() and .done()', async () => {
153
+ const streamable = createStreamableValue('hello');
154
+ const value = streamable.value;
155
+
156
+ streamable.append(' world');
157
+ streamable.done('fin');
158
+
159
+ const values = [];
160
+ for await (const v of readStreamableValue(value)) {
161
+ values.push(v);
162
+ }
163
+
164
+ expect(values).toStrictEqual(['hello', 'hello world', 'fin']);
165
+ });
@@ -0,0 +1,37 @@
1
+ export const STREAMABLE_VALUE_TYPE = Symbol.for('ui.streamable.value');
2
+
3
+ export type StreamablePatch = undefined | [0, string]; // Append string.
4
+
5
+ declare const __internal_curr: unique symbol;
6
+ declare const __internal_error: unique symbol;
7
+
8
+ /**
9
+ * StreamableValue is a value that can be streamed over the network via AI Actions.
10
+ * To read the streamed values, use the `readStreamableValue` or `useStreamableValue` APIs.
11
+ */
12
+ export type StreamableValue<T = any, E = any> = {
13
+ /**
14
+ * @internal Use `readStreamableValue` to read the values.
15
+ */
16
+ type?: typeof STREAMABLE_VALUE_TYPE;
17
+ /**
18
+ * @internal Use `readStreamableValue` to read the values.
19
+ */
20
+ curr?: T;
21
+ /**
22
+ * @internal Use `readStreamableValue` to read the values.
23
+ */
24
+ error?: E;
25
+ /**
26
+ * @internal Use `readStreamableValue` to read the values.
27
+ */
28
+ diff?: StreamablePatch;
29
+ /**
30
+ * @internal Use `readStreamableValue` to read the values.
31
+ */
32
+ next?: Promise<StreamableValue<T, E>>;
33
+
34
+ // branded types to maintain type signature after internal properties are stripped.
35
+ [__internal_curr]?: T;
36
+ [__internal_error]?: E;
37
+ };
@@ -0,0 +1,91 @@
1
+ import { startTransition, useLayoutEffect, useState } from 'react';
2
+ import { readStreamableValue } from './read-streamable-value';
3
+ import { StreamableValue } from './streamable-value';
4
+ import { isStreamableValue } from './is-streamable-value';
5
+
6
+ function checkStreamableValue(value: unknown): value is StreamableValue {
7
+ const hasSignature = isStreamableValue(value);
8
+
9
+ if (!hasSignature && value !== undefined) {
10
+ throw new Error(
11
+ 'Invalid value: this hook only accepts values created via `createStreamableValue`.',
12
+ );
13
+ }
14
+
15
+ return hasSignature;
16
+ }
17
+
18
+ /**
19
+ * `useStreamableValue` is a React hook that takes a streamable value created via the `createStreamableValue().value` API,
20
+ * and returns the current value, error, and pending state.
21
+ *
22
+ * This is useful for consuming streamable values received from a component's props. For example:
23
+ *
24
+ * ```js
25
+ * function MyComponent({ streamableValue }) {
26
+ * const [data, error, pending] = useStreamableValue(streamableValue);
27
+ *
28
+ * if (pending) return <div>Loading...</div>;
29
+ * if (error) return <div>Error: {error.message}</div>;
30
+ *
31
+ * return <div>Data: {data}</div>;
32
+ * }
33
+ * ```
34
+ */
35
+ export function useStreamableValue<T = unknown, Error = unknown>(
36
+ streamableValue?: StreamableValue<T>,
37
+ ): [data: T | undefined, error: Error | undefined, pending: boolean] {
38
+ const [curr, setCurr] = useState<T | undefined>(
39
+ checkStreamableValue(streamableValue) ? streamableValue.curr : undefined,
40
+ );
41
+ const [error, setError] = useState<Error | undefined>(
42
+ checkStreamableValue(streamableValue) ? streamableValue.error : undefined,
43
+ );
44
+ const [pending, setPending] = useState<boolean>(
45
+ checkStreamableValue(streamableValue) ? !!streamableValue.next : false,
46
+ );
47
+
48
+ useLayoutEffect(() => {
49
+ if (!checkStreamableValue(streamableValue)) return;
50
+
51
+ let cancelled = false;
52
+
53
+ const iterator = readStreamableValue(streamableValue);
54
+ if (streamableValue.next) {
55
+ startTransition(() => {
56
+ if (cancelled) return;
57
+ setPending(true);
58
+ });
59
+ }
60
+
61
+ (async () => {
62
+ try {
63
+ for await (const value of iterator) {
64
+ if (cancelled) return;
65
+ startTransition(() => {
66
+ if (cancelled) return;
67
+ setCurr(value);
68
+ });
69
+ }
70
+ } catch (e) {
71
+ if (cancelled) return;
72
+ startTransition(() => {
73
+ if (cancelled) return;
74
+ setError(e as Error);
75
+ });
76
+ } finally {
77
+ if (cancelled) return;
78
+ startTransition(() => {
79
+ if (cancelled) return;
80
+ setPending(false);
81
+ });
82
+ }
83
+ })();
84
+
85
+ return () => {
86
+ cancelled = true;
87
+ };
88
+ }, [streamableValue]);
89
+
90
+ return [curr, error, pending];
91
+ }