kiru 1.2.0 → 1.3.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 (123) hide show
  1. package/dist/appHandle.d.ts.map +1 -1
  2. package/dist/appHandle.js +4 -5
  3. package/dist/appHandle.js.map +1 -1
  4. package/dist/components/derive.d.ts +8 -7
  5. package/dist/components/derive.d.ts.map +1 -1
  6. package/dist/components/derive.js +10 -11
  7. package/dist/components/derive.js.map +1 -1
  8. package/dist/components/errorBoundary.d.ts +2 -2
  9. package/dist/components/errorBoundary.d.ts.map +1 -1
  10. package/dist/components/errorBoundary.js +1 -1
  11. package/dist/components/errorBoundary.js.map +1 -1
  12. package/dist/components/for.d.ts +1 -1
  13. package/dist/components/for.d.ts.map +1 -1
  14. package/dist/components/for.js +1 -1
  15. package/dist/components/lazy.d.ts +1 -1
  16. package/dist/components/lazy.js +1 -1
  17. package/dist/components/portal.d.ts +1 -1
  18. package/dist/components/portal.js +1 -1
  19. package/dist/components/show.d.ts +1 -1
  20. package/dist/components/show.js +1 -1
  21. package/dist/components/transition.d.ts +1 -1
  22. package/dist/components/transition.d.ts.map +1 -1
  23. package/dist/components/transition.js +15 -12
  24. package/dist/components/transition.js.map +1 -1
  25. package/dist/dom/nodes.d.ts.map +1 -1
  26. package/dist/dom/nodes.js +5 -1
  27. package/dist/dom/nodes.js.map +1 -1
  28. package/dist/dom/props.d.ts.map +1 -1
  29. package/dist/dom/props.js +23 -4
  30. package/dist/dom/props.js.map +1 -1
  31. package/dist/error.d.ts +0 -2
  32. package/dist/error.d.ts.map +1 -1
  33. package/dist/error.js +11 -14
  34. package/dist/error.js.map +1 -1
  35. package/dist/headlessRender.d.ts +2 -2
  36. package/dist/headlessRender.d.ts.map +1 -1
  37. package/dist/headlessRender.js +2 -3
  38. package/dist/headlessRender.js.map +1 -1
  39. package/dist/hmr.d.ts +1 -0
  40. package/dist/hmr.d.ts.map +1 -1
  41. package/dist/hmr.js +6 -2
  42. package/dist/hmr.js.map +1 -1
  43. package/dist/hooks/setup.d.ts +1 -1
  44. package/dist/hooks/setup.d.ts.map +1 -1
  45. package/dist/hooks/setup.js +110 -6
  46. package/dist/hooks/setup.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/renderToString.js +1 -1
  52. package/dist/renderToString.js.map +1 -1
  53. package/dist/resource.d.ts +19 -0
  54. package/dist/resource.d.ts.map +1 -0
  55. package/dist/resource.js +167 -0
  56. package/dist/resource.js.map +1 -0
  57. package/dist/scheduler.d.ts.map +1 -1
  58. package/dist/scheduler.js +15 -11
  59. package/dist/scheduler.js.map +1 -1
  60. package/dist/signals/base.d.ts +3 -5
  61. package/dist/signals/base.d.ts.map +1 -1
  62. package/dist/signals/base.js +24 -47
  63. package/dist/signals/base.js.map +1 -1
  64. package/dist/signals/computed.d.ts.map +1 -1
  65. package/dist/signals/computed.js +14 -12
  66. package/dist/signals/computed.js.map +1 -1
  67. package/dist/signals/globals.d.ts +0 -2
  68. package/dist/signals/globals.d.ts.map +1 -1
  69. package/dist/signals/globals.js +0 -1
  70. package/dist/signals/globals.js.map +1 -1
  71. package/dist/signals/types.d.ts +1 -1
  72. package/dist/signals/types.d.ts.map +1 -1
  73. package/dist/ssr/server.d.ts +4 -4
  74. package/dist/ssr/server.d.ts.map +1 -1
  75. package/dist/ssr/server.js +11 -7
  76. package/dist/ssr/server.js.map +1 -1
  77. package/dist/types.d.ts +1 -1
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/utils/index.d.ts +2 -1
  80. package/dist/utils/index.d.ts.map +1 -1
  81. package/dist/utils/index.js +2 -1
  82. package/dist/utils/index.js.map +1 -1
  83. package/dist/utils/stream.d.ts +12 -0
  84. package/dist/utils/stream.d.ts.map +1 -0
  85. package/dist/utils/stream.js +8 -0
  86. package/dist/utils/stream.js.map +1 -0
  87. package/dist/utils/vdom.d.ts +2 -1
  88. package/dist/utils/vdom.d.ts.map +1 -1
  89. package/dist/utils/vdom.js +4 -1
  90. package/dist/utils/vdom.js.map +1 -1
  91. package/package.json +1 -13
  92. package/src/appHandle.ts +6 -7
  93. package/src/components/derive.ts +34 -40
  94. package/src/components/errorBoundary.ts +4 -2
  95. package/src/components/for.ts +34 -34
  96. package/src/components/lazy.ts +1 -1
  97. package/src/components/portal.ts +1 -1
  98. package/src/components/show.ts +32 -32
  99. package/src/components/transition.ts +16 -11
  100. package/src/dom/nodes.ts +5 -2
  101. package/src/dom/props.ts +28 -3
  102. package/src/error.ts +11 -19
  103. package/src/headlessRender.ts +5 -5
  104. package/src/hmr.ts +13 -3
  105. package/src/hooks/setup.ts +143 -9
  106. package/src/index.ts +1 -1
  107. package/src/renderToString.ts +1 -1
  108. package/src/resource.ts +207 -0
  109. package/src/scheduler.ts +16 -11
  110. package/src/signals/base.ts +29 -52
  111. package/src/signals/computed.ts +13 -9
  112. package/src/signals/globals.ts +0 -3
  113. package/src/signals/types.ts +1 -1
  114. package/src/ssr/server.ts +18 -11
  115. package/src/types.ts +4 -4
  116. package/src/utils/index.ts +2 -1
  117. package/src/utils/stream.ts +17 -0
  118. package/src/utils/vdom.ts +5 -0
  119. package/dist/statefulPromise.d.ts +0 -22
  120. package/dist/statefulPromise.d.ts.map +0 -1
  121. package/dist/statefulPromise.js +0 -94
  122. package/dist/statefulPromise.js.map +0 -1
  123. package/src/statefulPromise.ts +0 -136
@@ -5,20 +5,19 @@ import {
5
5
  generateRandomID,
6
6
  registerVNodeCleanup,
7
7
  } from "../utils/index.js"
8
- import { $HMR_ACCEPT, $SIGNAL } from "../constants.js"
8
+ import { $DEV_FILE_LINK, $HMR_ACCEPT, $SIGNAL } from "../constants.js"
9
9
  import { __DEV__, isBrowser } from "../env.js"
10
10
  import { node } from "../globals.js"
11
11
  import { requestUpdate } from "../scheduler.js"
12
- import { signalSubsMap } from "./globals.js"
13
12
  import { tracking } from "./tracking.js"
14
- import type { SignalSubscriber, ReadonlySignal } from "./types.js"
13
+ import type { SignalSubscriber } from "./types.js"
15
14
  import type { HMRAccept } from "../hmr.js"
16
15
 
17
16
  export class Signal<T> {
18
17
  [$SIGNAL] = true;
19
18
  [$HMR_ACCEPT]?: HMRAccept<Signal<any>>
20
19
  displayName?: string
21
- protected $subs?: Set<SignalSubscriber<any>>
20
+ protected $subs: Set<SignalSubscriber<any>>
22
21
  protected $id: string
23
22
  protected $value: T
24
23
  protected $prevValue?: T
@@ -29,10 +28,10 @@ export class Signal<T> {
29
28
  constructor(initial: T, displayName?: string) {
30
29
  this.$id = generateRandomID()
31
30
  this.$value = initial
31
+ this.$subs = new Set()
32
32
  if (displayName) this.displayName = displayName
33
33
 
34
34
  if (__DEV__) {
35
- signalSubsMap.set(this.$id, new Set())
36
35
  this.$initialValue = safeStringify(initial)
37
36
  this[$HMR_ACCEPT] = {
38
37
  provide: () => {
@@ -40,11 +39,11 @@ export class Signal<T> {
40
39
  },
41
40
  inject: (prev) => {
42
41
  if (isBrowser) window.__kiru.devtools?.untrack(prev)
43
- signalSubsMap.get(this.$id)?.clear?.()
44
- signalSubsMap.delete(this.$id)
45
42
  this.$id = prev.$id
46
- // @ts-ignore - this handles scenarios where a reference to the prev has been encapsulated
47
- // and we need to be able to refer to the latest version of the signal.
43
+ this.$subs = prev.$subs
44
+ // this is a nice-to-have so that implementations of signal-on-signal don't need to do it themselves.
45
+ // eg. Object.assign(signal, { nestedSignal })
46
+ // it's only done by our HMR pass for top-level signals.
48
47
  prev.__next = this
49
48
 
50
49
  if (this.$initialValue === prev.$initialValue) {
@@ -55,8 +54,6 @@ export class Signal<T> {
55
54
  },
56
55
  destroy: () => {},
57
56
  } satisfies HMRAccept<Signal<any>>
58
- } else {
59
- this.$subs = new Set()
60
57
  }
61
58
 
62
59
  const n = node.current
@@ -120,23 +117,31 @@ export class Signal<T> {
120
117
 
121
118
  subscribe(cb: (state: T, prevState?: T) => void): () => void {
122
119
  if (__DEV__) {
123
- const subs = signalSubsMap.get(this.$id)!
124
- subs!.add(cb)
125
- return () => signalSubsMap.get(this.$id)?.delete(cb)
120
+ const tgt = latest(this)
121
+ if (__DEV__ && tgt.$isDisposed) {
122
+ const name = tgt.displayName ?? tgt.$id
123
+ let message = `Attempted to subscribe to a signal that has been disposed: ${name}`
124
+ if ($DEV_FILE_LINK in tgt) {
125
+ message += `\nFile: ${tgt[$DEV_FILE_LINK]}`
126
+ }
127
+ message += `\nInitial value: ${tgt.$initialValue}`
128
+ throw new Error(message)
129
+ }
126
130
  }
127
- this.$subs!.add(cb)
128
- return () => this.$subs!.delete(cb)
131
+ this.$subs.add(cb)
132
+ return () => this.$subs.delete(cb)
129
133
  }
130
134
 
131
135
  notify(filter?: (sub: SignalSubscriber) => boolean) {
132
136
  if (__DEV__) {
133
- return signalSubsMap.get(this.$id)?.forEach((sub) => {
137
+ const tgt = latest(this)
138
+ return tgt.$subs.forEach((sub) => {
134
139
  if (filter && !filter(sub)) return
135
140
  const { $value, $prevValue } = latest(this)
136
141
  return sub($value, $prevValue)
137
142
  })
138
143
  }
139
- this.$subs!.forEach((sub) => {
144
+ this.$subs.forEach((sub) => {
140
145
  if (filter && !filter(sub)) return
141
146
  return sub(this.$value, this.$prevValue)
142
147
  })
@@ -147,46 +152,17 @@ export class Signal<T> {
147
152
  }
148
153
 
149
154
  static subscribers(signal: Signal<any>) {
150
- if (__DEV__) {
151
- return signalSubsMap.get(signal.$id)!
152
- }
153
155
  return signal.$subs
154
156
  }
155
157
 
156
- static makeReadonly<T>(signal: Signal<T>): ReadonlySignal<T> {
157
- const desc = Object.getOwnPropertyDescriptor(signal, "value")
158
- if (desc && !desc.writable) return signal
159
- return Object.defineProperty(signal, "value", {
160
- get: function (this: Signal<T>) {
161
- Signal.entangle(this)
162
- return this.$value
163
- },
164
- configurable: true,
165
- })
166
- }
167
-
168
- static makeWritable<T>(signal: Signal<T>): Signal<T> {
169
- const desc = Object.getOwnPropertyDescriptor(signal, "value")
170
- if (desc && desc.writable) return signal
171
- return Object.defineProperty(signal, "value", {
172
- get: function (this: Signal<T>) {
173
- Signal.entangle(this)
174
- return this.$value
175
- },
176
- set: function (this: Signal<T>, value) {
177
- this.$value = value
178
- this.notify()
179
- },
180
- configurable: true,
181
- })
182
- }
183
-
184
158
  static entangle<T>(signal: Signal<T>) {
185
159
  if (tracking.enabled === false) return
160
+ if (__DEV__) signal = latest(signal)
186
161
 
187
162
  const vNode = node.current
188
163
  const trackedSignalObservations = tracking.current()
189
164
  if (trackedSignalObservations) {
165
+ // track non-rendering access, only track rendering access if renderMode is DOM/hydrate
190
166
  if (!vNode || (vNode && sideEffectsEnabled())) {
191
167
  trackedSignalObservations.set(signal.$id, signal)
192
168
  }
@@ -198,13 +174,14 @@ export class Signal<T> {
198
174
  }
199
175
 
200
176
  static dispose(signal: Signal<any>) {
177
+ if (signal.$isDisposed) return
178
+
201
179
  signal.$isDisposed = true
202
180
  if (__DEV__) {
203
- signalSubsMap.delete(signal.$id)
204
- if (isBrowser) window.__kiru.devtools?.untrack(signal)
181
+ if (isBrowser) window.__kiru.devtools?.untrack(latest(signal))
205
182
  return
206
183
  }
207
- signal.$subs!.clear()
184
+ signal.$subs.clear()
208
185
  }
209
186
  }
210
187
 
@@ -1,7 +1,7 @@
1
1
  import { __DEV__ } from "../env.js"
2
2
  import { $HMR_ACCEPT } from "../constants.js"
3
3
  import { call, latest } from "../utils/index.js"
4
- import { effectQueue, signalSubsMap } from "./globals.js"
4
+ import { effectQueue } from "./globals.js"
5
5
  import { executeWithTracking } from "./tracking.js"
6
6
  import { Signal } from "./base.js"
7
7
  import type { HMRAccept } from "../hmr.js"
@@ -17,16 +17,24 @@ export class ComputedSignal<T> extends Signal<T> {
17
17
  this.$isDirty = true
18
18
 
19
19
  if (__DEV__) {
20
- const inject = this[$HMR_ACCEPT]!.inject!
20
+ const { inject: baseInject } = this[$HMR_ACCEPT]!
21
21
  // @ts-expect-error this is fine 😅
22
22
  this[$HMR_ACCEPT] = {
23
23
  provide: () => {
24
24
  return this
25
25
  },
26
26
  inject: (prev) => {
27
- inject(prev)
27
+ baseInject(prev)
28
+ // Stop any pending reactions on the previous instance and mark
29
+ // this computed as dirty so it will recompute with the latest
30
+ // dependencies after HMR.
28
31
  ComputedSignal.stop(prev)
29
- this.$isDirty = prev.$isDirty
32
+ this.$isDirty = true
33
+ // Force a recompute immediately so any active subscribers (like
34
+ // text nodes bound to a computed signal) see the updated value
35
+ // after a hot reload where only its dependencies changed.
36
+ ComputedSignal.run(this)
37
+ this.notify()
30
38
  },
31
39
  destroy: () => {},
32
40
  } satisfies HMRAccept<ComputedSignal<T>>
@@ -88,11 +96,7 @@ export class ComputedSignal<T> extends Signal<T> {
88
96
  fn: () => $getter(computed.$value),
89
97
  onDepChanged: () => {
90
98
  computed.$isDirty = true
91
- if (__DEV__) {
92
- if (!signalSubsMap?.get(id)?.size) return
93
- } else {
94
- if (!computed.$subs!.size) return
95
- }
99
+ if (!computed.$subs.size) return
96
100
  ComputedSignal.run(computed)
97
101
  if (Object.is(computed.$value, computed.$prevValue)) return
98
102
  computed.notify()
@@ -1,4 +1 @@
1
- import type { SignalSubscriber } from "./types.js"
2
-
3
1
  export const effectQueue = new Map<string, Function>()
4
- export const signalSubsMap: Map<string, Set<SignalSubscriber<any>>> = new Map()
@@ -7,7 +7,7 @@ export type SignalSubscriber<T = unknown> = (value: T, prevValue?: T) => void
7
7
 
8
8
  export type SignalValues<T extends readonly Signal<unknown>[]> = {
9
9
  [I in keyof T]: T[I] extends Signal<infer V>
10
- ? V extends Kiru.StatefulPromiseBase<infer P>
10
+ ? V extends Kiru.StatefulPromise<infer P>
11
11
  ? P
12
12
  : V
13
13
  : never
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"
@@ -20,13 +19,21 @@ d.currentScript.remove()
20
19
  </script>
21
20
  `
22
21
 
23
- export function renderToReadableStream(element: JSX.Element): {
22
+ export interface ReadableStreamRenderResult {
24
23
  immediate: string
25
- stream: Readable
26
- } {
27
- const stream = new Readable({ read() {} })
24
+ stream: ReadableStream
25
+ }
26
+
27
+ export function renderToReadableStream(element: JSX.Element): ReadableStreamRenderResult {
28
+ let controller!: ReadableStreamDefaultController<string>
29
+ const stream = new ReadableStream<string>({
30
+ start(c) {
31
+ controller = c
32
+ },
33
+ })
34
+
28
35
  const rootNode = Fragment({ children: element })
29
- const streamPromises = new Set<Kiru.StatefulPromiseBase<unknown>>()
36
+ const streamPromises = new Set<Kiru.StatefulPromise<unknown>>()
30
37
  const pendingWritePromises: Promise<void>[] = []
31
38
 
32
39
  let immediate = ""
@@ -43,7 +50,7 @@ export function renderToReadableStream(element: JSX.Element): {
43
50
  .catch(() => ({ error: promise.error?.message }))
44
51
  .then((value) => {
45
52
  const content = JSON.stringify(value)
46
- stream.push(
53
+ controller.enqueue(
47
54
  `<script id="${promise.id}" k-data type="application/json">${content}</script>`
48
55
  )
49
56
  })
@@ -55,16 +62,16 @@ export function renderToReadableStream(element: JSX.Element): {
55
62
 
56
63
  const prev = renderMode.current
57
64
  renderMode.current = "stream"
58
- headlessRender(ctx, rootNode, null, 0)
65
+ headlessRender(ctx, rootNode)
59
66
  renderMode.current = prev
60
67
 
61
68
  if (pendingWritePromises.length > 0) {
62
69
  Promise.all(pendingWritePromises).then(() => {
63
- stream.push(STREAMED_DATA_SETUP)
64
- stream.push(null)
70
+ controller.enqueue(STREAMED_DATA_SETUP)
71
+ controller.close()
65
72
  })
66
73
  } else {
67
- stream.push(null)
74
+ controller.close()
68
75
  }
69
76
 
70
77
  return { immediate, stream }
package/src/types.ts CHANGED
@@ -123,9 +123,9 @@ declare global {
123
123
  }
124
124
 
125
125
  export interface FC<T = {}> {
126
- (props: T):
127
- | Exclude<JSX.Element, Kiru.FC<any>>
128
- | ((props: T) => JSX.Element)
126
+ (
127
+ props: T
128
+ ): Exclude<JSX.Element, Kiru.FC<any>> | ((props: T) => JSX.Element)
129
129
  /** Used to display the name of the component in devtools */
130
130
  displayName?: string
131
131
  }
@@ -148,7 +148,7 @@ declare global {
148
148
  error?: Error
149
149
  }
150
150
 
151
- interface StatefulPromiseBase<T> extends Promise<T>, PromiseState<T> {}
151
+ interface StatefulPromise<T> extends Promise<T>, PromiseState<T> {}
152
152
 
153
153
  type RenderMode = "dom" | "hydrate" | "string" | "stream"
154
154
 
@@ -1,6 +1,7 @@
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"
6
+ export * from "./stream.js"
5
7
  export * from "./vdom.js"
6
- export * from "./generateId.js"
@@ -0,0 +1,17 @@
1
+ import { $STREAM_DATA } from "../constants.js"
2
+
3
+ export interface StreamDataThrowValue {
4
+ [$STREAM_DATA]: {
5
+ fallback?: JSX.Element
6
+ data: Kiru.StatefulPromise<unknown>[]
7
+ }
8
+ }
9
+
10
+ /**
11
+ * Returns true if the value is a {@link StreamDataThrowValue}
12
+ */
13
+ export function isStreamDataThrowValue(
14
+ value: unknown
15
+ ): value is StreamDataThrowValue {
16
+ return typeof value === "object" && !!value && $STREAM_DATA in value
17
+ }
package/src/utils/vdom.ts CHANGED
@@ -34,6 +34,7 @@ export {
34
34
  createVNodeId,
35
35
  registerVNodeCleanup,
36
36
  propsChanged,
37
+ depthSort,
37
38
  }
38
39
 
39
40
  function cloneElement(vNode: Kiru.VNode): Kiru.Element {
@@ -219,3 +220,7 @@ function propsChanged(
219
220
  }
220
221
  return false
221
222
  }
223
+
224
+ function depthSort(a: Kiru.VNode, b: Kiru.VNode): number {
225
+ return a.depth - b.depth
226
+ }
@@ -1,22 +0,0 @@
1
- import { $STREAM_DATA } from "./constants.js";
2
- import { Signal } from "./signals/base.js";
3
- export interface StreamDataThrowValue {
4
- [$STREAM_DATA]: {
5
- fallback?: JSX.Element;
6
- data: Kiru.StatefulPromiseBase<unknown>[];
7
- };
8
- }
9
- /**
10
- * Returns true if the value is a {@link StreamDataThrowValue}
11
- */
12
- export declare function isStreamDataThrowValue(value: unknown): value is StreamDataThrowValue;
13
- /**
14
- * Returns true if the value is a {@link Kiru.StatefulPromiseBase}
15
- */
16
- export declare function isStatefulPromise(thing: unknown): thing is Kiru.StatefulPromiseBase<unknown>;
17
- type StatefulPromise<T> = Kiru.StatefulPromiseBase<T> & {
18
- isPending: Signal<boolean>;
19
- };
20
- export declare function statefulPromise<T>(callback: (signal: AbortSignal) => Promise<T>): StatefulPromise<T>;
21
- export {};
22
- //# sourceMappingURL=statefulPromise.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"statefulPromise.d.ts","sourceRoot":"","sources":["../src/statefulPromise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,gBAAgB,CAAA;AAElE,OAAO,EAAE,MAAM,EAAU,MAAM,mBAAmB,CAAA;AAIlD,MAAM,WAAW,oBAAoB;IACnC,CAAC,YAAY,CAAC,EAAE;QACd,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;QACtB,IAAI,EAAE,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAA;KAC1C,CAAA;CACF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAE/B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAE5C;AAID,KAAK,eAAe,CAAC,CAAC,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,GAAG;IACtD,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;CAC3B,CAAA;AAED,wBAAgB,eAAe,CAAC,CAAC,EAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,eAAe,CAAC,CAAC,CAAC,CAuDpB"}
@@ -1,94 +0,0 @@
1
- import { $STREAM_DATA, STREAMED_DATA_EVENT } from "./constants.js";
2
- import { hydrationMode, node, renderMode } from "./globals.js";
3
- import { signal } from "./signals/base.js";
4
- import { createVNodeId } from "./utils/vdom.js";
5
- import { onCleanup } from "./hooks/onCleanup.js";
6
- /**
7
- * Returns true if the value is a {@link StreamDataThrowValue}
8
- */
9
- export function isStreamDataThrowValue(value) {
10
- return typeof value === "object" && !!value && $STREAM_DATA in value;
11
- }
12
- /**
13
- * Returns true if the value is a {@link Kiru.StatefulPromiseBase}
14
- */
15
- export function isStatefulPromise(thing) {
16
- return thing instanceof Promise && "id" in thing && "state" in thing;
17
- }
18
- const nodeToPromiseIndex = new WeakMap();
19
- export function statefulPromise(callback) {
20
- const vNode = node.current;
21
- if (!vNode) {
22
- throw new Error("statefulPromise must be called inside a Kiru component");
23
- }
24
- const id = createVNodeId(vNode);
25
- const isPending = signal(true);
26
- isPending.value = true;
27
- const controller = new AbortController();
28
- onCleanup(() => controller.abort());
29
- const index = nodeToPromiseIndex.get(vNode) ?? 0;
30
- nodeToPromiseIndex.set(vNode, index + 1);
31
- const promiseId = `${id}:data:${index}`;
32
- let promise;
33
- if (renderMode.current === "string") {
34
- // if we're rendering to a string, there's no need to fire the callback
35
- promise = Promise.resolve();
36
- }
37
- else if (renderMode.current === "hydrate" &&
38
- hydrationMode.current === "dynamic") {
39
- // if we're hydrating and the hydration mode is not static,
40
- // we need to resolve the promise from cache/event
41
- promise = resolveDeferredPromise(promiseId, controller.signal);
42
- }
43
- else {
44
- // dom / stream / (hydrate + static)
45
- promise = callback(controller.signal);
46
- }
47
- const state = {
48
- id: promiseId,
49
- state: "pending",
50
- };
51
- const statefulPromise = Object.assign(promise, state);
52
- statefulPromise
53
- .then((value) => {
54
- statefulPromise.state = "fulfilled";
55
- statefulPromise.value = value;
56
- isPending.value = false;
57
- })
58
- .catch((error) => {
59
- statefulPromise.state = "rejected";
60
- statefulPromise.error = error instanceof Error ? error : new Error(error);
61
- });
62
- return Object.assign(statefulPromise, { isPending });
63
- }
64
- function resolveDeferredPromise(id, signal) {
65
- return new Promise((resolve, reject) => {
66
- const deferralCache = // @ts-ignore
67
- (window[STREAMED_DATA_EVENT] ?? (window[STREAMED_DATA_EVENT] = new Map()));
68
- const existing = deferralCache.get(id);
69
- if (existing) {
70
- const { data, error } = existing;
71
- deferralCache.delete(id);
72
- if (error)
73
- return reject(error);
74
- return resolve(data);
75
- }
76
- const onDataEvent = (event) => {
77
- const { detail } = event;
78
- if (detail.id === id) {
79
- deferralCache.delete(id);
80
- window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent);
81
- const { data, error } = detail;
82
- if (error)
83
- return reject(error);
84
- resolve(data);
85
- }
86
- };
87
- window.addEventListener(STREAMED_DATA_EVENT, onDataEvent);
88
- signal.addEventListener("abort", () => {
89
- window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent);
90
- reject();
91
- });
92
- });
93
- }
94
- //# sourceMappingURL=statefulPromise.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"statefulPromise.js","sourceRoot":"","sources":["../src/statefulPromise.ts"],"names":[],"mappings":"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"}
@@ -1,136 +0,0 @@
1
- import { $STREAM_DATA, STREAMED_DATA_EVENT } from "./constants.js"
2
- import { hydrationMode, node, renderMode } from "./globals.js"
3
- import { Signal, signal } from "./signals/base.js"
4
- import { createVNodeId } from "./utils/vdom.js"
5
- import { onCleanup } from "./hooks/onCleanup.js"
6
-
7
- export interface StreamDataThrowValue {
8
- [$STREAM_DATA]: {
9
- fallback?: JSX.Element
10
- data: Kiru.StatefulPromiseBase<unknown>[]
11
- }
12
- }
13
-
14
- /**
15
- * Returns true if the value is a {@link StreamDataThrowValue}
16
- */
17
- export function isStreamDataThrowValue(
18
- value: unknown
19
- ): value is StreamDataThrowValue {
20
- return typeof value === "object" && !!value && $STREAM_DATA in value
21
- }
22
-
23
- /**
24
- * Returns true if the value is a {@link Kiru.StatefulPromiseBase}
25
- */
26
- export function isStatefulPromise(
27
- thing: unknown
28
- ): thing is Kiru.StatefulPromiseBase<unknown> {
29
- return thing instanceof Promise && "id" in thing && "state" in thing
30
- }
31
-
32
- const nodeToPromiseIndex = new WeakMap<Kiru.VNode, number>()
33
-
34
- type StatefulPromise<T> = Kiru.StatefulPromiseBase<T> & {
35
- isPending: Signal<boolean>
36
- }
37
-
38
- export function statefulPromise<T>(
39
- callback: (signal: AbortSignal) => Promise<T>
40
- ): StatefulPromise<T> {
41
- const vNode = node.current!
42
- if (!vNode) {
43
- throw new Error("statefulPromise must be called inside a Kiru component")
44
- }
45
- const id = createVNodeId(vNode)
46
- const isPending = signal(true)
47
-
48
- isPending.value = true
49
-
50
- const controller = new AbortController()
51
- onCleanup(() => controller.abort())
52
-
53
- const index = nodeToPromiseIndex.get(vNode) ?? 0
54
- nodeToPromiseIndex.set(vNode, index + 1)
55
-
56
- const promiseId = `${id}:data:${index}`
57
-
58
- let promise: Promise<T>
59
- if (renderMode.current === "string") {
60
- // if we're rendering to a string, there's no need to fire the callback
61
- promise = Promise.resolve() as Promise<T>
62
- } else if (
63
- renderMode.current === "hydrate" &&
64
- hydrationMode.current === "dynamic"
65
- ) {
66
- // if we're hydrating and the hydration mode is not static,
67
- // we need to resolve the promise from cache/event
68
- promise = resolveDeferredPromise<T>(promiseId, controller.signal)
69
- } else {
70
- // dom / stream / (hydrate + static)
71
- promise = callback(controller.signal)
72
- }
73
-
74
- const state: Kiru.PromiseState<T> = {
75
- id: promiseId,
76
- state: "pending",
77
- }
78
- const statefulPromise: Kiru.StatefulPromiseBase<T> = Object.assign(
79
- promise,
80
- state
81
- )
82
-
83
- statefulPromise
84
- .then((value) => {
85
- statefulPromise.state = "fulfilled"
86
- statefulPromise.value = value
87
- isPending.value = false
88
- })
89
- .catch((error) => {
90
- statefulPromise.state = "rejected"
91
- statefulPromise.error = error instanceof Error ? error : new Error(error)
92
- })
93
-
94
- return Object.assign(statefulPromise, { isPending })
95
- }
96
-
97
- interface DeferredPromiseEventDetail<T> {
98
- id: string
99
- data?: T
100
- error?: string
101
- }
102
-
103
- function resolveDeferredPromise<T>(
104
- id: string,
105
- signal: AbortSignal
106
- ): Promise<T> {
107
- return new Promise<T>((resolve, reject) => {
108
- const deferralCache: Map<string, { data?: T; error?: string }> = // @ts-ignore
109
- (window[STREAMED_DATA_EVENT] ??= new Map())
110
-
111
- const existing = deferralCache.get(id)
112
- if (existing) {
113
- const { data, error } = existing
114
- deferralCache.delete(id)
115
- if (error) return reject(error)
116
- return resolve(data!)
117
- }
118
-
119
- const onDataEvent = (event: Event) => {
120
- const { detail } = event as CustomEvent<DeferredPromiseEventDetail<T>>
121
- if (detail.id === id) {
122
- deferralCache.delete(id)
123
- window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
124
- const { data, error } = detail
125
- if (error) return reject(error)
126
- resolve(data!)
127
- }
128
- }
129
-
130
- window.addEventListener(STREAMED_DATA_EVENT, onDataEvent)
131
- signal.addEventListener("abort", () => {
132
- window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
133
- reject()
134
- })
135
- })
136
- }