kiru 0.53.0 → 0.54.0-preview.1

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 (118) hide show
  1. package/dist/components/derive.d.ts +1 -1
  2. package/dist/components/derive.d.ts.map +1 -1
  3. package/dist/components/derive.js +3 -2
  4. package/dist/components/derive.js.map +1 -1
  5. package/dist/dom.d.ts.map +1 -1
  6. package/dist/dom.js +6 -2
  7. package/dist/dom.js.map +1 -1
  8. package/dist/globals.d.ts +1 -1
  9. package/dist/globals.d.ts.map +1 -1
  10. package/dist/globals.js.map +1 -1
  11. package/dist/hooks/usePromise.d.ts +2 -1
  12. package/dist/hooks/usePromise.d.ts.map +1 -1
  13. package/dist/hooks/usePromise.js +31 -62
  14. package/dist/hooks/usePromise.js.map +1 -1
  15. package/dist/index.js +1 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/router/client/index.d.ts +4 -2
  18. package/dist/router/client/index.d.ts.map +1 -1
  19. package/dist/router/client/index.js +59 -13
  20. package/dist/router/client/index.js.map +1 -1
  21. package/dist/router/constants.d.ts +2 -0
  22. package/dist/router/constants.d.ts.map +1 -0
  23. package/dist/router/constants.js +2 -0
  24. package/dist/router/constants.js.map +1 -0
  25. package/dist/router/context.d.ts +2 -0
  26. package/dist/router/context.d.ts.map +1 -1
  27. package/dist/router/context.js +5 -1
  28. package/dist/router/context.js.map +1 -1
  29. package/dist/router/fileRouterController.d.ts +2 -0
  30. package/dist/router/fileRouterController.d.ts.map +1 -1
  31. package/dist/router/fileRouterController.js +195 -107
  32. package/dist/router/fileRouterController.js.map +1 -1
  33. package/dist/router/globals.d.ts +3 -0
  34. package/dist/router/globals.d.ts.map +1 -1
  35. package/dist/router/globals.js +3 -0
  36. package/dist/router/globals.js.map +1 -1
  37. package/dist/router/guard.d.ts +17 -0
  38. package/dist/router/guard.d.ts.map +1 -0
  39. package/dist/router/guard.js +45 -0
  40. package/dist/router/guard.js.map +1 -0
  41. package/dist/router/head.d.ts.map +1 -1
  42. package/dist/router/head.js +5 -7
  43. package/dist/router/head.js.map +1 -1
  44. package/dist/router/index.d.ts +2 -1
  45. package/dist/router/index.d.ts.map +1 -1
  46. package/dist/router/index.js +2 -1
  47. package/dist/router/index.js.map +1 -1
  48. package/dist/router/{server → ssg}/index.d.ts +4 -3
  49. package/dist/router/ssg/index.d.ts.map +1 -0
  50. package/dist/router/{server → ssg}/index.js +8 -5
  51. package/dist/router/ssg/index.js.map +1 -0
  52. package/dist/router/ssr/index.d.ts +20 -0
  53. package/dist/router/ssr/index.d.ts.map +1 -0
  54. package/dist/router/ssr/index.js +163 -0
  55. package/dist/router/ssr/index.js.map +1 -0
  56. package/dist/router/types.d.ts +42 -16
  57. package/dist/router/types.d.ts.map +1 -1
  58. package/dist/router/types.internal.d.ts +4 -0
  59. package/dist/router/types.internal.d.ts.map +1 -1
  60. package/dist/router/utils/index.d.ts +8 -3
  61. package/dist/router/utils/index.d.ts.map +1 -1
  62. package/dist/router/utils/index.js +38 -6
  63. package/dist/router/utils/index.js.map +1 -1
  64. package/dist/scheduler.d.ts +14 -3
  65. package/dist/scheduler.d.ts.map +1 -1
  66. package/dist/scheduler.js +3 -4
  67. package/dist/scheduler.js.map +1 -1
  68. package/dist/ssr/client.d.ts +1 -1
  69. package/dist/ssr/client.d.ts.map +1 -1
  70. package/dist/ssr/server.d.ts +9 -3
  71. package/dist/ssr/server.d.ts.map +1 -1
  72. package/dist/ssr/server.js +37 -30
  73. package/dist/ssr/server.js.map +1 -1
  74. package/dist/types.d.ts +3 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/utils/format.d.ts +2 -1
  77. package/dist/utils/format.d.ts.map +1 -1
  78. package/dist/utils/format.js +4 -1
  79. package/dist/utils/format.js.map +1 -1
  80. package/dist/utils/index.d.ts +1 -1
  81. package/dist/utils/index.d.ts.map +1 -1
  82. package/dist/utils/index.js +1 -1
  83. package/dist/utils/index.js.map +1 -1
  84. package/dist/utils/promise.d.ts +2 -0
  85. package/dist/utils/promise.d.ts.map +1 -1
  86. package/dist/utils/promise.js +45 -1
  87. package/dist/utils/promise.js.map +1 -1
  88. package/dist/utils/runtime.d.ts +1 -1
  89. package/dist/utils/runtime.js +1 -1
  90. package/package.json +8 -4
  91. package/src/components/derive.ts +5 -3
  92. package/src/dom.ts +5 -1
  93. package/src/globals.ts +1 -1
  94. package/src/hooks/usePromise.ts +57 -77
  95. package/src/index.ts +1 -1
  96. package/src/router/client/index.ts +114 -16
  97. package/src/router/constants.ts +1 -0
  98. package/src/router/context.ts +7 -1
  99. package/src/router/fileRouterController.ts +304 -132
  100. package/src/router/globals.ts +4 -0
  101. package/src/router/guard.ts +72 -0
  102. package/src/router/head.ts +5 -7
  103. package/src/router/index.ts +12 -1
  104. package/src/router/{server → ssg}/index.ts +17 -10
  105. package/src/router/ssr/index.ts +252 -0
  106. package/src/router/types.internal.ts +5 -0
  107. package/src/router/types.ts +53 -16
  108. package/src/router/utils/index.ts +74 -8
  109. package/src/scheduler.ts +20 -3
  110. package/src/ssr/client.ts +1 -1
  111. package/src/ssr/server.ts +58 -34
  112. package/src/types.ts +3 -0
  113. package/src/utils/format.ts +5 -0
  114. package/src/utils/index.ts +1 -1
  115. package/src/utils/promise.ts +70 -1
  116. package/src/utils/runtime.ts +1 -1
  117. package/dist/router/server/index.d.ts.map +0 -1
  118. package/dist/router/server/index.js.map +0 -1
@@ -39,7 +39,7 @@ function setRef(ref, value) {
39
39
  ref.current = value;
40
40
  }
41
41
  /**
42
- * Returns false if called during "stream" or "string" render modes.
42
+ * Returns true if called during 'dom' or 'hydrate' mode.
43
43
  */
44
44
  function sideEffectsEnabled() {
45
45
  return renderMode.current === "dom" || renderMode.current === "hydrate";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiru",
3
- "version": "0.53.0",
3
+ "version": "0.54.0-preview.1",
4
4
  "description": "A batteries-included, easy-to-use rendering library with a tiny footprint",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -34,9 +34,13 @@
34
34
  "types": "./dist/router/client/index.d.ts",
35
35
  "default": "./dist/router/client/index.js"
36
36
  },
37
- "./router/server": {
38
- "types": "./dist/router/server/index.d.ts",
39
- "default": "./dist/router/server/index.js"
37
+ "./router/ssg": {
38
+ "types": "./dist/router/ssg/index.d.ts",
39
+ "default": "./dist/router/ssg/index.js"
40
+ },
41
+ "./router/ssr": {
42
+ "types": "./dist/router/ssr/index.d.ts",
43
+ "default": "./dist/router/ssr/index.js"
40
44
  },
41
45
  "./router/utils": {
42
46
  "types": "./dist/router/utils/index.d.ts",
@@ -7,6 +7,8 @@ import { sideEffectsEnabled } from "../utils/index.js"
7
7
  import type { RecordHas } from "../types.utils"
8
8
  import { isStatefulPromise, StreamDataThrowValue } from "../utils/promise.js"
9
9
 
10
+ const $NO_VALUE = Symbol("no value")
11
+
10
12
  export type Derivable =
11
13
  | Kiru.Signal<unknown>
12
14
  | Kiru.StatefulPromise<unknown>
@@ -36,7 +38,7 @@ export type DeriveFallbackMode = "swr" | "fallback"
36
38
 
37
39
  export interface DeriveProps<
38
40
  T extends Derivable,
39
- Mode extends DeriveFallbackMode = "fallback"
41
+ Mode extends DeriveFallbackMode = "swr"
40
42
  > {
41
43
  from: T
42
44
  mode?: Mode
@@ -65,7 +67,7 @@ export function Derive<
65
67
  U extends DeriveFallbackMode = "swr"
66
68
  >(props: DeriveProps<T, U>) {
67
69
  const { from, children, fallback, mode } = props
68
- const prevSuccess = useRef<UnwrapDerive<T> | null>(null)
70
+ const prevSuccess = useRef<UnwrapDerive<T> | typeof $NO_VALUE>($NO_VALUE)
69
71
 
70
72
  const promises = new Set<Kiru.StatefulPromise<any>>()
71
73
  let value: UnwrapDerive<T>
@@ -106,7 +108,7 @@ export function Derive<
106
108
  const nodeRef = node.current!
107
109
  Promise.allSettled(promises).then(() => requestUpdate(nodeRef))
108
110
 
109
- if (mode !== "fallback" && prevSuccess.current) {
111
+ if (mode !== "fallback" && prevSuccess.current !== $NO_VALUE) {
110
112
  return (children as ChildFnWithStale<UnwrapDerive<T>>)(
111
113
  prevSuccess.current,
112
114
  true
package/src/dom.ts CHANGED
@@ -699,7 +699,11 @@ function commitDeletion(vNode: VNode) {
699
699
 
700
700
  subs?.forEach((unsub) => unsub())
701
701
  if (cleanups) Object.values(cleanups).forEach((c) => c())
702
- while (hooks?.length) hooks.pop()!.cleanup?.()
702
+ while (hooks?.length) {
703
+ try {
704
+ hooks.pop()!.cleanup?.()
705
+ } catch {}
706
+ }
703
707
 
704
708
  if (__DEV__) {
705
709
  window.__kiru.profilingContext?.emit("removeNode", ctx)
package/src/globals.ts CHANGED
@@ -13,5 +13,5 @@ const renderMode = {
13
13
  }
14
14
 
15
15
  const hydrationMode = {
16
- current: "dynamic" as "static" | "dynamic",
16
+ current: "dynamic" as Kiru.HydrationMode,
17
17
  }
@@ -1,16 +1,21 @@
1
- import { STREAMED_DATA_EVENT } from "../constants.js"
2
1
  import { __DEV__ } from "../env.js"
3
2
  import { hydrationMode, renderMode } from "../globals.js"
4
3
  import { Signal, useSignal } from "../signals/base.js"
5
- import { cleanupHook, depsRequireChange, useHook } from "./utils.js"
4
+ import { depsRequireChange, useHook } from "./utils.js"
6
5
  import { useId } from "./useId.js"
6
+ import {
7
+ createStatefulPromise,
8
+ resolveStreamedPromise,
9
+ } from "../utils/promise.js"
7
10
 
8
11
  export { usePromise }
9
12
 
10
13
  const nodeToPromiseIndex = new WeakMap<Kiru.VNode, number>()
11
14
 
15
+ type PromiseMutator = (signal: AbortSignal) => unknown | Promise<unknown>
16
+
12
17
  type UsePromiseResult<T> = Kiru.StatefulPromise<T> & {
13
- refresh: () => void
18
+ refresh: (mutator?: PromiseMutator) => void
14
19
  isPending: Signal<boolean>
15
20
  }
16
21
 
@@ -18,7 +23,7 @@ function usePromise<T>(
18
23
  callback: (signal: AbortSignal) => Promise<T>,
19
24
  deps: unknown[]
20
25
  ): UsePromiseResult<T> {
21
- const id = useId()
26
+ const vNodeId = useId()
22
27
  const isPending = useSignal(true)
23
28
 
24
29
  return useHook(
@@ -27,24 +32,54 @@ function usePromise<T>(
27
32
  promise: UsePromiseResult<T>
28
33
  abortController?: AbortController
29
34
  deps?: unknown[]
35
+ refresh: (mutator?: PromiseMutator) => void
36
+ promiseId: string
37
+ epoch: number
30
38
  },
31
39
  ({ hook, isInit, vNode, update }) => {
32
- if (isInit || depsRequireChange(deps, hook.deps)) {
33
- isPending.value = true
34
- hook.deps = deps
35
- cleanupHook(hook)
36
-
37
- const controller = (hook.abortController = new AbortController())
38
- hook.cleanup = () => controller.abort()
40
+ if (isInit) {
41
+ hook.epoch = 0
42
+ hook.cleanup = () => hook.abortController?.abort("aborted")
39
43
 
40
44
  const index = nodeToPromiseIndex.get(vNode) ?? 0
41
45
  nodeToPromiseIndex.set(vNode, index + 1)
46
+ const promiseId = (hook.promiseId = `${vNodeId}:data:${index}`)
47
+
48
+ const refresh = (hook.refresh = (mutator?: PromiseMutator) => {
49
+ if (typeof mutator !== "function") {
50
+ delete hook.deps
51
+ return update()
52
+ }
53
+
54
+ hook.cleanup!()
55
+ const signal = (hook.abortController = new AbortController()).signal
56
+ const promise = Promise.try(mutator, signal).then(() =>
57
+ callback(signal)
58
+ )
59
+ const epoch = ++hook.epoch
60
+ hook.promise = createStatefulPromise(
61
+ promiseId,
62
+ promise,
63
+ { isPending, refresh },
64
+ () => epoch === hook.epoch && (isPending.value = false)
65
+ )
66
+
67
+ isPending.value = true
68
+ update()
69
+ })
70
+ }
42
71
 
43
- const promiseId = `${id}:data:${index}`
72
+ if (isInit || depsRequireChange(deps, hook.deps)) {
73
+ isPending.value = true
74
+ hook.deps = deps
75
+ hook.cleanup!()
76
+
77
+ const signal = (hook.abortController = new AbortController()).signal
78
+ const { promiseId, refresh } = hook
44
79
 
45
80
  let promise: Promise<T>
46
81
  if (renderMode.current === "string") {
47
- // if we're rendering to a string, there's no need to fire the callback
82
+ // if we're rendering to string, there's no need to fire the callback
48
83
  promise = Promise.resolve() as Promise<T>
49
84
  } else if (
50
85
  renderMode.current === "hydrate" &&
@@ -52,76 +87,21 @@ function usePromise<T>(
52
87
  ) {
53
88
  // if we're hydrating and the hydration mode is not static,
54
89
  // we need to resolve the promise from cache/event
55
- promise = resolveDeferredPromise<T>(promiseId, controller.signal)
90
+ promise = resolveStreamedPromise<T>(promiseId, signal)
56
91
  } else {
57
92
  // dom / stream / (hydrate + static)
58
- promise = callback(controller.signal)
93
+ promise = callback(signal)
59
94
  }
60
95
 
61
- const state: Kiru.PromiseState<T> = { id: promiseId, state: "pending" }
62
- const statefulPromise: Kiru.StatefulPromise<T> = (hook.promise =
63
- Object.assign(promise, state, {
64
- isPending,
65
- refresh: () => {
66
- hook.deps = undefined
67
- update()
68
- },
69
- }))
70
-
71
- statefulPromise
72
- .then((value) => {
73
- statefulPromise.state = "fulfilled"
74
- statefulPromise.value = value
75
- isPending.value = false
76
- })
77
- .catch((error) => {
78
- statefulPromise.state = "rejected"
79
- statefulPromise.error =
80
- error instanceof Error ? error : new Error(error)
81
- })
96
+ const epoch = ++hook.epoch
97
+ hook.promise = createStatefulPromise(
98
+ promiseId,
99
+ promise,
100
+ { isPending, refresh },
101
+ () => epoch === hook.epoch && (isPending.value = false)
102
+ )
82
103
  }
83
104
  return hook.promise
84
105
  }
85
106
  )
86
107
  }
87
-
88
- interface DeferredPromiseEventDetail<T> {
89
- id: string
90
- data?: T
91
- error?: string
92
- }
93
-
94
- function resolveDeferredPromise<T>(
95
- id: string,
96
- signal: AbortSignal
97
- ): Promise<T> {
98
- return new Promise<T>((resolve, reject) => {
99
- const deferralCache: Map<string, { data?: T; error?: string }> = // @ts-ignore
100
- (window[STREAMED_DATA_EVENT] ??= new Map())
101
-
102
- const existing = deferralCache.get(id)
103
- if (existing) {
104
- const { data, error } = existing
105
- deferralCache.delete(id)
106
- if (error) return reject(error)
107
- return resolve(data!)
108
- }
109
-
110
- const onDataEvent = (event: Event) => {
111
- const { detail } = event as CustomEvent<DeferredPromiseEventDetail<T>>
112
- if (detail.id === id) {
113
- deferralCache.delete(id)
114
- window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
115
- const { data, error } = detail
116
- if (error) return reject(error)
117
- resolve(data!)
118
- }
119
- }
120
-
121
- window.addEventListener(STREAMED_DATA_EVENT, onDataEvent)
122
- signal.addEventListener("abort", () => {
123
- window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
124
- reject()
125
- })
126
- })
127
- }
package/src/index.ts CHANGED
@@ -16,5 +16,5 @@ export * from "./store.js"
16
16
 
17
17
  // @ts-ignore
18
18
  if ("window" in globalThis && !globalThis.__KIRU_DEVTOOLS__) {
19
- globalThis.window.__kiru ??= createKiruGlobalContext()
19
+ window.__kiru ??= createKiruGlobalContext()
20
20
  }
@@ -1,45 +1,91 @@
1
- // initClient({ dir, baseUrl, pages, layouts })
2
-
3
1
  import { createElement } from "../../element.js"
4
2
  import { hydrate } from "../../ssr/client.js"
5
3
  import { FileRouter } from "../fileRouter.js"
4
+ import { toArray } from "../../utils/format.js"
5
+ import { resolveStreamedPromise } from "../../utils/promise.js"
6
6
  import {
7
- matchLayouts,
7
+ matchModules,
8
8
  matchRoute,
9
9
  match404Route,
10
10
  parseQuery,
11
+ runBeforeEachGuards,
12
+ runAfterEachGuards,
13
+ runBeforeEnterHooks,
11
14
  } from "../utils/index.js"
12
- import type { FormattedViteImportMap, PageModule } from "../types.internal"
15
+ import type {
16
+ FormattedViteImportMap,
17
+ GuardModule,
18
+ PageModule,
19
+ } from "../types.internal"
13
20
  import type { FileRouterConfig, FileRouterPreloadConfig } from "../types"
14
- import { fileRouterInstance, fileRouterRoute, routerCache } from "../globals.js"
21
+ import {
22
+ fileRouterInstance,
23
+ fileRouterRoute,
24
+ requestContext,
25
+ routerCache,
26
+ } from "../globals.js"
15
27
  import { FileRouterController } from "../fileRouterController.js"
16
28
  import { FileRouterDataLoadError } from "../errors.js"
17
29
  import { __DEV__ } from "../../env.js"
18
30
  import { RouterCache } from "../cache.js"
31
+ import { RequestContext } from "../context.js"
32
+ import { PAGE_DATA_PROMISE_ID } from "../constants.js"
33
+ import { AsyncTaskState } from "../../types.js"
19
34
 
20
35
  interface InitClientOptions {
21
36
  dir: string
22
37
  baseUrl: string
23
- pages: FormattedViteImportMap
38
+ pages: FormattedViteImportMap<PageModule>
24
39
  layouts: FormattedViteImportMap
40
+ guards?: FormattedViteImportMap<GuardModule>
25
41
  transition: boolean
42
+ hydrationMode?: Kiru.HydrationMode
26
43
  }
27
44
 
28
45
  export async function initClient(options: InitClientOptions) {
29
46
  routerCache.current = new RouterCache()
30
- const { dir, baseUrl, pages, layouts, transition } = options
47
+ const {
48
+ dir,
49
+ baseUrl,
50
+ pages,
51
+ layouts,
52
+ guards,
53
+ transition,
54
+ hydrationMode = "static",
55
+ } = options
31
56
 
57
+ try {
58
+ requestContext.current = JSON.parse(
59
+ document.querySelector("[k-request-context]")!.innerHTML
60
+ )
61
+ } catch {}
62
+
63
+ const preloaded = await preparePreloadConfig(
64
+ options,
65
+ false,
66
+ hydrationMode === "dynamic"
67
+ )
32
68
  const config: FileRouterConfig = {
33
69
  dir,
34
70
  baseUrl,
35
71
  pages,
36
72
  layouts,
73
+ guards,
37
74
  transition,
38
- preloaded: await preparePreloadConfig(options),
75
+ preloaded,
39
76
  }
40
77
 
41
- const app = createElement(FileRouter, { config })
42
- hydrate(app, document.body, { hydrationMode: "static" })
78
+ const children = createElement(FileRouter, { config })
79
+
80
+ const app =
81
+ hydrationMode === "static"
82
+ ? children
83
+ : createElement(RequestContext.Provider, {
84
+ value: requestContext.current,
85
+ children,
86
+ })
87
+
88
+ hydrate(app, document.body, { hydrationMode })
43
89
 
44
90
  if (__DEV__) {
45
91
  onLoadedDev()
@@ -48,7 +94,8 @@ export async function initClient(options: InitClientOptions) {
48
94
 
49
95
  async function preparePreloadConfig(
50
96
  options: InitClientOptions,
51
- isStatic404 = false
97
+ isStatic404 = false,
98
+ isSSR = false
52
99
  ): Promise<FileRouterPreloadConfig> {
53
100
  let pageProps = {}
54
101
  let cacheData: null | { value: unknown } = null
@@ -68,7 +115,25 @@ async function preparePreloadConfig(
68
115
  throw new Error(`No route defined (path: ${url.pathname}).`)
69
116
  }
70
117
 
71
- const layoutEntries = matchLayouts(options.layouts, routeMatch.routeSegments)
118
+ const layoutEntries = matchModules(options.layouts, routeMatch.routeSegments)
119
+
120
+ // Load and run guards before loading page
121
+ // if SSR, do we even need to do this?
122
+ let guardModules: GuardModule[] = []
123
+ if (options.guards) {
124
+ const guardEntries = matchModules(options.guards, routeMatch.routeSegments)
125
+ guardModules = await Promise.all(guardEntries.map((entry) => entry.load()))
126
+
127
+ const redirectPath = await runBeforeEachGuards(
128
+ guardModules,
129
+ { ...requestContext.current },
130
+ url.pathname
131
+ )
132
+ if (redirectPath !== null) {
133
+ window.location.href = redirectPath
134
+ }
135
+ }
136
+
72
137
  fileRouterInstance.current = new FileRouterController()
73
138
  fileRouterRoute.current = routeMatch.route
74
139
  const [page, ...layouts] = await Promise.all([
@@ -77,6 +142,26 @@ async function preparePreloadConfig(
77
142
  ])
78
143
  fileRouterRoute.current = null
79
144
 
145
+ const onBeforeEnter = page.config?.hooks?.onBeforeEnter
146
+ if (onBeforeEnter) {
147
+ const redirectPath = await runBeforeEnterHooks(
148
+ toArray(onBeforeEnter),
149
+ { ...requestContext.current },
150
+ url.pathname
151
+ )
152
+ if (redirectPath !== null) {
153
+ window.location.href = redirectPath
154
+ // @ts-ignore
155
+ return
156
+ }
157
+ }
158
+
159
+ const query = parseQuery(window.location.search)
160
+
161
+ let pagePropsPromise:
162
+ | Promise<AsyncTaskState<unknown, FileRouterDataLoadError>>
163
+ | undefined
164
+
80
165
  // Check if page has static props pre-loaded at build time
81
166
  if (page.__KIRU_STATIC_PROPS__) {
82
167
  const staticProps = page.__KIRU_STATIC_PROPS__[window.location.pathname]
@@ -95,26 +180,39 @@ async function preparePreloadConfig(
95
180
  pageProps = { loading: true, data: null, error: null }
96
181
 
97
182
  const loader = page.config.loader
98
- // Check cache first if caching is enabled
99
- if (loader.mode !== "static" && loader.cache) {
183
+ if (isSSR) {
184
+ pagePropsPromise = resolveStreamedPromise(PAGE_DATA_PROMISE_ID)
185
+ .then((data) => ({ data, error: null, loading: false } as const))
186
+ .catch((error) => ({ data: null, error, loading: false } as const))
187
+ } else if (!loader.static && loader.cache) {
100
188
  const cacheKey = {
101
189
  path: window.location.pathname,
102
190
  params: routeMatch.params,
103
- query: parseQuery(url.search),
191
+ query,
104
192
  }
105
193
 
106
194
  cacheData = routerCache.current!.get(cacheKey, loader.cache)
107
195
  }
108
196
  }
109
197
 
198
+ window.__kiru.on("mount", () => {
199
+ runAfterEachGuards(
200
+ guardModules,
201
+ { ...requestContext.current },
202
+ url.pathname
203
+ )
204
+ })
205
+
110
206
  return {
111
207
  pages: options.pages,
112
208
  layouts: options.layouts,
209
+ guards: options.guards,
113
210
  page: page,
114
211
  pageProps: pageProps,
212
+ pagePropsPromise,
115
213
  pageLayouts: layouts,
116
214
  params: routeMatch.params,
117
- query: parseQuery(url.search),
215
+ query: query,
118
216
  route: routeMatch.route,
119
217
  cacheData,
120
218
  }
@@ -0,0 +1 @@
1
+ export const PAGE_DATA_PROMISE_ID = "__KIRU_PAGE_DATA__"
@@ -1,6 +1,6 @@
1
+ import { useContext } from "../hooks/index.js"
1
2
  import { createContext } from "../context.js"
2
3
  import { __DEV__ } from "../env.js"
3
- import { useContext } from "../hooks/index.js"
4
4
  import type { RouteQuery, RouterState } from "./types.js"
5
5
 
6
6
  export interface ReloadOptions {
@@ -74,3 +74,9 @@ if (__DEV__) {
74
74
  export function useFileRouter(): FileRouterContextType {
75
75
  return useContext(RouterContext)
76
76
  }
77
+
78
+ export const RequestContext = createContext<Kiru.RequestContext>({})
79
+
80
+ export function useRequestContext() {
81
+ return useContext(RequestContext)
82
+ }