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
package/src/scheduler.ts CHANGED
@@ -52,10 +52,27 @@ let postEffects: Array<Function> = []
52
52
  let animationFrameHandle = -1
53
53
 
54
54
  /**
55
- * Runs a function after any existing work has been completed,
56
- * or immediately if the scheduler is already idle.
55
+ * Defers work until the scheduler becomes idle.
56
+ *
57
+ * This works in two modes:
58
+ * - `await nextIdle()` resolves once idle, allowing async/await usage.
59
+ * - `nextIdle(fn)` schedules a callback to run when idle.
60
+ *
61
+ * Callbacks are executed before promises resolve,
62
+ * and multiple calls queue until the scheduler becomes idle.
57
63
  */
58
- export function nextIdle(fn: () => void) {
64
+ export function nextIdle(): Promise<void>
65
+
66
+ /**
67
+ * Schedules `fn` to run once the scheduler becomes idle.
68
+ * If already idle, `fn` executes immediately.
69
+ */
70
+ export function nextIdle<T extends () => void>(fn: T): void
71
+
72
+ export function nextIdle(fn?: () => void): void | Promise<void> {
73
+ if (!fn) {
74
+ return new Promise<void>(nextIdle)
75
+ }
59
76
  if (isRunningOrQueued) {
60
77
  nextIdleEffects.push(fn)
61
78
  return
package/src/ssr/server.ts CHANGED
@@ -20,33 +20,29 @@ d.currentScript.remove()
20
20
  </script>
21
21
  `.replace(/\s+/g, " ")
22
22
 
23
- export function renderToReadableStream(element: JSX.Element): {
23
+ interface RenderToReadableStreamConfig {
24
+ /**
25
+ * Include additional data to stream - must be resolved on the client manually.
26
+ */
27
+ data?: Kiru.StatefulPromise<unknown>[]
28
+ }
29
+
30
+ export function renderToReadableStream(
31
+ element: JSX.Element,
32
+ config: RenderToReadableStreamConfig = {}
33
+ ): {
24
34
  immediate: string
25
35
  stream: ReadableStream | null
26
36
  } {
27
- const streamPromises = new Set<Kiru.StatefulPromise<unknown>>()
28
- const dataPromises: Promise<string>[] = []
29
- let stream: ReadableStream | null = null
37
+ const streamData = createStreamDataHandler()
38
+ if (config.data) {
39
+ streamData.enqueue(config.data)
40
+ }
30
41
 
31
42
  let immediate = ""
32
-
33
43
  const ctx: HeadlessRenderContext = {
34
44
  write: (chunk) => (immediate += chunk),
35
- onStreamData(data) {
36
- for (const promise of data) {
37
- if (streamPromises.has(promise)) continue
38
- streamPromises.add(promise)
39
-
40
- const dataPromise = promise
41
- .then(() => ({ data: promise.value }))
42
- .catch(() => ({ error: promise.error?.message }))
43
- .then(
44
- (value, content = JSON.stringify(value)) =>
45
- `<script id="${promise.id}" k-data type="application/json">${content}</script>`
46
- )
47
- dataPromises.push(dataPromise)
48
- }
49
- },
45
+ onStreamData: streamData.enqueue,
50
46
  }
51
47
 
52
48
  const prev = renderMode.current
@@ -54,10 +50,11 @@ export function renderToReadableStream(element: JSX.Element): {
54
50
  headlessRender(ctx, Fragment({ children: element }), null, 0)
55
51
  renderMode.current = prev
56
52
 
57
- if (dataPromises.length > 0) {
53
+ let stream: ReadableStream | null = null
54
+ if (streamData.chunks.length > 0) {
58
55
  stream = new ReadableStream({
59
56
  async pull(controller) {
60
- for await (const chunk of dataPromises) {
57
+ for await (const chunk of streamData.chunks) {
61
58
  controller.enqueue(chunk)
62
59
  }
63
60
  controller.enqueue(STREAMED_DATA_SETUP)
@@ -68,3 +65,32 @@ export function renderToReadableStream(element: JSX.Element): {
68
65
 
69
66
  return { immediate, stream }
70
67
  }
68
+
69
+ interface StreamDataHandler {
70
+ enqueue: NonNullable<HeadlessRenderContext["onStreamData"]>
71
+ chunks: Promise<string>[]
72
+ }
73
+
74
+ function createStreamDataHandler(): StreamDataHandler {
75
+ const seen = new Set<Kiru.StatefulPromise<unknown>>()
76
+ const chunks: Promise<string>[] = []
77
+
78
+ return {
79
+ chunks,
80
+ enqueue: (items) => {
81
+ for (const item of items) {
82
+ if (seen.has(item)) continue
83
+ seen.add(item)
84
+
85
+ const chunk = item
86
+ .then(() => ({ data: item.value }))
87
+ .catch(() => ({ error: item.error?.message }))
88
+ .then(
89
+ (value, content = JSON.stringify(value)) =>
90
+ `<script id="${item.id}" k-data type="application/json">${content}</script>`
91
+ )
92
+ chunks.push(chunk)
93
+ }
94
+ },
95
+ }
96
+ }
@@ -1,6 +1,6 @@
1
1
  export * from "./compare.js"
2
2
  export * from "./dom.js"
3
3
  export * from "./format.js"
4
+ export * from "./generateId.js"
4
5
  export * from "./runtime.js"
5
6
  export * from "./vdom.js"
6
- export * from "./generateId.js"
@@ -1,4 +1,4 @@
1
- import { $STREAM_DATA } from "../constants.js"
1
+ import { $STREAM_DATA, STREAMED_DATA_EVENT } from "../constants.js"
2
2
 
3
3
  export interface StreamDataThrowValue {
4
4
  [$STREAM_DATA]: {
@@ -24,3 +24,72 @@ export function isStatefulPromise(
24
24
  ): thing is Kiru.StatefulPromise<unknown> {
25
25
  return thing instanceof Promise && "id" in thing && "state" in thing
26
26
  }
27
+
28
+ export function createStatefulPromise<T, U extends Record<string, unknown>>(
29
+ id: string,
30
+ promise: Promise<T>,
31
+ extra: U = {} as U,
32
+ _finally: null | (() => void) = null
33
+ ): Kiru.StatefulPromise<T> & U {
34
+ const state: Kiru.PromiseState<T> = { id, state: "pending" }
35
+ const p = Object.assign(promise, state, extra)
36
+ p.then(
37
+ (value) => {
38
+ p.state = "fulfilled"
39
+ p.value = value
40
+ },
41
+ (error) => {
42
+ p.state = "rejected"
43
+ p.error = error instanceof Error ? error : new Error(String(error))
44
+ }
45
+ ).finally(_finally)
46
+
47
+ return p
48
+ }
49
+
50
+ interface StreamedPromiseEventDetail<T> {
51
+ id: string
52
+ data?: T
53
+ error?: string
54
+ }
55
+
56
+ export async function resolveStreamedPromise<T>(
57
+ id: string,
58
+ signal?: AbortSignal
59
+ ): Promise<T> {
60
+ const deferralCache: Map<string, { data?: T; error?: string }> = // @ts-ignore
61
+ (window[STREAMED_DATA_EVENT] ??= new Map())
62
+
63
+ const existing = deferralCache.get(id)
64
+ if (existing) {
65
+ deferralCache.delete(id)
66
+
67
+ const { data, error } = existing
68
+ if (error) {
69
+ return Promise.reject(error)
70
+ }
71
+ return Promise.resolve(data!)
72
+ }
73
+
74
+ return new Promise<T>((resolve, reject) => {
75
+ const onDataEvent = (event: Event) => {
76
+ const { detail } = event as CustomEvent<StreamedPromiseEventDetail<T>>
77
+ if (detail.id === id) {
78
+ deferralCache.delete(id)
79
+ window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
80
+
81
+ const { data, error } = detail
82
+ if (error) {
83
+ return reject(error)
84
+ }
85
+ resolve(data!)
86
+ }
87
+ }
88
+
89
+ window.addEventListener(STREAMED_DATA_EVENT, onDataEvent)
90
+ signal?.addEventListener("abort", () => {
91
+ window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
92
+ reject("aborted")
93
+ })
94
+ })
95
+ }