kiru 0.54.0-preview.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 (61) 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/hooks/usePromise.d.ts +2 -1
  9. package/dist/hooks/usePromise.d.ts.map +1 -1
  10. package/dist/hooks/usePromise.js +31 -62
  11. package/dist/hooks/usePromise.js.map +1 -1
  12. package/dist/index.js +1 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/router/client/index.d.ts.map +1 -1
  15. package/dist/router/client/index.js +12 -4
  16. package/dist/router/client/index.js.map +1 -1
  17. package/dist/router/constants.d.ts +2 -0
  18. package/dist/router/constants.d.ts.map +1 -0
  19. package/dist/router/constants.js +2 -0
  20. package/dist/router/constants.js.map +1 -0
  21. package/dist/router/fileRouterController.d.ts.map +1 -1
  22. package/dist/router/fileRouterController.js +123 -106
  23. package/dist/router/fileRouterController.js.map +1 -1
  24. package/dist/router/ssg/index.js +1 -1
  25. package/dist/router/ssg/index.js.map +1 -1
  26. package/dist/router/ssr/index.d.ts.map +1 -1
  27. package/dist/router/ssr/index.js +29 -26
  28. package/dist/router/ssr/index.js.map +1 -1
  29. package/dist/router/types.d.ts +5 -12
  30. package/dist/router/types.d.ts.map +1 -1
  31. package/dist/scheduler.d.ts +14 -3
  32. package/dist/scheduler.d.ts.map +1 -1
  33. package/dist/scheduler.js +3 -4
  34. package/dist/scheduler.js.map +1 -1
  35. package/dist/ssr/server.d.ts +8 -1
  36. package/dist/ssr/server.d.ts.map +1 -1
  37. package/dist/ssr/server.js +28 -18
  38. package/dist/ssr/server.js.map +1 -1
  39. package/dist/utils/index.d.ts +1 -1
  40. package/dist/utils/index.d.ts.map +1 -1
  41. package/dist/utils/index.js +1 -1
  42. package/dist/utils/index.js.map +1 -1
  43. package/dist/utils/promise.d.ts +2 -0
  44. package/dist/utils/promise.d.ts.map +1 -1
  45. package/dist/utils/promise.js +45 -1
  46. package/dist/utils/promise.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/components/derive.ts +5 -3
  49. package/src/dom.ts +5 -1
  50. package/src/hooks/usePromise.ts +57 -77
  51. package/src/index.ts +1 -1
  52. package/src/router/client/index.ts +16 -4
  53. package/src/router/constants.ts +1 -0
  54. package/src/router/fileRouterController.ts +180 -137
  55. package/src/router/ssg/index.ts +1 -1
  56. package/src/router/ssr/index.ts +38 -33
  57. package/src/router/types.ts +5 -12
  58. package/src/scheduler.ts +20 -3
  59. package/src/ssr/server.ts +48 -22
  60. package/src/utils/index.ts +1 -1
  61. package/src/utils/promise.ts +70 -1
@@ -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
  }
@@ -2,6 +2,7 @@ import { createElement } from "../../element.js"
2
2
  import { hydrate } from "../../ssr/client.js"
3
3
  import { FileRouter } from "../fileRouter.js"
4
4
  import { toArray } from "../../utils/format.js"
5
+ import { resolveStreamedPromise } from "../../utils/promise.js"
5
6
  import {
6
7
  matchModules,
7
8
  matchRoute,
@@ -28,6 +29,8 @@ import { FileRouterDataLoadError } from "../errors.js"
28
29
  import { __DEV__ } from "../../env.js"
29
30
  import { RouterCache } from "../cache.js"
30
31
  import { RequestContext } from "../context.js"
32
+ import { PAGE_DATA_PROMISE_ID } from "../constants.js"
33
+ import { AsyncTaskState } from "../../types.js"
31
34
 
32
35
  interface InitClientOptions {
33
36
  dir: string
@@ -73,6 +76,7 @@ export async function initClient(options: InitClientOptions) {
73
76
  }
74
77
 
75
78
  const children = createElement(FileRouter, { config })
79
+
76
80
  const app =
77
81
  hydrationMode === "static"
78
82
  ? children
@@ -91,7 +95,7 @@ export async function initClient(options: InitClientOptions) {
91
95
  async function preparePreloadConfig(
92
96
  options: InitClientOptions,
93
97
  isStatic404 = false,
94
- _isSSR = false
98
+ isSSR = false
95
99
  ): Promise<FileRouterPreloadConfig> {
96
100
  let pageProps = {}
97
101
  let cacheData: null | { value: unknown } = null
@@ -145,7 +149,7 @@ async function preparePreloadConfig(
145
149
  { ...requestContext.current },
146
150
  url.pathname
147
151
  )
148
- if (redirectPath) {
152
+ if (redirectPath !== null) {
149
153
  window.location.href = redirectPath
150
154
  // @ts-ignore
151
155
  return
@@ -154,6 +158,10 @@ async function preparePreloadConfig(
154
158
 
155
159
  const query = parseQuery(window.location.search)
156
160
 
161
+ let pagePropsPromise:
162
+ | Promise<AsyncTaskState<unknown, FileRouterDataLoadError>>
163
+ | undefined
164
+
157
165
  // Check if page has static props pre-loaded at build time
158
166
  if (page.__KIRU_STATIC_PROPS__) {
159
167
  const staticProps = page.__KIRU_STATIC_PROPS__[window.location.pathname]
@@ -172,8 +180,11 @@ async function preparePreloadConfig(
172
180
  pageProps = { loading: true, data: null, error: null }
173
181
 
174
182
  const loader = page.config.loader
175
- // Check cache first if caching is enabled
176
- 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) {
177
188
  const cacheKey = {
178
189
  path: window.location.pathname,
179
190
  params: routeMatch.params,
@@ -198,6 +209,7 @@ async function preparePreloadConfig(
198
209
  guards: options.guards,
199
210
  page: page,
200
211
  pageProps: pageProps,
212
+ pagePropsPromise,
201
213
  pageLayouts: layouts,
202
214
  params: routeMatch.params,
203
215
  query: query,
@@ -0,0 +1 @@
1
+ export const PAGE_DATA_PROMISE_ID = "__KIRU_PAGE_DATA__"