kiru 0.53.0 → 0.54.0-preview.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.
Files changed (81) hide show
  1. package/dist/globals.d.ts +1 -1
  2. package/dist/globals.d.ts.map +1 -1
  3. package/dist/globals.js.map +1 -1
  4. package/dist/router/client/index.d.ts +4 -2
  5. package/dist/router/client/index.d.ts.map +1 -1
  6. package/dist/router/client/index.js +49 -11
  7. package/dist/router/client/index.js.map +1 -1
  8. package/dist/router/context.d.ts +2 -0
  9. package/dist/router/context.d.ts.map +1 -1
  10. package/dist/router/context.js +5 -1
  11. package/dist/router/context.js.map +1 -1
  12. package/dist/router/fileRouterController.d.ts +2 -0
  13. package/dist/router/fileRouterController.d.ts.map +1 -1
  14. package/dist/router/fileRouterController.js +80 -9
  15. package/dist/router/fileRouterController.js.map +1 -1
  16. package/dist/router/globals.d.ts +3 -0
  17. package/dist/router/globals.d.ts.map +1 -1
  18. package/dist/router/globals.js +3 -0
  19. package/dist/router/globals.js.map +1 -1
  20. package/dist/router/guard.d.ts +17 -0
  21. package/dist/router/guard.d.ts.map +1 -0
  22. package/dist/router/guard.js +45 -0
  23. package/dist/router/guard.js.map +1 -0
  24. package/dist/router/head.d.ts.map +1 -1
  25. package/dist/router/head.js +5 -7
  26. package/dist/router/head.js.map +1 -1
  27. package/dist/router/index.d.ts +2 -1
  28. package/dist/router/index.d.ts.map +1 -1
  29. package/dist/router/index.js +2 -1
  30. package/dist/router/index.js.map +1 -1
  31. package/dist/router/{server → ssg}/index.d.ts +4 -3
  32. package/dist/router/ssg/index.d.ts.map +1 -0
  33. package/dist/router/{server → ssg}/index.js +7 -4
  34. package/dist/router/ssg/index.js.map +1 -0
  35. package/dist/router/ssr/index.d.ts +20 -0
  36. package/dist/router/ssr/index.d.ts.map +1 -0
  37. package/dist/router/ssr/index.js +160 -0
  38. package/dist/router/ssr/index.js.map +1 -0
  39. package/dist/router/types.d.ts +37 -4
  40. package/dist/router/types.d.ts.map +1 -1
  41. package/dist/router/types.internal.d.ts +4 -0
  42. package/dist/router/types.internal.d.ts.map +1 -1
  43. package/dist/router/utils/index.d.ts +8 -3
  44. package/dist/router/utils/index.d.ts.map +1 -1
  45. package/dist/router/utils/index.js +38 -6
  46. package/dist/router/utils/index.js.map +1 -1
  47. package/dist/ssr/client.d.ts +1 -1
  48. package/dist/ssr/client.d.ts.map +1 -1
  49. package/dist/ssr/server.d.ts +1 -2
  50. package/dist/ssr/server.d.ts.map +1 -1
  51. package/dist/ssr/server.js +16 -19
  52. package/dist/ssr/server.js.map +1 -1
  53. package/dist/types.d.ts +3 -0
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/utils/format.d.ts +2 -1
  56. package/dist/utils/format.d.ts.map +1 -1
  57. package/dist/utils/format.js +4 -1
  58. package/dist/utils/format.js.map +1 -1
  59. package/dist/utils/runtime.d.ts +1 -1
  60. package/dist/utils/runtime.js +1 -1
  61. package/package.json +8 -4
  62. package/src/globals.ts +1 -1
  63. package/src/router/client/index.ts +100 -14
  64. package/src/router/context.ts +7 -1
  65. package/src/router/fileRouterController.ts +137 -8
  66. package/src/router/globals.ts +4 -0
  67. package/src/router/guard.ts +72 -0
  68. package/src/router/head.ts +5 -7
  69. package/src/router/index.ts +12 -1
  70. package/src/router/{server → ssg}/index.ts +16 -9
  71. package/src/router/ssr/index.ts +247 -0
  72. package/src/router/types.internal.ts +5 -0
  73. package/src/router/types.ts +48 -4
  74. package/src/router/utils/index.ts +74 -8
  75. package/src/ssr/client.ts +1 -1
  76. package/src/ssr/server.ts +19 -21
  77. package/src/types.ts +3 -0
  78. package/src/utils/format.ts +5 -0
  79. package/src/utils/runtime.ts +1 -1
  80. package/dist/router/server/index.d.ts.map +0 -1
  81. package/dist/router/server/index.js.map +0 -1
@@ -0,0 +1,247 @@
1
+ import path from "path"
2
+ import { createElement, Fragment } from "../../element.js"
3
+ import { __DEV__ } from "../../env.js"
4
+ import { renderToString } from "../../renderToString.js"
5
+ import { renderToReadableStream } from "../../ssr/server.js"
6
+ import { toArray } from "../../utils/format.js"
7
+ import {
8
+ matchModules,
9
+ matchRoute,
10
+ match404Route,
11
+ parseQuery,
12
+ wrapWithLayouts,
13
+ runBeforeEachGuards,
14
+ runAfterEachGuards,
15
+ runBeforeEnterHooks,
16
+ } from "../utils/index.js"
17
+ import { RouterContext, RequestContext } from "../context.js"
18
+ import type { PageConfig, PageProps, RouterState } from "../types.js"
19
+ import type {
20
+ FormattedViteImportMap,
21
+ GuardModule,
22
+ PageModule,
23
+ } from "../types.internal.js"
24
+
25
+ export interface SSRRenderContext {
26
+ pages: FormattedViteImportMap<PageModule>
27
+ layouts: FormattedViteImportMap
28
+ guards: FormattedViteImportMap<GuardModule>
29
+ Document: Kiru.FC
30
+ userContext: Kiru.RequestContext
31
+ registerModule: (moduleId: string) => void
32
+ }
33
+
34
+ export interface SSRHttpResponse {
35
+ html: string
36
+ statusCode: number
37
+ headers: Array<[string, string]>
38
+ stream: ReadableStream | null
39
+ }
40
+
41
+ export interface SSRRenderResult {
42
+ httpResponse: SSRHttpResponse | null
43
+ }
44
+
45
+ export async function render(
46
+ url: string,
47
+ ctx: SSRRenderContext
48
+ ): Promise<SSRRenderResult> {
49
+ const extName = path.extname(url)
50
+ if (extName && extName.length > 0) {
51
+ return {
52
+ httpResponse: null,
53
+ }
54
+ } else if (url.startsWith("/@")) {
55
+ return {
56
+ httpResponse: null,
57
+ }
58
+ }
59
+ const u = new URL(url, "http://localhost")
60
+ const pathSegments = u.pathname.split("/").filter(Boolean)
61
+ let routeMatch = matchRoute(ctx.pages, pathSegments)
62
+
63
+ if (!routeMatch) {
64
+ // Try to find a 404 page in parent directories
65
+ const fourOhFourMatch = match404Route(ctx.pages, pathSegments)
66
+ if (fourOhFourMatch) {
67
+ routeMatch = fourOhFourMatch
68
+ } else {
69
+ // Fallback to root 404 or default fallback
70
+ if (url === "/404") {
71
+ if (__DEV__) {
72
+ console.warn(
73
+ "[kiru/router]: No 404 route defined. Using fallback 404 page."
74
+ )
75
+ }
76
+ return {
77
+ httpResponse: {
78
+ statusCode: 404,
79
+ headers: [["Content-Type", "text/html"]],
80
+ html: "<!doctype html><html><head><title>Not Found</title></head><body><h1>404</h1></body></html>",
81
+ stream: null,
82
+ },
83
+ }
84
+ }
85
+ // Recursively render the 404 page
86
+ const notFoundResponse = await render("/404", ctx)
87
+ return {
88
+ httpResponse: {
89
+ html: notFoundResponse.httpResponse?.html ?? "",
90
+ headers: notFoundResponse.httpResponse?.headers ?? [
91
+ ["Content-Type", "text/html"],
92
+ ],
93
+ ...notFoundResponse,
94
+ statusCode: 404,
95
+ stream: null,
96
+ },
97
+ }
98
+ }
99
+ }
100
+ const { pageEntry, routeSegments, params } = routeMatch
101
+ const is404Route = routeMatch.routeSegments.includes("404")
102
+
103
+ const guardEntries = matchModules(ctx.guards, routeSegments)
104
+ const guardModules = await Promise.all(
105
+ guardEntries.map((entry) => entry.load() as unknown as Promise<GuardModule>)
106
+ )
107
+
108
+ const redirectPath = await runBeforeEachGuards(
109
+ guardModules,
110
+ { ...ctx.userContext },
111
+ u.pathname
112
+ )
113
+
114
+ if (redirectPath !== null) {
115
+ return createRedirectResult(redirectPath)
116
+ }
117
+
118
+ const layoutEntries = matchModules(ctx.layouts, routeSegments)
119
+
120
+ // Register all modules for CSS collection
121
+ ;[pageEntry, ...layoutEntries].forEach((e) => {
122
+ ctx.registerModule(e.filePath)
123
+ })
124
+
125
+ const [page, ...layouts] = await Promise.all([
126
+ pageEntry.load(),
127
+ ...layoutEntries.map((layoutEntry) => layoutEntry.load()),
128
+ ])
129
+
130
+ const onBeforeEnter = page.config?.hooks?.onBeforeEnter
131
+ if (onBeforeEnter) {
132
+ const redirectPath = await runBeforeEnterHooks(
133
+ toArray(onBeforeEnter),
134
+ { ...ctx.userContext },
135
+ u.pathname
136
+ )
137
+ if (redirectPath) {
138
+ return createRedirectResult(redirectPath)
139
+ }
140
+ }
141
+
142
+ const query = parseQuery(u.search)
143
+
144
+ let props = {} as PageProps<PageConfig>
145
+ const config = page.config ?? {}
146
+ const abortController = new AbortController()
147
+
148
+ // PageConfig loaders don't run on the server
149
+ if (config.loader) {
150
+ props = {
151
+ data: null,
152
+ error: null,
153
+ loading: true,
154
+ }
155
+ }
156
+
157
+ let documentShell = renderToString(createElement(ctx.Document))
158
+
159
+ if (
160
+ documentShell.includes("</body>") ||
161
+ !documentShell.includes("<kiru-body-outlet>")
162
+ ) {
163
+ throw new Error(
164
+ "[kiru/router]: Document is expected to contain a <Body.Outlet> element. See https://kirujs.dev/docs/api/file-router#general-usage"
165
+ )
166
+ }
167
+
168
+ const children = wrapWithLayouts(
169
+ layouts
170
+ .map((layout) => layout.default)
171
+ .filter((l) => typeof l === "function"),
172
+ page.default,
173
+ props
174
+ )
175
+
176
+ const routerContextValue = {
177
+ state: {
178
+ params,
179
+ query,
180
+ pathname: u.pathname,
181
+ hash: "",
182
+ signal: abortController.signal,
183
+ } satisfies RouterState,
184
+ }
185
+
186
+ const app = createElement(RouterContext.Provider, {
187
+ children: createElement(RequestContext.Provider, {
188
+ children: Fragment({ children }),
189
+ value: ctx.userContext,
190
+ }),
191
+ value: routerContextValue,
192
+ })
193
+
194
+ let { immediate: pageOutletContent, stream } = renderToReadableStream(app)
195
+ const hasHeadContent = pageOutletContent.includes("<kiru-head-content>")
196
+ const hasHeadOutlet = documentShell.includes("<kiru-head-outlet>")
197
+
198
+ if (hasHeadOutlet && hasHeadContent) {
199
+ let [preHeadContent = "", headContentInner = "", postHeadContent = ""] =
200
+ pageOutletContent.split(/<kiru-head-content>|<\/kiru-head-content>/)
201
+
202
+ documentShell = documentShell.replace(
203
+ "<kiru-head-outlet>",
204
+ headContentInner
205
+ )
206
+ pageOutletContent = `${preHeadContent}${postHeadContent}`
207
+ } else if (hasHeadContent) {
208
+ // remove head content element and everything within it
209
+ pageOutletContent = pageOutletContent.replace(
210
+ /<kiru-head-content>(.*?)<\/kiru-head-content>/,
211
+ ""
212
+ )
213
+ } else if (hasHeadOutlet) {
214
+ // remove head outlet element and everything within it
215
+ documentShell = documentShell.replaceAll("<kiru-head-outlet>", "")
216
+ }
217
+
218
+ const [prePageOutlet, postPageOutlet] =
219
+ documentShell.split("<kiru-body-outlet>")
220
+
221
+ const html = `<!DOCTYPE html>${prePageOutlet}<body>${pageOutletContent}</body>${postPageOutlet}`
222
+ const statusCode = is404Route ? 404 : 200
223
+
224
+ queueMicrotask(() => {
225
+ runAfterEachGuards(guardModules, { ...ctx.userContext }, u.pathname)
226
+ })
227
+
228
+ return {
229
+ httpResponse: {
230
+ html,
231
+ statusCode,
232
+ headers: [["Content-Type", "text/html;charset=utf-8"]],
233
+ stream,
234
+ },
235
+ }
236
+ }
237
+
238
+ function createRedirectResult(to: string): SSRRenderResult {
239
+ return {
240
+ httpResponse: {
241
+ statusCode: 302,
242
+ headers: [["Location", to]],
243
+ html: "",
244
+ stream: null,
245
+ },
246
+ }
247
+ }
@@ -1,5 +1,6 @@
1
1
  import type { FileRouterContextType } from "./context"
2
2
  import type { PageConfig } from "./types"
3
+ import type { NavGuardBuilder } from "./guard"
3
4
 
4
5
  export interface CurrentPage {
5
6
  component: Kiru.FC<any>
@@ -20,6 +21,10 @@ export interface PageModule {
20
21
  >
21
22
  }
22
23
 
24
+ export interface GuardModule {
25
+ guard: NavGuardBuilder
26
+ }
27
+
23
28
  export interface ViteImportMap {
24
29
  [fp: string]: () => Promise<DefaultComponentModule>
25
30
  }
@@ -3,12 +3,14 @@ import type { FileRouterDataLoadError } from "./errors"
3
3
  import type {
4
4
  DefaultComponentModule,
5
5
  FormattedViteImportMap,
6
+ GuardModule,
6
7
  PageModule,
7
8
  } from "./types.internal"
8
9
 
9
10
  export interface FileRouterPreloadConfig {
10
- pages: FormattedViteImportMap
11
+ pages: FormattedViteImportMap<PageModule>
11
12
  layouts: FormattedViteImportMap
13
+ guards?: FormattedViteImportMap<GuardModule>
12
14
  page: PageModule
13
15
  pageProps: Record<string, unknown>
14
16
  pageLayouts: DefaultComponentModule[]
@@ -40,6 +42,14 @@ export interface FileRouterConfig {
40
42
  * ```
41
43
  */
42
44
  layouts: Record<string, unknown>
45
+ /**
46
+ * The import map to use for loading nav guards
47
+ * @example
48
+ * ```tsx
49
+ * <FileRouter config={{ guards: import.meta.glob("/∗∗/guard.{ts,js}"), ... }} />
50
+ * ```
51
+ */
52
+ guards?: Record<string, unknown>
43
53
 
44
54
  /**
45
55
  * The base url to use as a prefix for route matching
@@ -98,18 +108,34 @@ export interface RouterState {
98
108
  signal: AbortSignal
99
109
  }
100
110
 
101
- type PageDataLoaderContext = RouterState & {}
102
-
103
111
  export interface PageDataLoaderCacheConfig {
104
112
  type: "memory" | "localStorage" | "sessionStorage"
105
113
  ttl: number
106
114
  }
107
115
 
116
+ interface LoaderContext extends RouterState {
117
+ /**
118
+ * The request context - in SSR, this is the data from the server
119
+ * that's passed to the `renderPage` function
120
+ * @example
121
+ * ```ts
122
+ * // server.ts
123
+ * renderPage({ url, context: { test: 123 } })
124
+ *
125
+ * // page.tsx
126
+ * loader: {
127
+ * load: ({ context }) => context.test
128
+ * }
129
+ * ```
130
+ */
131
+ context: Kiru.RequestContext
132
+ }
133
+
108
134
  export type PageDataLoaderConfig<T = unknown> = {
109
135
  /**
110
136
  * The function to load the page data
111
137
  */
112
- load: (context: PageDataLoaderContext) => Promise<T>
138
+ load: (context: LoaderContext) => Promise<T>
113
139
  } & (
114
140
  | {
115
141
  /**
@@ -149,6 +175,22 @@ export type PageDataLoaderConfig<T = unknown> = {
149
175
  }
150
176
  )
151
177
 
178
+ export type NavigationHook<T> = (
179
+ context: Kiru.RequestContext,
180
+ to: string,
181
+ from: string
182
+ ) => T
183
+
184
+ export type OnBeforeEnterHook = NavigationHook<
185
+ string | void | Promise<string | void>
186
+ >
187
+ export type OnBeforeLeaveHook = NavigationHook<false | void>
188
+
189
+ interface PageContextHooks {
190
+ onBeforeEnter?: OnBeforeEnterHook | OnBeforeEnterHook[]
191
+ onBeforeLeave?: OnBeforeLeaveHook | OnBeforeLeaveHook[]
192
+ }
193
+
152
194
  export interface PageConfig<T = unknown> {
153
195
  /**
154
196
  * The loader configuration for this page
@@ -159,6 +201,8 @@ export interface PageConfig<T = unknown> {
159
201
  * returned, a page will be generated
160
202
  */
161
203
  generateStaticParams?: () => RouteParams[] | Promise<RouteParams[]>
204
+
205
+ hooks?: PageContextHooks
162
206
  }
163
207
 
164
208
  export type PageProps<T extends PageConfig<any>> = T extends PageConfig<infer U>
@@ -1,19 +1,27 @@
1
1
  import { createElement } from "../../element.js"
2
2
  import { __DEV__ } from "../../env.js"
3
+ import { resolveNavguard } from "../guard.js"
3
4
  import type {
5
+ DefaultComponentModule,
4
6
  FormattedViteImportMap,
7
+ GuardModule,
5
8
  RouteMatch,
6
9
  ViteImportMap,
7
10
  } from "../types.internal"
11
+ import { OnBeforeEnterHook, OnBeforeLeaveHook } from "../types.js"
8
12
 
9
13
  export {
10
14
  formatViteImportMap,
11
15
  matchRoute,
12
16
  match404Route,
13
- matchLayouts,
17
+ matchModules,
14
18
  normalizePrefixPath,
15
19
  parseQuery,
16
20
  wrapWithLayouts,
21
+ runBeforeLeaveHooks,
22
+ runBeforeEnterHooks,
23
+ runBeforeEachGuards,
24
+ runAfterEachGuards,
17
25
  }
18
26
 
19
27
  function formatViteImportMap(
@@ -177,20 +185,20 @@ function match404Route(
177
185
  return null
178
186
  }
179
187
 
180
- function matchLayouts(
181
- layouts: FormattedViteImportMap,
188
+ function matchModules<T = DefaultComponentModule>(
189
+ modules: FormattedViteImportMap<T>,
182
190
  routeSegments: string[]
183
191
  ) {
184
192
  return ["/", ...routeSegments].reduce((acc, _, i) => {
185
- const layoutPath = "/" + routeSegments.slice(0, i).join("/")
186
- const layout = layouts[layoutPath]
193
+ const modulePath = "/" + routeSegments.slice(0, i).join("/")
194
+ const module = modules[modulePath]
187
195
 
188
- if (!layout) {
196
+ if (!module) {
189
197
  return acc
190
198
  }
191
199
 
192
- return [...acc, layout]
193
- }, [] as FormattedViteImportMap[string][])
200
+ return [...acc, module]
201
+ }, [] as FormattedViteImportMap<T>[string][])
194
202
  }
195
203
 
196
204
  function normalizePrefixPath(path: string) {
@@ -239,3 +247,61 @@ function wrapWithLayouts(
239
247
  createElement(page, props)
240
248
  )
241
249
  }
250
+
251
+ function runBeforeLeaveHooks(
252
+ hooks: OnBeforeLeaveHook[],
253
+ context: Kiru.RequestContext,
254
+ to: string,
255
+ from: string = to
256
+ ): false | void {
257
+ for (const hook of hooks) {
258
+ const res = hook(context, to, from)
259
+ if (res === false) {
260
+ return false
261
+ }
262
+ }
263
+ }
264
+
265
+ async function runBeforeEnterHooks(
266
+ hooks: OnBeforeEnterHook[],
267
+ context: Kiru.RequestContext,
268
+ to: string,
269
+ from: string = to
270
+ ) {
271
+ for (const hook of hooks) {
272
+ const result = await hook(context, to, from)
273
+ if (typeof result === "string") {
274
+ return result
275
+ }
276
+ }
277
+
278
+ return null
279
+ }
280
+
281
+ async function runBeforeEachGuards(
282
+ guardModules: GuardModule[],
283
+ context: Kiru.RequestContext,
284
+ to: string,
285
+ from: string = to
286
+ ): Promise<string | null> {
287
+ const beforeHooks = guardModules
288
+ .map((guardModule) => resolveNavguard(guardModule)?.beforeEach)
289
+ .filter((x) => typeof x === "function")
290
+
291
+ return runBeforeEnterHooks(beforeHooks, context, to, from)
292
+ }
293
+
294
+ async function runAfterEachGuards(
295
+ guardModules: GuardModule[],
296
+ context: Kiru.RequestContext,
297
+ to: string,
298
+ from: string = to
299
+ ): Promise<void> {
300
+ const afterHooks = guardModules
301
+ .map((guardModule) => resolveNavguard(guardModule)?.afterEach)
302
+ .filter((x) => typeof x === "function")
303
+
304
+ for (const hook of afterHooks) {
305
+ await hook(context, to, from)
306
+ }
307
+ }
package/src/ssr/client.ts CHANGED
@@ -10,7 +10,7 @@ interface HydrationAppContextOptions extends AppContextOptions {
10
10
  * - "dynamic": SSR with lazy promise hydration
11
11
  * @default "dynamic"
12
12
  */
13
- hydrationMode?: "static" | "dynamic"
13
+ hydrationMode?: Kiru.HydrationMode
14
14
  }
15
15
 
16
16
  export function hydrate(
package/src/ssr/server.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Readable } from "node:stream"
2
1
  import { Fragment } from "../element.js"
3
2
  import { renderMode } from "../globals.js"
4
3
  import { STREAMED_DATA_EVENT } from "../constants.js"
@@ -19,16 +18,15 @@ d.querySelectorAll("[k-data]").forEach((p) => {
19
18
  });
20
19
  d.currentScript.remove()
21
20
  </script>
22
- `
21
+ `.replace(/\s+/g, " ")
23
22
 
24
23
  export function renderToReadableStream(element: JSX.Element): {
25
24
  immediate: string
26
- stream: Readable
25
+ stream: ReadableStream | null
27
26
  } {
28
- const stream = new Readable({ read() {} })
29
- const rootNode = Fragment({ children: element })
30
27
  const streamPromises = new Set<Kiru.StatefulPromise<unknown>>()
31
- const pendingWritePromises: Promise<void>[] = []
28
+ const dataPromises: Promise<string>[] = []
29
+ let stream: ReadableStream | null = null
32
30
 
33
31
  let immediate = ""
34
32
 
@@ -39,33 +37,33 @@ export function renderToReadableStream(element: JSX.Element): {
39
37
  if (streamPromises.has(promise)) continue
40
38
  streamPromises.add(promise)
41
39
 
42
- const writePromise = promise
40
+ const dataPromise = promise
43
41
  .then(() => ({ data: promise.value }))
44
42
  .catch(() => ({ error: promise.error?.message }))
45
- .then((value) => {
46
- const content = JSON.stringify(value)
47
- stream.push(
43
+ .then(
44
+ (value, content = JSON.stringify(value)) =>
48
45
  `<script id="${promise.id}" k-data type="application/json">${content}</script>`
49
- )
50
- })
51
-
52
- pendingWritePromises.push(writePromise)
46
+ )
47
+ dataPromises.push(dataPromise)
53
48
  }
54
49
  },
55
50
  }
56
51
 
57
52
  const prev = renderMode.current
58
53
  renderMode.current = "stream"
59
- headlessRender(ctx, rootNode, null, 0)
54
+ headlessRender(ctx, Fragment({ children: element }), null, 0)
60
55
  renderMode.current = prev
61
56
 
62
- if (pendingWritePromises.length > 0) {
63
- Promise.all(pendingWritePromises).then(() => {
64
- stream.push(STREAMED_DATA_SETUP)
65
- stream.push(null)
57
+ if (dataPromises.length > 0) {
58
+ stream = new ReadableStream({
59
+ async pull(controller) {
60
+ for await (const chunk of dataPromises) {
61
+ controller.enqueue(chunk)
62
+ }
63
+ controller.enqueue(STREAMED_DATA_SETUP)
64
+ controller.close()
65
+ },
66
66
  })
67
- } else {
68
- stream.push(null)
69
67
  }
70
68
 
71
69
  return { immediate, stream }
package/src/types.ts CHANGED
@@ -117,6 +117,8 @@ declare global {
117
117
  }
118
118
  }
119
119
  namespace Kiru {
120
+ interface RequestContext {}
121
+
120
122
  interface CustomEvents {}
121
123
 
122
124
  interface ProviderProps<T> {
@@ -173,6 +175,7 @@ declare global {
173
175
 
174
176
  interface StatefulPromise<T> extends Promise<T>, PromiseState<T> {}
175
177
 
178
+ type HydrationMode = "static" | "dynamic"
176
179
  type RenderMode = "dom" | "hydrate" | "string" | "stream"
177
180
 
178
181
  type StateSetter<T> = T | ((prev: T) => T)
@@ -2,6 +2,7 @@ import { unwrap } from "../signals/utils.js"
2
2
  import { booleanAttributes, snakeCaseAttributes } from "../constants.js"
3
3
 
4
4
  export {
5
+ toArray,
5
6
  className,
6
7
  encodeHtmlEntities,
7
8
  propFilters,
@@ -20,6 +21,10 @@ const REGEX_DBLQT = /"/g
20
21
  const REGEX_SLASH = /\//g
21
22
  const REGEX_ALPHA_UPPER = /[A-Z]/g
22
23
 
24
+ function toArray<T>(value: T | T[]): T[] {
25
+ return Array.isArray(value) ? value : [value]
26
+ }
27
+
23
28
  function className(...classes: (string | false | null | undefined)[]): string {
24
29
  return classes.filter(Boolean).join(" ")
25
30
  }
@@ -46,7 +46,7 @@ function setRef<T>(ref: Kiru.Ref<T>, value: T): void {
46
46
  }
47
47
 
48
48
  /**
49
- * Returns false if called during "stream" or "string" render modes.
49
+ * Returns true if called during 'dom' or 'hydrate' mode.
50
50
  */
51
51
  function sideEffectsEnabled(): boolean {
52
52
  return renderMode.current === "dom" || renderMode.current === "hydrate"
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/router/server/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,sBAAsB,EAAc,MAAM,sBAAsB,CAAA;AAKzE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,sBAAsB,CAAA;IAC7B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAA;IACjB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,0BAA0B,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;CACrE;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,MAAM,CAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,aAAa,EAClB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,YAAY,CAAC,CAuJvB;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,mCAmCtE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/router/server/index.ts"],"names":[],"mappings":"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"}