Skip to content

Commit 8439884

Browse files
authored
ai/rsc: Make RSC streamable utils chainable (#1479)
1 parent 3719622 commit 8439884

File tree

3 files changed

+66
-6
lines changed

3 files changed

+66
-6
lines changed

‎.changeset/plenty-onions-invite.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': patch
3+
---
4+
5+
ai/rsc: make RSC streamable utils chainable

‎packages/core/rsc/streamable.tsx‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function createStreamableUI(initialValue?: React.ReactNode) {
4747
}
4848
warnUnclosedStream();
4949

50-
return {
50+
const streamable = {
5151
/**
5252
* The value of the streamable UI. This can be returned from a Server Action and received by the client.
5353
*/
@@ -61,7 +61,7 @@ function createStreamableUI(initialValue?: React.ReactNode) {
6161
// There is no need to update the value if it's referentially equal.
6262
if (value === currentValue) {
6363
warnUnclosedStream();
64-
return;
64+
return streamable;
6565
}
6666

6767
const resolvable = createResolvablePromise();
@@ -72,6 +72,8 @@ function createStreamableUI(initialValue?: React.ReactNode) {
7272
reject = resolvable.reject;
7373

7474
warnUnclosedStream();
75+
76+
return streamable;
7577
},
7678
/**
7779
* This method is used to append a new UI node to the end of the old one.
@@ -100,6 +102,8 @@ function createStreamableUI(initialValue?: React.ReactNode) {
100102
reject = resolvable.reject;
101103

102104
warnUnclosedStream();
105+
106+
return streamable;
103107
},
104108
/**
105109
* This method is used to signal that there is an error in the UI stream.
@@ -113,6 +117,8 @@ function createStreamableUI(initialValue?: React.ReactNode) {
113117
}
114118
closed = true;
115119
reject(error);
120+
121+
return streamable;
116122
},
117123
/**
118124
* This method marks the UI node as finalized. You can either call it without any parameters or with a new UI node as the final state.
@@ -129,11 +135,15 @@ function createStreamableUI(initialValue?: React.ReactNode) {
129135
closed = true;
130136
if (args.length) {
131137
resolve({ value: args[0], done: true });
132-
return;
138+
return streamable;
133139
}
134140
resolve({ value: currentValue, done: true });
141+
142+
return streamable;
135143
},
136144
};
145+
146+
return streamable;
137147
}
138148

139149
const STREAMABLE_VALUE_INTERNAL_LOCK = Symbol('streamable.value.lock');
@@ -276,7 +286,7 @@ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
276286
currentValue = value;
277287
}
278288

279-
return {
289+
const streamable = {
280290
/**
281291
* @internal This is an internal lock to prevent the value from being
282292
* updated by the user.
@@ -306,6 +316,8 @@ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
306316
resolvePrevious(createWrapped());
307317

308318
warnUnclosedStream();
319+
320+
return streamable;
309321
},
310322
/**
311323
* This method is used to append a delta string to the current value. It
@@ -351,6 +363,8 @@ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
351363
resolvePrevious(createWrapped());
352364

353365
warnUnclosedStream();
366+
367+
return streamable;
354368
},
355369
/**
356370
* This method is used to signal that there is an error in the value stream.
@@ -368,6 +382,8 @@ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
368382
currentPromise = undefined;
369383

370384
resolvable.resolve({ error });
385+
386+
return streamable;
371387
},
372388
/**
373389
* This method marks the value as finalized. You can either call it without
@@ -389,12 +405,16 @@ function createStreamableValueImpl<T = any, E = any>(initialValue?: T) {
389405
if (args.length) {
390406
updateValueStates(args[0]);
391407
resolvable.resolve(createWrapped());
392-
return;
408+
return streamable;
393409
}
394410

395411
resolvable.resolve({});
412+
413+
return streamable;
396414
},
397415
};
416+
417+
return streamable;
398418
}
399419

400420
export { createStreamableUI, createStreamableValue };

‎packages/core/rsc/streamable.ui.test.tsx‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import {
33
openaiFunctionCallChunks,
44
} from '../tests/snapshots/openai-chat';
55
import { DEFAULT_TEST_URL, createMockServer } from '../tests/utils/mock-server';
6-
import { createStreamableUI, render } from './streamable';
6+
import {
7+
createStreamableUI,
8+
createStreamableValue,
9+
render,
10+
} from './streamable';
711
import { z } from 'zod';
812

913
const FUNCTION_CALL_TEST_URL = DEFAULT_TEST_URL + 'mock-func-call';
@@ -445,4 +449,35 @@ describe('rsc - createStreamableUI()', () => {
445449
"
446450
`);
447451
});
452+
453+
it('should return self', async () => {
454+
const ui = createStreamableUI(<div>1</div>)
455+
.update(<div>2</div>)
456+
.update(<div>3</div>)
457+
.done(<div>4</div>);
458+
459+
expect(await flightRender(ui.value)).toMatchInlineSnapshot(`
460+
"1:\\"$Sreact.suspense\\"
461+
2:D{\\"name\\":\\"\\",\\"env\\":\\"Server\\"}
462+
0:[\\"$\\",\\"$1\\",null,{\\"fallback\\":[\\"$\\",\\"div\\",null,{\\"children\\":\\"1\\"}],\\"children\\":\\"$L2\\"}]
463+
3:D{\\"name\\":\\"\\",\\"env\\":\\"Server\\"}
464+
2:[\\"$\\",\\"$1\\",null,{\\"fallback\\":[\\"$\\",\\"div\\",null,{\\"children\\":\\"2\\"}],\\"children\\":\\"$L3\\"}]
465+
4:D{\\"name\\":\\"\\",\\"env\\":\\"Server\\"}
466+
3:[\\"$\\",\\"$1\\",null,{\\"fallback\\":[\\"$\\",\\"div\\",null,{\\"children\\":\\"3\\"}],\\"children\\":\\"$L4\\"}]
467+
4:[\\"$\\",\\"div\\",null,{\\"children\\":\\"4\\"}]
468+
"
469+
`);
470+
});
471+
});
472+
473+
describe('rsc - createStreamableValue()', () => {
474+
it('should return self', async () => {
475+
const value = createStreamableValue(1).update(2).update(3).done(4);
476+
expect(value.value).toMatchInlineSnapshot(`
477+
{
478+
"curr": 4,
479+
"type": Symbol(ui.streamable.value),
480+
}
481+
`);
482+
});
448483
});

0 commit comments

Comments
 (0)