1
- import type { ReactNode } from 'react' ;
1
+ import { type ReactNode , isValidElement , ReactElement } from 'react' ;
2
2
import type OpenAI from 'openai' ;
3
3
import { z } from 'zod' ;
4
4
import zodToJsonSchema from 'zod-to-json-schema' ;
@@ -10,12 +10,9 @@ import {
10
10
STREAMABLE_VALUE_TYPE ,
11
11
DEV_DEFAULT_STREAMABLE_WARNING_TIME ,
12
12
} from './constants' ;
13
- import {
14
- createResolvablePromise ,
15
- createSuspensedChunk ,
16
- consumeStream ,
17
- } from './utils' ;
13
+ import { createResolvablePromise , consumeStream } from './utils' ;
18
14
import type { StreamablePatch , StreamableValue } from './types' ;
15
+ import { InternalStreamableUIClient } from './rsc-shared.mjs' ;
19
16
20
17
// It's necessary to define the type manually here, otherwise TypeScript compiler
21
18
// will not be able to infer the correct return type as it's circular.
@@ -68,9 +65,9 @@ type StreamableUIWrapper = {
68
65
* On the client side, it can be rendered as a normal React node.
69
66
*/
70
67
function createStreamableUI ( initialValue ?: React . ReactNode ) {
71
- let currentValue = initialValue ;
68
+ const innerStreamable = createStreamableValue < React . ReactNode > ( initialValue ) ;
69
+
72
70
let closed = false ;
73
- let { row, resolve, reject } = createSuspensedChunk ( initialValue ) ;
74
71
75
72
function assertStream ( method : string ) {
76
73
if ( closed ) {
@@ -94,37 +91,19 @@ function createStreamableUI(initialValue?: React.ReactNode) {
94
91
warnUnclosedStream ( ) ;
95
92
96
93
const streamable : StreamableUIWrapper = {
97
- value : row ,
94
+ value : < InternalStreamableUIClient s = { innerStreamable . value } /> ,
98
95
update ( value : React . ReactNode ) {
99
96
assertStream ( '.update()' ) ;
100
97
101
- // There is no need to update the value if it's referentially equal.
102
- if ( value === currentValue ) {
103
- warnUnclosedStream ( ) ;
104
- return streamable ;
105
- }
106
-
107
- const resolvable = createResolvablePromise ( ) ;
108
- currentValue = value ;
109
-
110
- resolve ( { value : currentValue , done : false , next : resolvable . promise } ) ;
111
- resolve = resolvable . resolve ;
112
- reject = resolvable . reject ;
113
-
98
+ innerStreamable . update ( value ) ;
114
99
warnUnclosedStream ( ) ;
115
100
116
101
return streamable ;
117
102
} ,
118
103
append ( value : React . ReactNode ) {
119
104
assertStream ( '.append()' ) ;
120
105
121
- const resolvable = createResolvablePromise ( ) ;
122
- currentValue = value ;
123
-
124
- resolve ( { value, done : false , append : true , next : resolvable . promise } ) ;
125
- resolve = resolvable . resolve ;
126
- reject = resolvable . reject ;
127
-
106
+ innerStreamable . append ( value ) ;
128
107
warnUnclosedStream ( ) ;
129
108
130
109
return streamable ;
@@ -136,7 +115,7 @@ function createStreamableUI(initialValue?: React.ReactNode) {
136
115
clearTimeout ( warningTimeout ) ;
137
116
}
138
117
closed = true ;
139
- reject ( error ) ;
118
+ innerStreamable . error ( error ) ;
140
119
141
120
return streamable ;
142
121
} ,
@@ -148,11 +127,11 @@ function createStreamableUI(initialValue?: React.ReactNode) {
148
127
}
149
128
closed = true ;
150
129
if ( args . length ) {
151
- resolve ( { value : args [ 0 ] , done : true } ) ;
130
+ innerStreamable . done ( args [ 0 ] ) ;
152
131
return streamable ;
153
132
}
154
- resolve ( { value : currentValue , done : true } ) ;
155
133
134
+ innerStreamable . done ( ) ;
156
135
return streamable ;
157
136
} ,
158
137
} ;
@@ -377,31 +356,45 @@ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
377
356
append ( value : T ) {
378
357
assertStream ( '.append()' ) ;
379
358
380
- if (
381
- typeof currentValue !== 'string' &&
382
- typeof currentValue !== 'undefined'
383
- ) {
359
+ if ( typeof value !== 'string' && ! isValidElement ( value ) ) {
384
360
throw new Error (
385
- `.append(): The current value is not a string . Received: ${ typeof currentValue } ` ,
361
+ `.append(): The value type can't be appended to the stream . Received: ${ typeof value } ` ,
386
362
) ;
387
363
}
388
- if ( typeof value !== 'string' ) {
364
+
365
+ if ( typeof currentValue === 'undefined' ) {
366
+ currentPatchValue = undefined ;
367
+ currentValue = value ;
368
+ } else if ( typeof currentValue === 'string' ) {
369
+ if ( typeof value === 'string' ) {
370
+ currentPatchValue = [ 0 , value ] ;
371
+ ( currentValue as string ) = currentValue + value ;
372
+ } else {
373
+ currentPatchValue = [ 1 , value ] ;
374
+ ( currentValue as unknown as ReactElement ) = (
375
+ < >
376
+ { currentValue }
377
+ { value }
378
+ </ >
379
+ ) ;
380
+ }
381
+ } else if ( isValidElement ( currentValue ) ) {
382
+ currentPatchValue = [ 1 , value ] ;
383
+ ( currentValue as ReactElement ) = (
384
+ < >
385
+ { currentValue }
386
+ { value }
387
+ </ >
388
+ ) ;
389
+ } else {
389
390
throw new Error (
390
- `.append(): The value is not a string. Received : ${ typeof value } ` ,
391
+ `.append(): The current value doesn't support appending data. Type : ${ typeof currentValue } ` ,
391
392
) ;
392
393
}
393
394
394
395
const resolvePrevious = resolvable . resolve ;
395
396
resolvable = createResolvablePromise ( ) ;
396
397
397
- if ( typeof currentValue === 'string' ) {
398
- currentPatchValue = [ 0 , value ] ;
399
- ( currentValue as string ) = currentValue + value ;
400
- } else {
401
- currentPatchValue = undefined ;
402
- currentValue = value ;
403
- }
404
-
405
398
currentPromise = resolvable . promise ;
406
399
resolvePrevious ( createWrapped ( ) ) ;
407
400
0 commit comments