ai 6.0.295 → 6.0.297
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/CHANGELOG.md +22 -0
- package/dist/index.d.mts +42 -11
- package/dist/index.d.ts +42 -11
- package/dist/index.js +210 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +210 -43
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.mjs +1 -1
- package/docs/04-ai-sdk-ui/02-chatbot.mdx +10 -1
- package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +8 -0
- package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +10 -3
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +10 -1
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +3 -3
- package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +3 -3
- package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +7 -0
- package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +7 -0
- package/package.json +2 -2
- package/src/agent/create-agent-ui-stream-response.ts +5 -2
- package/src/agent/create-agent-ui-stream.ts +5 -3
- package/src/agent/pipe-agent-ui-stream-to-response.ts +5 -2
- package/src/embed/embed-many.ts +5 -3
- package/src/generate-image/generate-image.ts +7 -2
- package/src/generate-text/smooth-stream.ts +24 -9
- package/src/generate-text/stream-text-result.ts +5 -0
- package/src/generate-text/stream-text.ts +4 -1
- package/src/generate-video/generate-video.ts +6 -4
- package/src/registry/provider-registry.ts +1 -1
- package/src/ui/chat.ts +13 -4
- package/src/ui/http-chat-transport.ts +27 -1
- package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +15 -3
- package/src/ui/process-ui-message-stream.ts +59 -18
- package/src/ui/ui-messages.ts +14 -0
- package/src/ui/validate-ui-messages.ts +2 -0
- package/src/ui-message-stream/create-sse-stream-with-keep-alive.ts +89 -0
- package/src/ui-message-stream/create-ui-message-stream-response.ts +7 -1
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +7 -1
- package/src/ui-message-stream/ui-message-stream-response-init.ts +7 -0
- package/src/util/get-own.ts +18 -0
- package/src/util/set-own.ts +16 -0
|
@@ -60,22 +60,55 @@ export function createStreamingUIMessageState<UI_MESSAGE extends UIMessage>({
|
|
|
60
60
|
lastMessage: UI_MESSAGE | undefined;
|
|
61
61
|
messageId: string;
|
|
62
62
|
}): StreamingUIMessageState<UI_MESSAGE> {
|
|
63
|
+
const message =
|
|
64
|
+
lastMessage?.role === 'assistant'
|
|
65
|
+
? lastMessage
|
|
66
|
+
: ({
|
|
67
|
+
id: messageId,
|
|
68
|
+
metadata: undefined,
|
|
69
|
+
role: 'assistant',
|
|
70
|
+
parts: [] as UIMessagePart<
|
|
71
|
+
InferUIMessageData<UI_MESSAGE>,
|
|
72
|
+
InferUIMessageTools<UI_MESSAGE>
|
|
73
|
+
>[],
|
|
74
|
+
} as UI_MESSAGE);
|
|
75
|
+
const partialToolCalls: StreamingUIMessageState<UI_MESSAGE>['partialToolCalls'] =
|
|
76
|
+
createIdMap();
|
|
77
|
+
const lastStepStartIndex = message.parts.findLastIndex(
|
|
78
|
+
part => part.type === 'step-start',
|
|
79
|
+
);
|
|
80
|
+
let staticToolIndex = 0;
|
|
81
|
+
|
|
82
|
+
for (const part of message.parts.slice(lastStepStartIndex + 1)) {
|
|
83
|
+
if (!isToolUIPart(part)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const index = staticToolIndex;
|
|
88
|
+
if (isStaticToolUIPart(part)) {
|
|
89
|
+
staticToolIndex++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (part.state !== 'input-streaming') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
partialToolCalls[part.toolCallId] = {
|
|
97
|
+
text: part.rawInput ?? '',
|
|
98
|
+
index,
|
|
99
|
+
toolName:
|
|
100
|
+
part.type === 'dynamic-tool' ? part.toolName : getStaticToolName(part),
|
|
101
|
+
dynamic: part.type === 'dynamic-tool',
|
|
102
|
+
title: part.title,
|
|
103
|
+
toolMetadata: part.toolMetadata,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
63
107
|
return {
|
|
64
|
-
message
|
|
65
|
-
lastMessage?.role === 'assistant'
|
|
66
|
-
? lastMessage
|
|
67
|
-
: ({
|
|
68
|
-
id: messageId,
|
|
69
|
-
metadata: undefined,
|
|
70
|
-
role: 'assistant',
|
|
71
|
-
parts: [] as UIMessagePart<
|
|
72
|
-
InferUIMessageData<UI_MESSAGE>,
|
|
73
|
-
InferUIMessageTools<UI_MESSAGE>
|
|
74
|
-
>[],
|
|
75
|
-
} as UI_MESSAGE),
|
|
108
|
+
message,
|
|
76
109
|
activeTextParts: createIdMap(),
|
|
77
110
|
activeReasoningParts: createIdMap(),
|
|
78
|
-
partialToolCalls
|
|
111
|
+
partialToolCalls,
|
|
79
112
|
};
|
|
80
113
|
}
|
|
81
114
|
|
|
@@ -167,6 +200,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
167
200
|
| {
|
|
168
201
|
state: 'input-streaming';
|
|
169
202
|
input: unknown;
|
|
203
|
+
rawInput?: string;
|
|
170
204
|
providerExecuted?: boolean;
|
|
171
205
|
providerMetadata?: ProviderMetadata;
|
|
172
206
|
}
|
|
@@ -282,6 +316,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
282
316
|
| {
|
|
283
317
|
state: 'input-streaming';
|
|
284
318
|
input: unknown;
|
|
319
|
+
rawInput?: string;
|
|
285
320
|
providerMetadata?: ProviderMetadata;
|
|
286
321
|
}
|
|
287
322
|
| {
|
|
@@ -322,7 +357,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
322
357
|
anyPart.input = anyOptions.input;
|
|
323
358
|
anyPart.output = anyOptions.output;
|
|
324
359
|
anyPart.errorText = anyOptions.errorText;
|
|
325
|
-
anyPart.rawInput = anyOptions.rawInput
|
|
360
|
+
anyPart.rawInput = anyOptions.rawInput;
|
|
326
361
|
anyPart.preliminary = anyOptions.preliminary;
|
|
327
362
|
if (options.title !== undefined) {
|
|
328
363
|
anyPart.title = options.title;
|
|
@@ -615,6 +650,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
615
650
|
toolName: partialToolCall.toolName,
|
|
616
651
|
state: 'input-streaming',
|
|
617
652
|
input: partialArgs,
|
|
653
|
+
rawInput: partialToolCall.text,
|
|
618
654
|
title: partialToolCall.title,
|
|
619
655
|
toolMetadata: partialToolCall.toolMetadata,
|
|
620
656
|
});
|
|
@@ -624,6 +660,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
624
660
|
toolName: partialToolCall.toolName,
|
|
625
661
|
state: 'input-streaming',
|
|
626
662
|
input: partialArgs,
|
|
663
|
+
rawInput: partialToolCall.text,
|
|
627
664
|
title: partialToolCall.title,
|
|
628
665
|
toolMetadata: partialToolCall.toolMetadata,
|
|
629
666
|
});
|
|
@@ -757,7 +794,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
757
794
|
providerExecuted: chunk.providerExecuted,
|
|
758
795
|
providerMetadata: chunk.providerMetadata,
|
|
759
796
|
title: toolInvocation.title,
|
|
760
|
-
toolMetadata:
|
|
797
|
+
toolMetadata:
|
|
798
|
+
chunk.toolMetadata ?? toolInvocation.toolMetadata,
|
|
761
799
|
},
|
|
762
800
|
toolInvocation,
|
|
763
801
|
);
|
|
@@ -773,7 +811,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
773
811
|
preliminary: chunk.preliminary,
|
|
774
812
|
providerMetadata: chunk.providerMetadata,
|
|
775
813
|
title: toolInvocation.title,
|
|
776
|
-
toolMetadata:
|
|
814
|
+
toolMetadata:
|
|
815
|
+
chunk.toolMetadata ?? toolInvocation.toolMetadata,
|
|
777
816
|
},
|
|
778
817
|
toolInvocation as ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
|
|
779
818
|
);
|
|
@@ -797,7 +836,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
797
836
|
providerExecuted: chunk.providerExecuted,
|
|
798
837
|
providerMetadata: chunk.providerMetadata,
|
|
799
838
|
title: toolInvocation.title,
|
|
800
|
-
toolMetadata:
|
|
839
|
+
toolMetadata:
|
|
840
|
+
chunk.toolMetadata ?? toolInvocation.toolMetadata,
|
|
801
841
|
},
|
|
802
842
|
toolInvocation,
|
|
803
843
|
);
|
|
@@ -813,7 +853,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
813
853
|
providerExecuted: chunk.providerExecuted,
|
|
814
854
|
providerMetadata: chunk.providerMetadata,
|
|
815
855
|
title: toolInvocation.title,
|
|
816
|
-
toolMetadata:
|
|
856
|
+
toolMetadata:
|
|
857
|
+
chunk.toolMetadata ?? toolInvocation.toolMetadata,
|
|
817
858
|
},
|
|
818
859
|
toolInvocation as ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
|
|
819
860
|
);
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -238,6 +238,13 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
238
238
|
| {
|
|
239
239
|
state: 'input-streaming';
|
|
240
240
|
input?: DeepPartial<asUITool<TOOL>['input']> | undefined;
|
|
241
|
+
/**
|
|
242
|
+
* The raw tool input text received so far.
|
|
243
|
+
*
|
|
244
|
+
* This is used to continue input streaming when a message is persisted
|
|
245
|
+
* and later resumed.
|
|
246
|
+
*/
|
|
247
|
+
rawInput?: string;
|
|
241
248
|
output?: never;
|
|
242
249
|
errorText?: never;
|
|
243
250
|
callProviderMetadata?: ProviderMetadata;
|
|
@@ -361,6 +368,13 @@ export type DynamicToolUIPart = {
|
|
|
361
368
|
| {
|
|
362
369
|
state: 'input-streaming';
|
|
363
370
|
input?: unknown;
|
|
371
|
+
/**
|
|
372
|
+
* The raw tool input text received so far.
|
|
373
|
+
*
|
|
374
|
+
* This is used to continue input streaming when a message is persisted
|
|
375
|
+
* and later resumed.
|
|
376
|
+
*/
|
|
377
|
+
rawInput?: string;
|
|
364
378
|
output?: never;
|
|
365
379
|
errorText?: never;
|
|
366
380
|
callProviderMetadata?: ProviderMetadata;
|
|
@@ -139,6 +139,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
139
139
|
toolMetadata: toolMetadataSchema.optional(),
|
|
140
140
|
state: z.literal('input-streaming'),
|
|
141
141
|
input: z.unknown().optional(),
|
|
142
|
+
rawInput: z.string().optional(),
|
|
142
143
|
providerExecuted: z.boolean().optional(),
|
|
143
144
|
callProviderMetadata: providerMetadataSchema.optional(),
|
|
144
145
|
output: z.never().optional(),
|
|
@@ -235,6 +236,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
235
236
|
providerExecuted: z.boolean().optional(),
|
|
236
237
|
callProviderMetadata: providerMetadataSchema.optional(),
|
|
237
238
|
input: z.unknown().optional(),
|
|
239
|
+
rawInput: z.string().optional(),
|
|
238
240
|
output: z.never().optional(),
|
|
239
241
|
errorText: z.never().optional(),
|
|
240
242
|
approval: z.never().optional(),
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const STREAM_OPEN_COMMENT = ': stream-open\n\n';
|
|
2
|
+
const KEEP_ALIVE_COMMENT = ': keep-alive\n\n';
|
|
3
|
+
|
|
4
|
+
export function createSseStreamWithKeepAlive({
|
|
5
|
+
stream,
|
|
6
|
+
keepAliveMs,
|
|
7
|
+
}: {
|
|
8
|
+
stream: ReadableStream<string>;
|
|
9
|
+
keepAliveMs: number | undefined;
|
|
10
|
+
}): ReadableStream<string> {
|
|
11
|
+
if (keepAliveMs == null) {
|
|
12
|
+
return stream;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
!Number.isFinite(keepAliveMs) ||
|
|
17
|
+
keepAliveMs <= 0 ||
|
|
18
|
+
keepAliveMs > 2_147_483_647
|
|
19
|
+
) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'keepAliveMs must be a positive finite timer duration no greater than 2147483647',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const reader = stream.getReader();
|
|
26
|
+
let keepAliveTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
27
|
+
let isCancelled = false;
|
|
28
|
+
|
|
29
|
+
const clearKeepAliveTimeout = () => {
|
|
30
|
+
clearTimeout(keepAliveTimeout);
|
|
31
|
+
keepAliveTimeout = undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const scheduleKeepAlive = (
|
|
35
|
+
controller: ReadableStreamDefaultController<string>,
|
|
36
|
+
) => {
|
|
37
|
+
clearKeepAliveTimeout();
|
|
38
|
+
keepAliveTimeout = setTimeout(() => {
|
|
39
|
+
if (isCancelled) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (controller.desiredSize != null && controller.desiredSize > 0) {
|
|
44
|
+
controller.enqueue(KEEP_ALIVE_COMMENT);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
scheduleKeepAlive(controller);
|
|
48
|
+
}, keepAliveMs);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return new ReadableStream<string>({
|
|
52
|
+
start(controller) {
|
|
53
|
+
controller.enqueue(STREAM_OPEN_COMMENT);
|
|
54
|
+
scheduleKeepAlive(controller);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
pull(controller) {
|
|
58
|
+
return reader.read().then(
|
|
59
|
+
result => {
|
|
60
|
+
clearKeepAliveTimeout();
|
|
61
|
+
|
|
62
|
+
if (isCancelled) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (result.done) {
|
|
67
|
+
controller.close();
|
|
68
|
+
} else {
|
|
69
|
+
controller.enqueue(result.value);
|
|
70
|
+
scheduleKeepAlive(controller);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
error => {
|
|
74
|
+
clearKeepAliveTimeout();
|
|
75
|
+
|
|
76
|
+
if (!isCancelled) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async cancel(reason) {
|
|
84
|
+
isCancelled = true;
|
|
85
|
+
clearKeepAliveTimeout();
|
|
86
|
+
await reader.cancel(reason);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareHeaders } from '../util/prepare-headers';
|
|
2
|
+
import { createSseStreamWithKeepAlive } from './create-sse-stream-with-keep-alive';
|
|
2
3
|
import { JsonToSseTransformStream } from './json-to-sse-transform-stream';
|
|
3
4
|
import { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
4
5
|
import type { UIMessageChunk } from './ui-message-chunks';
|
|
@@ -12,6 +13,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
|
|
|
12
13
|
* @param options.statusText - The HTTP status text for the response.
|
|
13
14
|
* @param options.headers - Additional HTTP headers to include in the response.
|
|
14
15
|
* @param options.stream - The UI message chunk stream to send.
|
|
16
|
+
* @param options.keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
15
17
|
* @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
|
|
16
18
|
*
|
|
17
19
|
* @returns A `Response` object with the UI message stream as the body.
|
|
@@ -21,11 +23,15 @@ export function createUIMessageStreamResponse({
|
|
|
21
23
|
statusText,
|
|
22
24
|
headers,
|
|
23
25
|
stream,
|
|
26
|
+
keepAliveMs,
|
|
24
27
|
consumeSseStream,
|
|
25
28
|
}: UIMessageStreamResponseInit & {
|
|
26
29
|
stream: ReadableStream<UIMessageChunk>;
|
|
27
30
|
}): Response {
|
|
28
|
-
let sseStream =
|
|
31
|
+
let sseStream = createSseStreamWithKeepAlive({
|
|
32
|
+
stream: stream.pipeThrough(new JsonToSseTransformStream()),
|
|
33
|
+
keepAliveMs,
|
|
34
|
+
});
|
|
29
35
|
|
|
30
36
|
// when the consumeSseStream is provided, we need to tee the stream
|
|
31
37
|
// and send the second part to the consumeSseStream function
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ServerResponse } from 'node:http';
|
|
2
2
|
import { prepareHeaders } from '../util/prepare-headers';
|
|
3
3
|
import { writeToServerResponse } from '../util/write-to-server-response';
|
|
4
|
+
import { createSseStreamWithKeepAlive } from './create-sse-stream-with-keep-alive';
|
|
4
5
|
import { JsonToSseTransformStream } from './json-to-sse-transform-stream';
|
|
5
6
|
import { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
6
7
|
import type { UIMessageChunk } from './ui-message-chunks';
|
|
@@ -15,6 +16,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
|
|
|
15
16
|
* @param options.statusText - The HTTP status text for the response.
|
|
16
17
|
* @param options.headers - Additional HTTP headers to include in the response.
|
|
17
18
|
* @param options.stream - The UI message chunk stream to send.
|
|
19
|
+
* @param options.keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
18
20
|
* @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
|
|
19
21
|
* @returns A promise that resolves when the stream has been written.
|
|
20
22
|
*/
|
|
@@ -24,12 +26,16 @@ export function pipeUIMessageStreamToResponse({
|
|
|
24
26
|
statusText,
|
|
25
27
|
headers,
|
|
26
28
|
stream,
|
|
29
|
+
keepAliveMs,
|
|
27
30
|
consumeSseStream,
|
|
28
31
|
}: {
|
|
29
32
|
response: ServerResponse;
|
|
30
33
|
stream: ReadableStream<UIMessageChunk>;
|
|
31
34
|
} & UIMessageStreamResponseInit): Promise<void> {
|
|
32
|
-
let sseStream =
|
|
35
|
+
let sseStream = createSseStreamWithKeepAlive({
|
|
36
|
+
stream: stream.pipeThrough(new JsonToSseTransformStream()),
|
|
37
|
+
keepAliveMs,
|
|
38
|
+
});
|
|
33
39
|
|
|
34
40
|
// when the consumeSseStream is provided, we need to tee the stream
|
|
35
41
|
// and send the second part to the consumeSseStream function
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Extends the standard `ResponseInit` with additional streaming options.
|
|
4
4
|
*/
|
|
5
5
|
export type UIMessageStreamResponseInit = ResponseInit & {
|
|
6
|
+
/**
|
|
7
|
+
* Optional interval in milliseconds for sending SSE keep-alive comments.
|
|
8
|
+
* When set, an opening comment is sent immediately and additional comments
|
|
9
|
+
* are sent after the stream has been idle for the configured interval.
|
|
10
|
+
*/
|
|
11
|
+
keepAliveMs?: number;
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
14
|
* Optional callback to consume a copy of the SSE stream independently.
|
|
8
15
|
* This is useful for logging, debugging, or processing the stream in parallel.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a property by an untrusted key, ignoring inherited prototype members.
|
|
3
|
+
*
|
|
4
|
+
* Tool sets, tool contexts, and similar lookup objects are indexed by names
|
|
5
|
+
* that can come from model output or client-supplied message history. Plain
|
|
6
|
+
* bracket access (`obj[name]`) resolves names such as `constructor`,
|
|
7
|
+
* `toString`, or `__proto__` to values on `Object.prototype`, which would slip
|
|
8
|
+
* past the `== null` / `!value` guards that treat an unknown name as "not
|
|
9
|
+
* present". This helper returns `undefined` unless `key` is an own property.
|
|
10
|
+
*/
|
|
11
|
+
export function getOwn<T extends object>(
|
|
12
|
+
obj: T | undefined | null,
|
|
13
|
+
key: string,
|
|
14
|
+
): T[keyof T] | undefined {
|
|
15
|
+
return obj != null && Object.hasOwn(obj, key)
|
|
16
|
+
? obj[key as keyof T]
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes an enumerable own property without invoking inherited setters such as
|
|
3
|
+
* `Object.prototype.__proto__`. Keeps the target object's prototype unchanged.
|
|
4
|
+
*/
|
|
5
|
+
export function setOwn<T>(
|
|
6
|
+
object: Record<string, T>,
|
|
7
|
+
key: string,
|
|
8
|
+
value: T,
|
|
9
|
+
): void {
|
|
10
|
+
Object.defineProperty(object, key, {
|
|
11
|
+
value,
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
});
|
|
16
|
+
}
|