@tanstack/react-start-rsc 0.0.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/dist/esm/ClientSlot.js +19 -0
- package/dist/esm/ClientSlot.js.map +1 -0
- package/dist/esm/CompositeComponent.js +93 -0
- package/dist/esm/CompositeComponent.js.map +1 -0
- package/dist/esm/ReplayableStream.js +147 -0
- package/dist/esm/ReplayableStream.js.map +1 -0
- package/dist/esm/RscNodeRenderer.js +46 -0
- package/dist/esm/RscNodeRenderer.js.map +1 -0
- package/dist/esm/ServerComponentTypes.js +22 -0
- package/dist/esm/ServerComponentTypes.js.map +1 -0
- package/dist/esm/SlotContext.js +30 -0
- package/dist/esm/SlotContext.js.map +1 -0
- package/dist/esm/awaitLazyElements.js +41 -0
- package/dist/esm/awaitLazyElements.js.map +1 -0
- package/dist/esm/createCompositeComponent.js +205 -0
- package/dist/esm/createCompositeComponent.js.map +1 -0
- package/dist/esm/createCompositeComponent.stub.js +15 -0
- package/dist/esm/createCompositeComponent.stub.js.map +1 -0
- package/dist/esm/createRscProxy.js +138 -0
- package/dist/esm/createRscProxy.js.map +1 -0
- package/dist/esm/createServerComponentFromStream.js +74 -0
- package/dist/esm/createServerComponentFromStream.js.map +1 -0
- package/dist/esm/entry/rsc.js +21 -0
- package/dist/esm/entry/rsc.js.map +1 -0
- package/dist/esm/flight.js +56 -0
- package/dist/esm/flight.js.map +1 -0
- package/dist/esm/flight.rsc.js +2 -0
- package/dist/esm/flight.stub.js +15 -0
- package/dist/esm/flight.stub.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.rsc.js +6 -0
- package/dist/esm/plugin/vite.js +172 -0
- package/dist/esm/plugin/vite.js.map +1 -0
- package/dist/esm/reactSymbols.js +8 -0
- package/dist/esm/reactSymbols.js.map +1 -0
- package/dist/esm/renderServerComponent.js +58 -0
- package/dist/esm/renderServerComponent.js.map +1 -0
- package/dist/esm/renderServerComponent.stub.js +16 -0
- package/dist/esm/renderServerComponent.stub.js.map +1 -0
- package/dist/esm/serialization.client.js +21 -0
- package/dist/esm/serialization.client.js.map +1 -0
- package/dist/esm/serialization.server.js +121 -0
- package/dist/esm/serialization.server.js.map +1 -0
- package/dist/esm/slotUsageSanitizer.js +33 -0
- package/dist/esm/slotUsageSanitizer.js.map +1 -0
- package/dist/esm/src/ClientSlot.d.ts +5 -0
- package/dist/esm/src/CompositeComponent.d.ts +28 -0
- package/dist/esm/src/ReplayableStream.d.ts +76 -0
- package/dist/esm/src/RscNodeRenderer.d.ts +7 -0
- package/dist/esm/src/ServerComponentTypes.d.ts +99 -0
- package/dist/esm/src/SlotContext.d.ts +21 -0
- package/dist/esm/src/awaitLazyElements.d.ts +17 -0
- package/dist/esm/src/createCompositeComponent.d.ts +32 -0
- package/dist/esm/src/createCompositeComponent.stub.d.ts +9 -0
- package/dist/esm/src/createRscProxy.d.ts +18 -0
- package/dist/esm/src/createServerComponentFromStream.d.ts +24 -0
- package/dist/esm/src/entry/rsc.d.ts +7 -0
- package/dist/esm/src/flight.d.ts +41 -0
- package/dist/esm/src/flight.rsc.d.ts +17 -0
- package/dist/esm/src/flight.stub.d.ts +8 -0
- package/dist/esm/src/index.d.ts +7 -0
- package/dist/esm/src/index.rsc.d.ts +6 -0
- package/dist/esm/src/plugin/vite.d.ts +9 -0
- package/dist/esm/src/reactSymbols.d.ts +3 -0
- package/dist/esm/src/renderServerComponent.d.ts +33 -0
- package/dist/esm/src/renderServerComponent.stub.d.ts +9 -0
- package/dist/esm/src/rscSsrHandler.d.ts +24 -0
- package/dist/esm/src/serialization.client.d.ts +11 -0
- package/dist/esm/src/serialization.server.d.ts +10 -0
- package/dist/esm/src/slotUsageSanitizer.d.ts +1 -0
- package/dist/esm/src/types.d.ts +13 -0
- package/dist/plugin/entry/rsc.tsx +23 -0
- package/package.json +108 -0
- package/src/ClientSlot.tsx +34 -0
- package/src/CompositeComponent.tsx +165 -0
- package/src/ReplayableStream.ts +249 -0
- package/src/RscNodeRenderer.tsx +76 -0
- package/src/ServerComponentTypes.ts +226 -0
- package/src/SlotContext.tsx +42 -0
- package/src/awaitLazyElements.ts +91 -0
- package/src/createCompositeComponent.stub.ts +20 -0
- package/src/createCompositeComponent.ts +338 -0
- package/src/createRscProxy.tsx +294 -0
- package/src/createServerComponentFromStream.ts +105 -0
- package/src/entry/rsc.tsx +23 -0
- package/src/entry/virtual-modules.d.ts +12 -0
- package/src/flight.rsc.ts +17 -0
- package/src/flight.stub.ts +15 -0
- package/src/flight.ts +68 -0
- package/src/global.d.ts +75 -0
- package/src/index.rsc.ts +25 -0
- package/src/index.ts +26 -0
- package/src/plugin/vite.ts +241 -0
- package/src/reactSymbols.ts +6 -0
- package/src/renderServerComponent.stub.ts +26 -0
- package/src/renderServerComponent.ts +110 -0
- package/src/rscSsrHandler.ts +39 -0
- package/src/serialization.client.ts +43 -0
- package/src/serialization.server.ts +193 -0
- package/src/slotUsageSanitizer.ts +62 -0
- package/src/types.ts +15 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, use } from 'react'
|
|
4
|
+
import type { SlotImplementations } from './types'
|
|
5
|
+
|
|
6
|
+
export interface SlotContextValue {
|
|
7
|
+
implementations: SlotImplementations
|
|
8
|
+
strict: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SlotContext = createContext<SlotContextValue | null>(null)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to access slot implementations from within ClientSlot.
|
|
15
|
+
*/
|
|
16
|
+
export function useSlotContext(): SlotContextValue | null {
|
|
17
|
+
return use(SlotContext)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SlotProviderProps {
|
|
21
|
+
implementations: SlotImplementations
|
|
22
|
+
strict?: boolean
|
|
23
|
+
children?: React.ReactNode
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* SlotProvider - makes slot implementations available to ClientSlot components.
|
|
28
|
+
*
|
|
29
|
+
* Must wrap the decoded RSC content so that ClientSlot components can
|
|
30
|
+
* access their slot implementations via React Context.
|
|
31
|
+
*/
|
|
32
|
+
export function SlotProvider({
|
|
33
|
+
implementations,
|
|
34
|
+
strict,
|
|
35
|
+
children,
|
|
36
|
+
}: SlotProviderProps) {
|
|
37
|
+
return (
|
|
38
|
+
<SlotContext value={{ implementations, strict: strict ?? false }}>
|
|
39
|
+
{children}
|
|
40
|
+
</SlotContext>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ReactElement, ReactLazy, ReactSuspense } from './reactSymbols'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Optional callback for collecting CSS hrefs during tree traversal.
|
|
5
|
+
* Only called server-side when processing <link rel="stylesheet" data-rsc-css-href>
|
|
6
|
+
*/
|
|
7
|
+
export type CssHrefCollector = (href: string) => void
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Yields pending lazy element payloads from a tree, stopping at Suspense boundaries.
|
|
11
|
+
* Also collects CSS hrefs from <link rel="stylesheet" data-rsc-css-href> elements.
|
|
12
|
+
*/
|
|
13
|
+
function* findPendingLazyPayloads(
|
|
14
|
+
obj: unknown,
|
|
15
|
+
seen = new Set(),
|
|
16
|
+
cssCollector?: CssHrefCollector,
|
|
17
|
+
): Generator<PromiseLike<unknown>> {
|
|
18
|
+
if (!obj || typeof obj !== 'object') return
|
|
19
|
+
if (seen.has(obj)) return
|
|
20
|
+
seen.add(obj)
|
|
21
|
+
|
|
22
|
+
const el = obj as any
|
|
23
|
+
|
|
24
|
+
// Stop at Suspense boundaries - lazy elements inside are intentionally deferred
|
|
25
|
+
if (el.$$typeof === ReactElement && el.type === ReactSuspense) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Collect CSS hrefs from <link rel="stylesheet" data-rsc-css-href>
|
|
30
|
+
// The active RSC bundler adapter injects these for CSS module imports
|
|
31
|
+
if (
|
|
32
|
+
el.$$typeof === ReactElement &&
|
|
33
|
+
el.type === 'link' &&
|
|
34
|
+
el.props?.rel === 'stylesheet'
|
|
35
|
+
) {
|
|
36
|
+
const cssHref = el.props['data-rsc-css-href'] as string | undefined
|
|
37
|
+
if (cssHref && cssCollector) {
|
|
38
|
+
cssCollector(cssHref)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Yield pending lazy element payload
|
|
43
|
+
if (el.$$typeof === ReactLazy) {
|
|
44
|
+
const payload = el._payload
|
|
45
|
+
if (
|
|
46
|
+
payload &&
|
|
47
|
+
typeof payload === 'object' &&
|
|
48
|
+
(payload.status === 'pending' || payload.status === 'blocked') &&
|
|
49
|
+
typeof payload.then === 'function'
|
|
50
|
+
) {
|
|
51
|
+
yield payload
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recurse into children
|
|
56
|
+
if (Array.isArray(obj)) {
|
|
57
|
+
for (const item of obj) {
|
|
58
|
+
yield* findPendingLazyPayloads(item, seen, cssCollector)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
for (const key of Object.keys(obj)) {
|
|
62
|
+
if (key !== '_owner' && key !== '_store') {
|
|
63
|
+
yield* findPendingLazyPayloads(el[key], seen, cssCollector)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Wait for all lazy elements in a tree to be resolved.
|
|
71
|
+
* This ensures client component chunks are fully loaded before rendering,
|
|
72
|
+
* preventing Suspense boundaries from flashing during SWR navigation.
|
|
73
|
+
*
|
|
74
|
+
* Also collects CSS hrefs from <link rel="stylesheet" data-rsc-css-href>
|
|
75
|
+
* elements for preloading in <head>.
|
|
76
|
+
*
|
|
77
|
+
* @param tree - The tree to process
|
|
78
|
+
* @param cssCollector - Optional callback to collect CSS hrefs (server-only)
|
|
79
|
+
*/
|
|
80
|
+
export async function awaitLazyElements(
|
|
81
|
+
tree: unknown,
|
|
82
|
+
cssCollector?: CssHrefCollector,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
for (const payload of findPendingLazyPayloads(
|
|
85
|
+
tree,
|
|
86
|
+
new Set(),
|
|
87
|
+
cssCollector,
|
|
88
|
+
)) {
|
|
89
|
+
await Promise.resolve(payload).catch(() => {})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CompositeComponentResult,
|
|
3
|
+
ValidateCompositeComponent,
|
|
4
|
+
} from './ServerComponentTypes'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Client stub for createCompositeComponent.
|
|
8
|
+
*
|
|
9
|
+
* This function should never be called at runtime on the client.
|
|
10
|
+
* It exists only to satisfy bundler imports in client bundles.
|
|
11
|
+
* The real implementation only runs inside server functions.
|
|
12
|
+
*/
|
|
13
|
+
export function createCompositeComponent<TComp>(
|
|
14
|
+
_component: ValidateCompositeComponent<TComp>,
|
|
15
|
+
): Promise<CompositeComponentResult<TComp>> {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'createCompositeComponent cannot be called on the client. ' +
|
|
18
|
+
'This function should only be called inside a server function or route loader.',
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { createElement } from 'react'
|
|
2
|
+
import { renderToReadableStream } from 'virtual:tanstack-rsc-runtime'
|
|
3
|
+
import { getRequest } from '@tanstack/start-server-core'
|
|
4
|
+
import { getStartContext } from '@tanstack/start-storage-context'
|
|
5
|
+
import { sanitizeSlotArgs } from './slotUsageSanitizer'
|
|
6
|
+
|
|
7
|
+
import { ReplayableStream } from './ReplayableStream'
|
|
8
|
+
import { ClientSlot } from './ClientSlot'
|
|
9
|
+
import {
|
|
10
|
+
RSC_SLOT_USAGES_STREAM,
|
|
11
|
+
SERVER_COMPONENT_STREAM,
|
|
12
|
+
} from './ServerComponentTypes'
|
|
13
|
+
import type {
|
|
14
|
+
AnyCompositeComponent,
|
|
15
|
+
CompositeComponentResult,
|
|
16
|
+
RscSlotUsageEvent,
|
|
17
|
+
ServerComponentStream,
|
|
18
|
+
ValidateCompositeComponent,
|
|
19
|
+
} from './ServerComponentTypes'
|
|
20
|
+
|
|
21
|
+
import './rscSsrHandler' // Import for global declaration side effect
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a composite server component with slot support.
|
|
25
|
+
*
|
|
26
|
+
* Supports returning:
|
|
27
|
+
* - A ReactNode directly
|
|
28
|
+
* - An object structure with ReactNodes: accessed as `src.Foo`
|
|
29
|
+
* - Nested structures: accessed as `src.x.Bar`
|
|
30
|
+
*
|
|
31
|
+
* Props that are functions become slots - they render as ClientSlot placeholders
|
|
32
|
+
* in the RSC output, filled in by the consumer with actual implementations.
|
|
33
|
+
*
|
|
34
|
+
* The returned value is NOT directly renderable. Use `<CompositeComponent src={...} />`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const src = await createCompositeComponent((props) => (
|
|
39
|
+
* <div>
|
|
40
|
+
* <header>{props.header('Dashboard')}</header>
|
|
41
|
+
* <main>{props.children}</main>
|
|
42
|
+
* </div>
|
|
43
|
+
* ))
|
|
44
|
+
*
|
|
45
|
+
* // In route component
|
|
46
|
+
* return (
|
|
47
|
+
* <CompositeComponent src={src} header={(title) => <h1>{title}</h1>}>
|
|
48
|
+
* <p>Main content</p>
|
|
49
|
+
* </CompositeComponent>
|
|
50
|
+
* )
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export async function createCompositeComponent<TComp>(
|
|
54
|
+
component: ValidateCompositeComponent<TComp>,
|
|
55
|
+
): Promise<CompositeComponentResult<TComp>> {
|
|
56
|
+
const isDev = process.env.NODE_ENV === 'development'
|
|
57
|
+
|
|
58
|
+
// Dev-only: stream slot usage events (slot + raw args)
|
|
59
|
+
const slotUsagesEmitter = isDev
|
|
60
|
+
? createReadableStreamEmitter<RscSlotUsageEvent>()
|
|
61
|
+
: null
|
|
62
|
+
|
|
63
|
+
// Create a wrapper component that will be rendered inside React's Flight context.
|
|
64
|
+
// This ensures React.cache works properly since the component is called during
|
|
65
|
+
// renderToReadableStream's render phase, not before it.
|
|
66
|
+
const { proxy: proxyProps } = createSlotProxy<{}>({
|
|
67
|
+
onSlotCall: slotUsagesEmitter
|
|
68
|
+
? (name, args) => {
|
|
69
|
+
const sanitizedArgs = sanitizeSlotArgs(args)
|
|
70
|
+
slotUsagesEmitter.emit({
|
|
71
|
+
slot: name,
|
|
72
|
+
args: sanitizedArgs.length ? sanitizedArgs : undefined,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Wrapper that renders the user's component inside Flight render context
|
|
79
|
+
async function ServerComponentWrapper() {
|
|
80
|
+
return (component as React.FC)(proxyProps)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Render using createElement so React calls our component during Flight rendering
|
|
84
|
+
// This is critical for React.cache to work - the component must be invoked
|
|
85
|
+
// during renderToReadableStream's execution, not before
|
|
86
|
+
const flightStream = renderToReadableStream(
|
|
87
|
+
createElement(ServerComponentWrapper),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Check if this is an SSR request (router) or a direct server function call
|
|
91
|
+
const ctx = getStartContext({ throwIfNotFound: false })
|
|
92
|
+
const isRouterRequest = ctx?.handlerType === 'router'
|
|
93
|
+
const ssrHandler = globalThis.__RSC_SSR__
|
|
94
|
+
|
|
95
|
+
// SSR path: buffer stream for replay, pre-decode for synchronous rendering
|
|
96
|
+
if (isRouterRequest && ssrHandler) {
|
|
97
|
+
const signal = getRequest().signal
|
|
98
|
+
const stream = new ReplayableStream(flightStream, { signal })
|
|
99
|
+
|
|
100
|
+
// Pre-decode during loader phase for synchronous SSR rendering
|
|
101
|
+
const decoded = await ssrHandler.decode(stream)
|
|
102
|
+
|
|
103
|
+
// For SSR we know decode fully consumed the Flight stream.
|
|
104
|
+
slotUsagesEmitter?.close()
|
|
105
|
+
|
|
106
|
+
const proxy = ssrHandler.createCompositeProxy(
|
|
107
|
+
stream,
|
|
108
|
+
decoded,
|
|
109
|
+
slotUsagesEmitter?.stream,
|
|
110
|
+
)
|
|
111
|
+
return proxy as CompositeComponentResult<TComp>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Server function call path:
|
|
115
|
+
// The serialization adapter will stream to the client.
|
|
116
|
+
const monitoredFlightStream =
|
|
117
|
+
isDev && slotUsagesEmitter
|
|
118
|
+
? wrapReadableStream(flightStream, {
|
|
119
|
+
onDone: () => {
|
|
120
|
+
slotUsagesEmitter.close()
|
|
121
|
+
},
|
|
122
|
+
onCancel: () => {
|
|
123
|
+
slotUsagesEmitter.close()
|
|
124
|
+
},
|
|
125
|
+
onError: () => {
|
|
126
|
+
slotUsagesEmitter.close()
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
: flightStream
|
|
130
|
+
|
|
131
|
+
return createCompositeHandle(monitoredFlightStream, {
|
|
132
|
+
slotUsagesStream: slotUsagesEmitter?.stream,
|
|
133
|
+
}) as CompositeComponentResult<TComp>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a composite handle for server function responses.
|
|
138
|
+
* No proxy needed - the client will decode and create its own proxy.
|
|
139
|
+
*/
|
|
140
|
+
function createCompositeHandle(
|
|
141
|
+
flightStream: ReadableStream<Uint8Array>,
|
|
142
|
+
options?: {
|
|
143
|
+
slotUsagesStream?: ReadableStream<RscSlotUsageEvent>
|
|
144
|
+
},
|
|
145
|
+
): AnyCompositeComponent {
|
|
146
|
+
// Simple single-use stream wrapper. For server function calls, the stream
|
|
147
|
+
// is consumed exactly once by the serialization adapter for transport.
|
|
148
|
+
const streamWrapper: ServerComponentStream = {
|
|
149
|
+
createReplayStream: () => flightStream,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Create a stub function with the stream attached for serialization.
|
|
153
|
+
// This will never be rendered directly - it goes through serialization
|
|
154
|
+
// which extracts the stream and sends it to the client.
|
|
155
|
+
const stub = function CompositeComponentStub(): never {
|
|
156
|
+
throw new Error(
|
|
157
|
+
'CompositeComponent from server function cannot be rendered on server. ' +
|
|
158
|
+
'It should be serialized and sent to the client.',
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
;(stub as any)[SERVER_COMPONENT_STREAM] = streamWrapper
|
|
163
|
+
// Note: RENDERABLE_RSC is not set (or implicitly false), indicating this is a composite component
|
|
164
|
+
|
|
165
|
+
if (options?.slotUsagesStream) {
|
|
166
|
+
;(stub as any)[RSC_SLOT_USAGES_STREAM] = options.slotUsagesStream
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return stub as unknown as AnyCompositeComponent
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Base slot props type - functions that become ClientSlot placeholders
|
|
174
|
+
*/
|
|
175
|
+
interface SlotPropsBase {
|
|
176
|
+
[key: string]:
|
|
177
|
+
| ((...args: Array<any>) => React.ReactNode)
|
|
178
|
+
| React.ReactNode
|
|
179
|
+
| undefined
|
|
180
|
+
children?: React.ReactNode
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface SlotProxyResult<TSlotProps extends object> {
|
|
184
|
+
proxy: TSlotProps & SlotPropsBase
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Proxy that turns property access into ClientSlot renders.
|
|
189
|
+
* Also tracks accessed slot names for devtools.
|
|
190
|
+
*/
|
|
191
|
+
function createSlotProxy<TSlotProps extends object>(options?: {
|
|
192
|
+
onSlotCall?: (name: string, args: Array<any>) => void
|
|
193
|
+
}): SlotProxyResult<TSlotProps> {
|
|
194
|
+
const cache = new Map<string, (...args: Array<any>) => React.ReactNode>()
|
|
195
|
+
|
|
196
|
+
const proxy = new Proxy({} as TSlotProps & SlotPropsBase, {
|
|
197
|
+
get(_target, prop) {
|
|
198
|
+
if (prop === 'then' || typeof prop !== 'string') return undefined
|
|
199
|
+
|
|
200
|
+
if (prop === 'children') {
|
|
201
|
+
options?.onSlotCall?.('children', [])
|
|
202
|
+
return createElement(ClientSlot, { slot: 'children', args: [] })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let fn = cache.get(prop)
|
|
206
|
+
if (!fn) {
|
|
207
|
+
fn = (...args: Array<any>) => {
|
|
208
|
+
options?.onSlotCall?.(prop, args)
|
|
209
|
+
return createElement(ClientSlot, { slot: prop, args })
|
|
210
|
+
}
|
|
211
|
+
cache.set(prop, fn)
|
|
212
|
+
}
|
|
213
|
+
return fn
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
proxy,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function createReadableStreamEmitter<T>(): {
|
|
223
|
+
stream: ReadableStream<T>
|
|
224
|
+
emit: (value: T) => void
|
|
225
|
+
close: () => void
|
|
226
|
+
} {
|
|
227
|
+
let closed = false
|
|
228
|
+
const queue: Array<T> = []
|
|
229
|
+
let controller: ReadableStreamDefaultController<T> | null = null
|
|
230
|
+
|
|
231
|
+
const stream = new ReadableStream<T>({
|
|
232
|
+
start(ctrl) {
|
|
233
|
+
controller = ctrl
|
|
234
|
+
for (const value of queue) {
|
|
235
|
+
try {
|
|
236
|
+
ctrl.enqueue(value)
|
|
237
|
+
} catch {
|
|
238
|
+
// Ignore
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
queue.length = 0
|
|
242
|
+
if (closed) {
|
|
243
|
+
try {
|
|
244
|
+
ctrl.close()
|
|
245
|
+
} catch {
|
|
246
|
+
// Ignore
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
cancel() {
|
|
251
|
+
closed = true
|
|
252
|
+
controller = null
|
|
253
|
+
queue.length = 0
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
const emit = (value: T) => {
|
|
258
|
+
if (closed) return
|
|
259
|
+
if (!controller) {
|
|
260
|
+
queue.push(value)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
controller.enqueue(value)
|
|
265
|
+
} catch {
|
|
266
|
+
// Ignore
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const close = () => {
|
|
271
|
+
if (closed) return
|
|
272
|
+
closed = true
|
|
273
|
+
if (controller) {
|
|
274
|
+
try {
|
|
275
|
+
controller.close()
|
|
276
|
+
} catch {
|
|
277
|
+
// Ignore
|
|
278
|
+
}
|
|
279
|
+
controller = null
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { stream, emit, close }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function wrapReadableStream<T>(
|
|
287
|
+
source: ReadableStream<T>,
|
|
288
|
+
handlers: {
|
|
289
|
+
onDone?: () => void
|
|
290
|
+
onCancel?: () => void
|
|
291
|
+
onError?: () => void
|
|
292
|
+
},
|
|
293
|
+
): ReadableStream<T> {
|
|
294
|
+
const reader = source.getReader()
|
|
295
|
+
let finished = false
|
|
296
|
+
|
|
297
|
+
const finish = () => {
|
|
298
|
+
if (finished) return
|
|
299
|
+
finished = true
|
|
300
|
+
handlers.onDone?.()
|
|
301
|
+
try {
|
|
302
|
+
reader.releaseLock()
|
|
303
|
+
} catch {
|
|
304
|
+
// Ignore
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return new ReadableStream<T>({
|
|
309
|
+
async pull(controller) {
|
|
310
|
+
try {
|
|
311
|
+
const { value, done } = await reader.read()
|
|
312
|
+
if (done) {
|
|
313
|
+
controller.close()
|
|
314
|
+
finish()
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
controller.enqueue(value)
|
|
318
|
+
} catch (err) {
|
|
319
|
+
try {
|
|
320
|
+
controller.error(err)
|
|
321
|
+
} catch {
|
|
322
|
+
// Ignore
|
|
323
|
+
}
|
|
324
|
+
handlers.onError?.()
|
|
325
|
+
finish()
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
async cancel(reason) {
|
|
329
|
+
handlers.onCancel?.()
|
|
330
|
+
try {
|
|
331
|
+
await reader.cancel(reason)
|
|
332
|
+
} catch {
|
|
333
|
+
// Ignore
|
|
334
|
+
}
|
|
335
|
+
finish()
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
}
|