@octanejs/tanstack-start 0.1.1 → 0.1.5

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 (43) hide show
  1. package/package.json +13 -8
  2. package/src/GenericHydrate.tsrx +396 -0
  3. package/src/GenericHydrate.tsrx.d.ts +5 -0
  4. package/src/Hydrate.tsrx +106 -0
  5. package/src/Hydrate.tsrx.d.ts +63 -0
  6. package/src/client-only-server-strip.js +461 -0
  7. package/src/hydration/generic.d.ts +18 -0
  8. package/src/hydration/generic.js +26 -0
  9. package/src/hydration/idle.d.ts +9 -0
  10. package/src/hydration/idle.js +10 -0
  11. package/src/hydration/load.tsrx +38 -0
  12. package/src/hydration/load.tsrx.d.ts +8 -0
  13. package/src/hydration/never.tsrx +71 -0
  14. package/src/hydration/never.tsrx.d.ts +6 -0
  15. package/src/hydration/visible.tsrx +123 -0
  16. package/src/hydration/visible.tsrx.d.ts +12 -0
  17. package/src/hydration.d.ts +20 -0
  18. package/src/hydration.js +8 -0
  19. package/src/index.d.ts +11 -0
  20. package/src/index.js +2 -0
  21. package/src/internal/router-generator/filesystem/physical/getRouteNodes.js +4 -6
  22. package/src/internal/router-generator/generator.js +3 -0
  23. package/src/internal/router-plugin/core/code-splitter/compilers.js +3 -6
  24. package/src/internal/router-plugin/core/config.d.ts +1 -3
  25. package/src/internal/router-plugin/core/router-code-splitter-plugin.js +6 -3
  26. package/src/internal/router-plugin/esbuild.d.ts +4 -10
  27. package/src/internal/router-plugin/vite.d.ts +4 -10
  28. package/src/internal/start-plugin-core/import-protection/analysis.js +13 -12
  29. package/src/internal/start-plugin-core/import-protection/constants.d.ts +0 -1
  30. package/src/internal/start-plugin-core/import-protection/constants.js +0 -3
  31. package/src/internal/start-plugin-core/schema.d.ts +4 -14
  32. package/src/internal/start-plugin-core/start-compiler/compiler.d.ts +1 -6
  33. package/src/internal/start-plugin-core/start-compiler/compiler.js +4 -4
  34. package/src/internal/start-plugin-core/start-compiler/utils.d.ts +7 -0
  35. package/src/internal/start-plugin-core/start-compiler/utils.js +7 -0
  36. package/src/internal/start-plugin-core/types.d.ts +1 -2
  37. package/src/internal/start-plugin-core/vite/import-protection-plugin/plugin.js +21 -21
  38. package/src/internal/start-plugin-core/vite/module-id.d.ts +6 -0
  39. package/src/internal/start-plugin-core/vite/module-id.js +23 -0
  40. package/src/internal/start-plugin-core/vite/schema.d.ts +3 -12
  41. package/src/internal/start-plugin-core/vite/start-compiler-plugin/plugin.js +9 -3
  42. package/src/plugin-vite.d.ts +9 -1
  43. package/src/plugin-vite.js +20 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/tanstack-start",
3
- "version": "0.1.1",
3
+ "version": "0.1.5",
4
4
  "description": "TanStack Start for Octane, including file-route generation, server functions, SSR streaming, hydration, and Vite integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -86,6 +86,11 @@
86
86
  "import": "./src/client-only.js",
87
87
  "default": "./src/client-only.js"
88
88
  },
89
+ "./hydration": {
90
+ "types": "./src/hydration.d.ts",
91
+ "import": "./src/hydration.js",
92
+ "default": "./src/hydration.js"
93
+ },
89
94
  "./plugin/vite": {
90
95
  "types": "./src/plugin-vite.d.ts",
91
96
  "import": "./src/plugin-vite.js",
@@ -116,7 +121,7 @@
116
121
  "magic-string": "^0.30.21",
117
122
  "pathe": "^2.0.3",
118
123
  "picomatch": "^4.0.3",
119
- "prettier": "^3.8.4",
124
+ "prettier": "^3.9.6",
120
125
  "seroval": "^1.5.4",
121
126
  "source-map": "^0.7.6",
122
127
  "srvx": "^0.11.9",
@@ -126,11 +131,11 @@
126
131
  "vitefu": "^1.1.1",
127
132
  "xmlbuilder2": "^4.0.3",
128
133
  "zod": "^4.4.3",
129
- "@octanejs/tanstack-router": "0.1.11"
134
+ "@octanejs/tanstack-router": "0.1.15"
130
135
  },
131
136
  "peerDependencies": {
132
137
  "vite": ">=7.0.0",
133
- "octane": "0.1.12"
138
+ "octane": "0.1.16"
134
139
  },
135
140
  "peerDependenciesMeta": {
136
141
  "vite": {
@@ -138,10 +143,10 @@
138
143
  }
139
144
  },
140
145
  "devDependencies": {
141
- "@types/node": "^24.3.0",
142
- "vite": "^8.0.16",
143
- "vitest": "^4.1.9",
144
- "octane": "0.1.12"
146
+ "@types/node": "^24.13.3",
147
+ "vite": "^8.1.5",
148
+ "vitest": "^4.1.10",
149
+ "octane": "0.1.16"
145
150
  },
146
151
  "scripts": {
147
152
  "test": "cd ../.. && vitest run --project tanstack-start"
@@ -0,0 +1,396 @@
1
+ // GenericHydrate — port of @tanstack/react-start-client's GenericHydrate.tsx.
2
+ // The shared renderer behind the `idle` / `media` / `condition` / `interaction`
3
+ // strategies (and dynamic `when` functions): it renders the hydration marker
4
+ // `<div>` and gates the children behind a Suspense boundary whose promise is
5
+ // the strategy's hydration gate (`use(gate.promise)`), so deferred subtrees
6
+ // stay dehydrated until the strategy resolves the gate. All gate bookkeeping,
7
+ // delegated-intent listening, and prefetch waiting delegate to the
8
+ // framework-agnostic `@tanstack/start-client-core/hydration` runtime, exactly
9
+ // as upstream does. `reactUse` feature-detection is dropped: octane's `use`
10
+ // always exists. Upstream's SSR-safe `useLayoutEffect` re-export from
11
+ // `@tanstack/react-router` maps to octane's own `useLayoutEffect` (octane's
12
+ // server runtime ignores effects, so no SSR warning shim is needed).
13
+ //
14
+ // OCTANE ADAPTATION: upstream invokes strategy renderers inline
15
+ // (`props.when._h(props)` runs the renderer's hooks on the caller's fiber).
16
+ // Octane's compiled components take the `(props, __s, __extra)` ABI and cannot
17
+ // be bare-called, so octane's `Hydrate` renders `_h` as a child component
18
+ // instead — `GenericHydrate` is that component. Same DOM and effects; the
19
+ // hooks live in their own component scope rather than Hydrate's.
20
+ import {
21
+ Suspense,
22
+ use,
23
+ useCallback,
24
+ useEffect,
25
+ useId,
26
+ useLayoutEffect,
27
+ useRef,
28
+ useState,
29
+ } from 'octane';
30
+ import type { OctaneNode } from 'octane';
31
+ import { useHydrated } from '@octanejs/tanstack-router';
32
+ import { isServer } from '@tanstack/router-core/isServer';
33
+ import {
34
+ hydrateIdAttribute,
35
+ hydrateWhenAttribute,
36
+ } from '@tanstack/start-client-core/hydration/constants';
37
+ import {
38
+ createResolvedGate,
39
+ getFallbackHtml,
40
+ getOrCreateGate,
41
+ onGateResolve,
42
+ releaseGate,
43
+ runHydrationStrategyCleanup,
44
+ saveFallbackHtml,
45
+ waitForHydrationPrefetchStrategy,
46
+ } from '@tanstack/start-client-core/hydration/runtime';
47
+ import { listenForDelegatedHydrationIntent } from '@tanstack/start-client-core/hydration';
48
+ import type {
49
+ HydrationRuntimeContext,
50
+ HydrationStrategy,
51
+ HydrationWhen,
52
+ } from '@tanstack/start-client-core/hydration';
53
+ import type { HydrationGateRecord } from '@tanstack/start-client-core/hydration/runtime';
54
+ import type { HydrateProps, InternalHydrateProps } from './Hydrate.tsrx';
55
+
56
+ type Gate = HydrationGateRecord & { promise: Promise<void> };
57
+ type PrefetchController = {
58
+ abortController: AbortController;
59
+ hydrationRequested: boolean;
60
+ hydrationListeners: Set<() => void>;
61
+ hydrationResolvePending: boolean;
62
+ started: boolean;
63
+ promise?: Promise<void>;
64
+ cleanup?: () => void;
65
+ };
66
+
67
+ const dynamicType = 'dynamic';
68
+ const dynamicHydrateStrategy: HydrationStrategy<typeof dynamicType, false> = {
69
+ _t: dynamicType,
70
+ _d: () => true,
71
+ };
72
+
73
+ function shouldDeferHydration(strategy: HydrationStrategy) {
74
+ return strategy._d ? strategy._d() : strategy._t !== 'load';
75
+ }
76
+
77
+ function useLatest<T>(value: T) {
78
+ const ref = useRef(value);
79
+ ref.current = value;
80
+ return ref;
81
+ }
82
+
83
+ function useHydrationGate(props: InternalHydrateProps) {
84
+ const hydrated = useHydrated();
85
+ const octaneId = useId();
86
+ const id = props.h ? `${props.h}${octaneId}` : octaneId;
87
+ const when = props.when;
88
+ const isDynamicHydrate = typeof when === 'function';
89
+ const dynamicHydrateStrategyRef = useRef<HydrationStrategy | undefined>(undefined);
90
+ if (isDynamicHydrate) {
91
+ dynamicHydrateStrategyRef.current ??= isServer ?? typeof window === 'undefined'
92
+ ? dynamicHydrateStrategy
93
+ : when();
94
+ }
95
+ const hydrateStrategy = isDynamicHydrate ? dynamicHydrateStrategyRef.current! : when;
96
+ const markerHydrateType: HydrationWhen = isDynamicHydrate ? dynamicType : hydrateStrategy._t!;
97
+ const [prefetchError, setPrefetchError] = useState<unknown>();
98
+ const latestRef = useLatest({
99
+ prefetch: props.prefetch,
100
+ preload: props.p,
101
+ });
102
+ const gateRef = useRef<HydrationGateRecord | undefined>(undefined);
103
+ const markerElementRef = useRef<HTMLDivElement | null>(null);
104
+ const shouldPreserveServerHTMLRef = useRef<boolean | undefined>(undefined);
105
+ const shouldDeferInitialHydrationRef = useRef<boolean | undefined>(undefined);
106
+ const didPrefetchRef = useRef(false);
107
+ const prefetchControllerRef = useRef<PrefetchController | undefined>(undefined);
108
+
109
+ prefetchControllerRef.current ??= {
110
+ abortController: new AbortController(),
111
+ hydrationRequested: false,
112
+ hydrationListeners: new Set<() => void>(),
113
+ hydrationResolvePending: false,
114
+ started: false,
115
+ };
116
+
117
+ shouldPreserveServerHTMLRef.current ??= (isServer ?? typeof window === 'undefined') || !hydrated;
118
+ shouldDeferInitialHydrationRef.current ??= !hydrated && shouldDeferHydration(hydrateStrategy);
119
+
120
+ if (!gateRef.current) {
121
+ gateRef.current = isServer ?? typeof window === 'undefined'
122
+ ? createResolvedGate(id, hydrateStrategy._t!)
123
+ : getOrCreateGate(id, hydrateStrategy._t!);
124
+ }
125
+
126
+ gateRef.current.when = hydrateStrategy._t!;
127
+
128
+ if (
129
+ !(isServer ?? typeof window === 'undefined') && hydrateStrategy._t !== 'never' &&
130
+ (!shouldDeferInitialHydrationRef.current || !shouldDeferHydration(hydrateStrategy))
131
+ ) {
132
+ gateRef.current.resolve();
133
+ }
134
+
135
+ const markerRef = useCallback((element: HTMLDivElement | null) => {
136
+ markerElementRef.current = element;
137
+ if (element) {
138
+ if (hydrateStrategy._t === 'never' && !shouldPreserveServerHTMLRef.current) {
139
+ element.replaceChildren();
140
+ }
141
+ saveFallbackHtml(id, element);
142
+ }
143
+ }, [hydrateStrategy._t, id]);
144
+
145
+ useEffect(() => {
146
+ const gate = gateRef.current!;
147
+ return () => {
148
+ const controller = prefetchControllerRef.current;
149
+ controller?.abortController.abort();
150
+ controller?.cleanup?.();
151
+ controller?.hydrationListeners.clear();
152
+ releaseGate(gate);
153
+ };
154
+ }, []);
155
+
156
+ useEffect(() => {
157
+ if ((isServer ?? typeof window === 'undefined') || !latestRef.current.prefetch) {
158
+ return;
159
+ }
160
+
161
+ const controller = prefetchControllerRef.current!;
162
+ if (controller.started) return;
163
+ controller.started = true;
164
+
165
+ const onHydrate = (listener: () => void) => {
166
+ if (controller.hydrationRequested) {
167
+ listener();
168
+ return () => {};
169
+ }
170
+
171
+ controller.hydrationListeners.add(listener);
172
+ return () => {
173
+ controller.hydrationListeners.delete(listener);
174
+ };
175
+ };
176
+
177
+ const preload = () => latestRef.current.preload?.() ?? Promise.resolve();
178
+ const prefetchInput = latestRef.current.prefetch;
179
+
180
+ if (typeof prefetchInput === 'function') {
181
+ const promise = Promise.resolve().then(
182
+ () => prefetchInput({
183
+ element: markerElementRef.current,
184
+ signal: controller.abortController.signal,
185
+ preload,
186
+ waitFor: (strategy) => waitForHydrationPrefetchStrategy(strategy, {
187
+ element: markerElementRef.current,
188
+ signal: controller.abortController.signal,
189
+ onHydrate,
190
+ }),
191
+ }),
192
+ ).then(() => undefined);
193
+
194
+ controller.promise = promise;
195
+ promise.catch((error) => {
196
+ if (!controller.abortController.signal.aborted) {
197
+ setPrefetchError(error);
198
+ }
199
+ });
200
+ return;
201
+ }
202
+
203
+ if (!latestRef.current.preload) return;
204
+
205
+ const prefetch = () => {
206
+ if (didPrefetchRef.current) return;
207
+ didPrefetchRef.current = true;
208
+ void preload();
209
+ };
210
+
211
+ controller.cleanup = runHydrationStrategyCleanup(
212
+ prefetchInput._s?.({
213
+ element: markerElementRef.current,
214
+ prefetch,
215
+ }),
216
+ );
217
+ }, [hydrateStrategy, latestRef]);
218
+
219
+ useLayoutEffect(() => {
220
+ const gate = gateRef.current!;
221
+ if (!shouldDeferInitialHydrationRef.current || hydrateStrategy._t === 'never') {
222
+ return;
223
+ }
224
+
225
+ if (gate.resolved) {
226
+ return;
227
+ }
228
+
229
+ const cleanups: Array<() => void> = [];
230
+ let removeResolveListener = () => {};
231
+ let disposed = false;
232
+ const resolveGate = gate.resolve;
233
+
234
+ const cleanup = () => {
235
+ if (disposed) return;
236
+ disposed = true;
237
+ if (gate.resolve === requestHydration) {
238
+ gate.resolve = resolveGate;
239
+ }
240
+ removeResolveListener();
241
+ cleanups.forEach((fn) => fn());
242
+ };
243
+
244
+ const addCleanup = (fn: void | (() => void)) => {
245
+ if (!fn) return;
246
+ if (disposed || gate.resolved) {
247
+ fn();
248
+ return;
249
+ }
250
+ cleanups.push(fn);
251
+ };
252
+
253
+ const requestHydration = () => {
254
+ const controller = prefetchControllerRef.current!;
255
+ if (!controller.hydrationRequested) {
256
+ controller.hydrationRequested = true;
257
+ controller.hydrationListeners.forEach((listener) => listener());
258
+ controller.hydrationListeners.clear();
259
+ }
260
+
261
+ if (!controller.promise) {
262
+ resolveGate();
263
+ return;
264
+ }
265
+ if (controller.hydrationResolvePending) return;
266
+ controller.hydrationResolvePending = true;
267
+
268
+ controller.promise.then(() => resolveGate(), (error) => {
269
+ if (!controller.abortController.signal.aborted) {
270
+ setPrefetchError(error);
271
+ }
272
+ });
273
+ };
274
+
275
+ gate.resolve = requestHydration;
276
+ removeResolveListener = onGateResolve(gate, cleanup);
277
+
278
+ const context: HydrationRuntimeContext = {
279
+ element: markerElementRef.current,
280
+ gate,
281
+ };
282
+ addCleanup(runHydrationStrategyCleanup(hydrateStrategy._s?.(context)));
283
+
284
+ if (hydrateStrategy._t !== 'interaction') {
285
+ addCleanup(
286
+ runHydrationStrategyCleanup(
287
+ markerElementRef.current
288
+ ? listenForDelegatedHydrationIntent(markerElementRef.current, context)
289
+ : undefined,
290
+ ),
291
+ );
292
+ }
293
+
294
+ return cleanup;
295
+ }, [hydrateStrategy, latestRef]);
296
+
297
+ return {
298
+ gate: gateRef.current,
299
+ markerRef,
300
+ markerElementRef,
301
+ hydrateStrategy,
302
+ markerHydrateType,
303
+ prefetchError,
304
+ shouldPreserveServerHTML: shouldPreserveServerHTMLRef.current,
305
+ };
306
+ }
307
+
308
+ function HydrationGate(props: { gate: Gate; children?: OctaneNode }) {
309
+ if (isServer ?? typeof window === 'undefined') {
310
+ return props.children;
311
+ }
312
+
313
+ if (props.gate.resolved) {
314
+ return props.children;
315
+ }
316
+
317
+ use(props.gate.promise);
318
+
319
+ return props.children;
320
+ }
321
+
322
+ function HydratedBoundary(props: {
323
+ id: string;
324
+ onHydrated?: () => void;
325
+ onStrategyHydrated?: (id: string) => void;
326
+ children?: OctaneNode;
327
+ }) {
328
+ const { id, onHydrated, onStrategyHydrated } = props;
329
+ const didHydrateRef = useRef(false);
330
+
331
+ useEffect(() => {
332
+ if (didHydrateRef.current) return;
333
+ didHydrateRef.current = true;
334
+ onHydrated?.();
335
+ onStrategyHydrated?.(id);
336
+ }, [id, onHydrated, onStrategyHydrated]);
337
+
338
+ return props.children;
339
+ }
340
+
341
+ export function GenericHydrate(props: HydrateProps) @{
342
+ const internalProps = props as InternalHydrateProps;
343
+ const {
344
+ gate,
345
+ hydrateStrategy,
346
+ markerHydrateType,
347
+ markerElementRef,
348
+ markerRef,
349
+ prefetchError,
350
+ shouldPreserveServerHTML,
351
+ } = useHydrationGate(internalProps);
352
+ if (prefetchError) throw prefetchError;
353
+
354
+ let fallback: OctaneNode;
355
+ if (shouldPreserveServerHTML) {
356
+ const html = getFallbackHtml(gate.id);
357
+ fallback = html
358
+ ? <div style={{ display: 'contents' }} dangerouslySetInnerHTML={{ __html: html }} />
359
+ : null;
360
+ } else {
361
+ fallback = props.fallback ?? null;
362
+ }
363
+ const markerAttributes =
364
+ markerHydrateType === dynamicType ? undefined : hydrateStrategy._a?.();
365
+
366
+ const hydrateType = hydrateStrategy._t!;
367
+ const preserveNever = hydrateType === 'never' && !shouldPreserveServerHTML;
368
+
369
+ <div
370
+ ref={markerRef}
371
+ {...{
372
+ [hydrateIdAttribute]: gate.id,
373
+ [hydrateWhenAttribute]: markerHydrateType,
374
+ ...markerAttributes,
375
+ }}
376
+ >
377
+ @if (preserveNever) {
378
+ <>
379
+ {props.fallback ?? null}
380
+ </>
381
+ } @else {
382
+ <Suspense fallback={fallback}>
383
+ <HydrationGate gate={gate as Gate}>
384
+ <HydratedBoundary
385
+ id={gate.id}
386
+ onHydrated={props.onHydrated}
387
+ onStrategyHydrated={(id) => {
388
+ markerElementRef.current?.removeAttribute(hydrateWhenAttribute);
389
+ hydrateStrategy._o?.(id);
390
+ }}
391
+ >{props.children}</HydratedBoundary>
392
+ </HydrationGate>
393
+ </Suspense>
394
+ }
395
+ </div>
396
+ }
@@ -0,0 +1,5 @@
1
+ // Type declaration for the .tsrx module (resolved by relative path).
2
+ import type { OctaneNode } from 'octane';
3
+ import type { HydrateProps } from './Hydrate.tsrx';
4
+
5
+ export declare function GenericHydrate(props: HydrateProps): OctaneNode;
@@ -0,0 +1,106 @@
1
+ // Hydrate — port of @tanstack/react-start-client's Hydrate.tsx (partial
2
+ // hydration entry point). `props.when` carries a hydration strategy created by
3
+ // the `@octanejs/tanstack-start/hydration` factories; each strategy brings its
4
+ // own renderer (`_h`), so `Hydrate` delegates to it. A function-valued `when`
5
+ // picks the strategy on the client only; on the server it renders the generic
6
+ // `dynamic` marker so the streamed HTML stays strategy-agnostic.
7
+ //
8
+ // OCTANE ADAPTATION: upstream invokes the renderer inline as a method call
9
+ // (`props.when._h(props)` — the renderer's hooks run on Hydrate's own fiber
10
+ // and it reads the strategy off `this`). Octane's compiled components use the
11
+ // `(props, __s, __extra)` ABI and cannot be bare-called, so `_h` is rendered
12
+ // as a child COMPONENT here instead, and renderers that need their strategy
13
+ // derive it from `props.when` (resolving function-valued `when`, exactly as
14
+ // upstream's Hydrate does per render). Same DOM, markers, and effects; the
15
+ // renderer's hooks live in their own component scope rather than Hydrate's.
16
+ import { Suspense, useId } from 'octane';
17
+ import type { OctaneNode } from 'octane';
18
+ import { isServer } from '@tanstack/router-core/isServer';
19
+ import {
20
+ hydrateIdAttribute,
21
+ hydrateWhenAttribute,
22
+ } from '@tanstack/start-client-core/hydration/constants';
23
+ import type {
24
+ HydrationStrategy as CoreHydrationStrategy,
25
+ HydrationPrefetchFunction,
26
+ HydrationPrefetchStrategy,
27
+ HydrationWhen,
28
+ } from '@tanstack/start-client-core/hydration';
29
+
30
+ export type OctaneHydrationStrategy<
31
+ TWhen extends HydrationWhen = HydrationWhen,
32
+ TCanPrefetch extends boolean = boolean,
33
+ > =
34
+ CoreHydrationStrategy<TWhen, TCanPrefetch> & {
35
+ _h: (props: HydrateProps) => OctaneNode;
36
+ };
37
+
38
+ export type HydrationStrategy<
39
+ TWhen extends HydrationWhen = HydrationWhen,
40
+ TCanPrefetch extends boolean = boolean,
41
+ > =
42
+ OctaneHydrationStrategy<TWhen, TCanPrefetch>;
43
+
44
+ export type HydrateWhen = OctaneHydrationStrategy | (() => OctaneHydrationStrategy);
45
+
46
+ type HydrateCommonOptions = {
47
+ when: HydrateWhen;
48
+ fallback?: OctaneNode;
49
+ onHydrated?: () => void;
50
+ };
51
+
52
+ export type HydrateOptions =
53
+ | (HydrateCommonOptions & {
54
+ prefetch?: never;
55
+ split?: boolean;
56
+ })
57
+ | (HydrateCommonOptions & {
58
+ prefetch: HydrationPrefetchStrategy;
59
+ split?: true;
60
+ })
61
+ | (HydrateCommonOptions & {
62
+ prefetch: HydrationPrefetchFunction;
63
+ split?: boolean;
64
+ });
65
+
66
+ export type HydrateProps =
67
+ HydrateOptions & {
68
+ children: OctaneNode;
69
+ };
70
+
71
+ export type InternalHydrateProps =
72
+ HydrateProps & {
73
+ h?: string;
74
+ p?: () => Promise<void>;
75
+ };
76
+
77
+ const dynamicType = 'dynamic';
78
+
79
+ function ServerDynamicHydrate(props: HydrateProps) @{
80
+ const internalProps = props as InternalHydrateProps;
81
+ const octaneId = useId();
82
+ const id = internalProps.h ? `${internalProps.h}${octaneId}` : octaneId;
83
+
84
+ <div
85
+ {...{
86
+ [hydrateIdAttribute]: id,
87
+ [hydrateWhenAttribute]: dynamicType,
88
+ }}
89
+ >
90
+ <Suspense fallback={props.fallback ?? null}>{props.children}</Suspense>
91
+ </div>
92
+ }
93
+
94
+ export function Hydrate(props: HydrateProps) {
95
+ if (typeof props.when === 'function') {
96
+ if (isServer ?? typeof window === 'undefined') {
97
+ return <ServerDynamicHydrate {...props} />;
98
+ }
99
+
100
+ const DynamicRenderer = props.when()._h;
101
+ return <DynamicRenderer {...props} />;
102
+ }
103
+
104
+ const Renderer = props.when._h;
105
+ return <Renderer {...props} />;
106
+ }
@@ -0,0 +1,63 @@
1
+ // Type declarations for the .tsrx module (resolved by relative path).
2
+ import type { OctaneNode } from 'octane';
3
+ import type {
4
+ HydrationStrategy as CoreHydrationStrategy,
5
+ HydrationPrefetchFunction,
6
+ HydrationPrefetchStrategy,
7
+ HydrationWhen,
8
+ } from '@tanstack/start-client-core/hydration';
9
+
10
+ export type {
11
+ HydrationInteractionEvent,
12
+ HydrationInteractionEvents,
13
+ HydrationPrefetchContext,
14
+ HydrationPrefetchFunction,
15
+ HydrationPrefetchStrategy,
16
+ HydrationPrefetchWaitReason,
17
+ HydrationWhen,
18
+ } from '@tanstack/start-client-core/hydration';
19
+
20
+ export type OctaneHydrationStrategy<
21
+ TWhen extends HydrationWhen = HydrationWhen,
22
+ TCanPrefetch extends boolean = boolean,
23
+ > = CoreHydrationStrategy<TWhen, TCanPrefetch> & {
24
+ _h: (props: HydrateProps) => OctaneNode;
25
+ };
26
+
27
+ export type HydrationStrategy<
28
+ TWhen extends HydrationWhen = HydrationWhen,
29
+ TCanPrefetch extends boolean = boolean,
30
+ > = OctaneHydrationStrategy<TWhen, TCanPrefetch>;
31
+
32
+ export type HydrateWhen = OctaneHydrationStrategy | (() => OctaneHydrationStrategy);
33
+
34
+ type HydrateCommonOptions = {
35
+ when: HydrateWhen;
36
+ fallback?: OctaneNode;
37
+ onHydrated?: () => void;
38
+ };
39
+
40
+ export type HydrateOptions =
41
+ | (HydrateCommonOptions & {
42
+ prefetch?: never;
43
+ split?: boolean;
44
+ })
45
+ | (HydrateCommonOptions & {
46
+ prefetch: HydrationPrefetchStrategy;
47
+ split?: true;
48
+ })
49
+ | (HydrateCommonOptions & {
50
+ prefetch: HydrationPrefetchFunction;
51
+ split?: boolean;
52
+ });
53
+
54
+ export type HydrateProps = HydrateOptions & {
55
+ children: OctaneNode;
56
+ };
57
+
58
+ export type InternalHydrateProps = HydrateProps & {
59
+ h?: string;
60
+ p?: () => Promise<void>;
61
+ };
62
+
63
+ export declare function Hydrate(props: HydrateProps): OctaneNode;