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.
- package/dist/appHandle.d.ts.map +1 -1
- package/dist/appHandle.js +4 -5
- package/dist/appHandle.js.map +1 -1
- package/dist/components/derive.d.ts +8 -7
- package/dist/components/derive.d.ts.map +1 -1
- package/dist/components/derive.js +10 -11
- package/dist/components/derive.js.map +1 -1
- package/dist/components/errorBoundary.d.ts +2 -2
- package/dist/components/errorBoundary.d.ts.map +1 -1
- package/dist/components/errorBoundary.js +1 -1
- package/dist/components/errorBoundary.js.map +1 -1
- package/dist/components/for.d.ts +1 -1
- package/dist/components/for.d.ts.map +1 -1
- package/dist/components/for.js +1 -1
- package/dist/components/lazy.d.ts +1 -1
- package/dist/components/lazy.js +1 -1
- package/dist/components/portal.d.ts +1 -1
- package/dist/components/portal.js +1 -1
- package/dist/components/show.d.ts +1 -1
- package/dist/components/show.js +1 -1
- package/dist/components/transition.d.ts +1 -1
- package/dist/components/transition.d.ts.map +1 -1
- package/dist/components/transition.js +15 -12
- package/dist/components/transition.js.map +1 -1
- package/dist/dom/nodes.d.ts.map +1 -1
- package/dist/dom/nodes.js +5 -1
- package/dist/dom/nodes.js.map +1 -1
- package/dist/dom/props.d.ts.map +1 -1
- package/dist/dom/props.js +23 -4
- package/dist/dom/props.js.map +1 -1
- package/dist/error.d.ts +0 -2
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +11 -14
- package/dist/error.js.map +1 -1
- package/dist/headlessRender.d.ts +2 -2
- package/dist/headlessRender.d.ts.map +1 -1
- package/dist/headlessRender.js +2 -3
- package/dist/headlessRender.js.map +1 -1
- package/dist/hmr.d.ts +1 -0
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +6 -2
- package/dist/hmr.js.map +1 -1
- package/dist/hooks/setup.d.ts +1 -1
- package/dist/hooks/setup.d.ts.map +1 -1
- package/dist/hooks/setup.js +110 -6
- package/dist/hooks/setup.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/renderToString.js +1 -1
- package/dist/renderToString.js.map +1 -1
- package/dist/resource.d.ts +19 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +167 -0
- package/dist/resource.js.map +1 -0
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +15 -11
- package/dist/scheduler.js.map +1 -1
- package/dist/signals/base.d.ts +3 -5
- package/dist/signals/base.d.ts.map +1 -1
- package/dist/signals/base.js +24 -47
- package/dist/signals/base.js.map +1 -1
- package/dist/signals/computed.d.ts.map +1 -1
- package/dist/signals/computed.js +14 -12
- package/dist/signals/computed.js.map +1 -1
- package/dist/signals/globals.d.ts +0 -2
- package/dist/signals/globals.d.ts.map +1 -1
- package/dist/signals/globals.js +0 -1
- package/dist/signals/globals.js.map +1 -1
- package/dist/signals/types.d.ts +1 -1
- package/dist/signals/types.d.ts.map +1 -1
- package/dist/ssr/server.d.ts +4 -4
- package/dist/ssr/server.d.ts.map +1 -1
- package/dist/ssr/server.js +11 -7
- package/dist/ssr/server.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/stream.d.ts +12 -0
- package/dist/utils/stream.d.ts.map +1 -0
- package/dist/utils/stream.js +8 -0
- package/dist/utils/stream.js.map +1 -0
- package/dist/utils/vdom.d.ts +2 -1
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +4 -1
- package/dist/utils/vdom.js.map +1 -1
- package/package.json +1 -13
- package/src/appHandle.ts +6 -7
- package/src/components/derive.ts +34 -40
- package/src/components/errorBoundary.ts +4 -2
- package/src/components/for.ts +34 -34
- package/src/components/lazy.ts +1 -1
- package/src/components/portal.ts +1 -1
- package/src/components/show.ts +32 -32
- package/src/components/transition.ts +16 -11
- package/src/dom/nodes.ts +5 -2
- package/src/dom/props.ts +28 -3
- package/src/error.ts +11 -19
- package/src/headlessRender.ts +5 -5
- package/src/hmr.ts +13 -3
- package/src/hooks/setup.ts +143 -9
- package/src/index.ts +1 -1
- package/src/renderToString.ts +1 -1
- package/src/resource.ts +207 -0
- package/src/scheduler.ts +16 -11
- package/src/signals/base.ts +29 -52
- package/src/signals/computed.ts +13 -9
- package/src/signals/globals.ts +0 -3
- package/src/signals/types.ts +1 -1
- package/src/ssr/server.ts +18 -11
- package/src/types.ts +4 -4
- package/src/utils/index.ts +2 -1
- package/src/utils/stream.ts +17 -0
- package/src/utils/vdom.ts +5 -0
- package/dist/statefulPromise.d.ts +0 -22
- package/dist/statefulPromise.d.ts.map +0 -1
- package/dist/statefulPromise.js +0 -94
- package/dist/statefulPromise.js.map +0 -1
- package/src/statefulPromise.ts +0 -136
package/src/signals/base.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
47
|
-
//
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
128
|
-
return () => this.$subs
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
184
|
+
signal.$subs.clear()
|
|
208
185
|
}
|
|
209
186
|
}
|
|
210
187
|
|
package/src/signals/computed.ts
CHANGED
|
@@ -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
|
|
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]
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
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()
|
package/src/signals/globals.ts
CHANGED
package/src/signals/types.ts
CHANGED
|
@@ -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.
|
|
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
|
|
22
|
+
export interface ReadableStreamRenderResult {
|
|
24
23
|
immediate: string
|
|
25
|
-
stream:
|
|
26
|
-
}
|
|
27
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
65
|
+
headlessRender(ctx, rootNode)
|
|
59
66
|
renderMode.current = prev
|
|
60
67
|
|
|
61
68
|
if (pendingWritePromises.length > 0) {
|
|
62
69
|
Promise.all(pendingWritePromises).then(() => {
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
controller.enqueue(STREAMED_DATA_SETUP)
|
|
71
|
+
controller.close()
|
|
65
72
|
})
|
|
66
73
|
} else {
|
|
67
|
-
|
|
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
|
-
(
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
151
|
+
interface StatefulPromise<T> extends Promise<T>, PromiseState<T> {}
|
|
152
152
|
|
|
153
153
|
type RenderMode = "dom" | "hydrate" | "string" | "stream"
|
|
154
154
|
|
package/src/utils/index.ts
CHANGED
|
@@ -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"}
|
package/dist/statefulPromise.js
DELETED
|
@@ -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":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAU,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAShD;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAc;IAEd,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,YAAY,IAAI,KAAK,CAAA;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAc;IAEd,OAAO,KAAK,YAAY,OAAO,IAAI,IAAI,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,CAAA;AACtE,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,OAAO,EAAsB,CAAA;AAM5D,MAAM,UAAU,eAAe,CAC7B,QAA6C;IAE7C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAQ,CAAA;IAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;IAC3E,CAAC;IACD,MAAM,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;IAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IAE9B,SAAS,CAAC,KAAK,GAAG,IAAI,CAAA;IAEtB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAA;IAEnC,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAChD,kBAAkB,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;IAExC,MAAM,SAAS,GAAG,GAAG,EAAE,SAAS,KAAK,EAAE,CAAA;IAEvC,IAAI,OAAmB,CAAA;IACvB,IAAI,UAAU,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpC,uEAAuE;QACvE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAgB,CAAA;IAC3C,CAAC;SAAM,IACL,UAAU,CAAC,OAAO,KAAK,SAAS;QAChC,aAAa,CAAC,OAAO,KAAK,SAAS,EACnC,CAAC;QACD,2DAA2D;QAC3D,kDAAkD;QAClD,OAAO,GAAG,sBAAsB,CAAI,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,CAAA;IACnE,CAAC;SAAM,CAAC;QACN,oCAAoC;QACpC,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,KAAK,GAAyB;QAClC,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;KACjB,CAAA;IACD,MAAM,eAAe,GAAgC,MAAM,CAAC,MAAM,CAChE,OAAO,EACP,KAAK,CACN,CAAA;IAED,eAAe;SACZ,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACd,eAAe,CAAC,KAAK,GAAG,WAAW,CAAA;QACnC,eAAe,CAAC,KAAK,GAAG,KAAK,CAAA;QAC7B,SAAS,CAAC,KAAK,GAAG,KAAK,CAAA;IACzB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACf,eAAe,CAAC,KAAK,GAAG,UAAU,CAAA;QAClC,eAAe,CAAC,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEJ,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,CAAC,CAAA;AACtD,CAAC;AAQD,SAAS,sBAAsB,CAC7B,EAAU,EACV,MAAmB;IAEnB,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,aAAa,GAA8C,aAAa;SAC5E,CAAC,MAAM,CAAC,mBAAmB,MAA1B,MAAM,CAAC,mBAAmB,IAAM,IAAI,GAAG,EAAE,EAAC,CAAA;QAE7C,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACtC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAA;YAChC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACxB,IAAI,KAAK;gBAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;YAC/B,OAAO,OAAO,CAAC,IAAK,CAAC,CAAA;QACvB,CAAC;QAED,MAAM,WAAW,GAAG,CAAC,KAAY,EAAE,EAAE;YACnC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAmD,CAAA;YACtE,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBACrB,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;gBACxB,MAAM,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;gBAC5D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,CAAA;gBAC9B,IAAI,KAAK;oBAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;gBAC/B,OAAO,CAAC,IAAK,CAAC,CAAA;YAChB,CAAC;QACH,CAAC,CAAA;QAED,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QACzD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;YAC5D,MAAM,EAAE,CAAA;QACV,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/src/statefulPromise.ts
DELETED
|
@@ -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
|
-
}
|