@tanstack/redact 0.0.14 → 0.0.15
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/core/internal.d.ts +23 -39
- package/dist/core/internal.js +7 -23
- package/dist/core/internal.js.map +3 -3
- package/dist/dom/dispatcher.d.ts +3 -1
- package/dist/dom/dispatcher.js +104 -97
- package/dist/dom/dispatcher.js.map +2 -2
- package/dist/dom/dom.js +1 -1
- package/dist/dom/dom.js.map +2 -2
- package/dist/dom/event-replay.js +7 -29
- package/dist/dom/event-replay.js.map +2 -2
- package/dist/dom/features/class/full.js +16 -16
- package/dist/dom/features/class/full.js.map +2 -2
- package/dist/dom/features/class/stub.js +7 -7
- package/dist/dom/features/class/stub.js.map +2 -2
- package/dist/dom/features/context/full.js +7 -7
- package/dist/dom/features/context/full.js.map +2 -2
- package/dist/dom/features/context/stub.js +3 -3
- package/dist/dom/features/context/stub.js.map +2 -2
- package/dist/dom/features/forward-ref/full.js +11 -11
- package/dist/dom/features/forward-ref/full.js.map +2 -2
- package/dist/dom/features/hydration/full.d.ts +26 -14
- package/dist/dom/features/hydration/full.js +412 -92
- package/dist/dom/features/hydration/full.js.map +2 -2
- package/dist/dom/features/hydration/stub.d.ts +13 -12
- package/dist/dom/features/hydration/stub.js +23 -8
- package/dist/dom/features/hydration/stub.js.map +2 -2
- package/dist/dom/features/lazy/full.js +3 -43
- package/dist/dom/features/lazy/full.js.map +2 -2
- package/dist/dom/features/lazy/stub.js +1 -1
- package/dist/dom/features/lazy/stub.js.map +2 -2
- package/dist/dom/features/memo/full.js +3 -3
- package/dist/dom/features/memo/full.js.map +2 -2
- package/dist/dom/features/memo/stub.js +1 -1
- package/dist/dom/features/memo/stub.js.map +2 -2
- package/dist/dom/features/portal/full.js +2 -2
- package/dist/dom/features/portal/full.js.map +2 -2
- package/dist/dom/features/suspense/full.js +151 -109
- package/dist/dom/features/suspense/full.js.map +2 -2
- package/dist/dom/reconcile.d.ts +3 -1
- package/dist/dom/reconcile.js +141 -129
- package/dist/dom/reconcile.js.map +2 -2
- package/dist/dom/root-internal.d.ts +9 -0
- package/dist/dom/root-internal.js +31 -0
- package/dist/dom/root-internal.js.map +7 -0
- package/dist/dom/root.d.ts +1 -1
- package/dist/dom/root.js +4 -136
- package/dist/dom/root.js.map +2 -2
- package/dist/dom/test-utils.js +1 -1
- package/dist/dom/test-utils.js.map +2 -2
- package/dist/react/children.js +4 -1
- package/dist/react/children.js.map +2 -2
- package/dist/react/class.js +8 -2
- package/dist/react/class.js.map +2 -2
- package/dist/react/context.js +4 -1
- package/dist/react/context.js.map +2 -2
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +2 -2
- package/dist/react/shared-internals.d.ts +4 -7
- package/dist/react/shared-internals.js +4 -7
- package/dist/react/shared-internals.js.map +2 -2
- package/dist/server/stream.d.ts +5 -1
- package/dist/server/stream.js.map +2 -2
- package/package.json +1 -1
- package/src/core/internal.ts +30 -50
- package/src/dom/dispatcher.ts +114 -105
- package/src/dom/dom.ts +1 -1
- package/src/dom/event-replay.ts +14 -30
- package/src/dom/features/class/full.ts +16 -16
- package/src/dom/features/class/stub.ts +7 -7
- package/src/dom/features/context/full.ts +7 -7
- package/src/dom/features/context/stub.ts +3 -3
- package/src/dom/features/forward-ref/full.ts +11 -11
- package/src/dom/features/hydration/full.ts +516 -104
- package/src/dom/features/hydration/stub.ts +28 -14
- package/src/dom/features/lazy/full.ts +3 -56
- package/src/dom/features/lazy/stub.ts +1 -1
- package/src/dom/features/memo/full.ts +3 -3
- package/src/dom/features/memo/stub.ts +1 -1
- package/src/dom/features/portal/full.ts +2 -2
- package/src/dom/features/suspense/full.ts +176 -129
- package/src/dom/reconcile.ts +164 -170
- package/src/dom/root-internal.ts +40 -0
- package/src/dom/root.ts +5 -165
- package/src/dom/test-utils.ts +1 -1
- package/src/react/children.ts +4 -1
- package/src/react/class.ts +8 -2
- package/src/react/context.ts +4 -1
- package/src/react/index.ts +1 -1
- package/src/react/shared-internals.ts +8 -14
- package/src/server/stream.ts +9 -4
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FiberTag, createFiber, type Fiber, type FiberRoot } from '../core'
|
|
2
|
+
|
|
3
|
+
export interface RootOptionsInternal {
|
|
4
|
+
identifierPrefix?: string
|
|
5
|
+
onRecoverableError?: (error: unknown) => void
|
|
6
|
+
onCaughtError?: (error: unknown) => void
|
|
7
|
+
onUncaughtError?: (error: unknown) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createFiberRoot(
|
|
11
|
+
container: Element | Document | DocumentFragment,
|
|
12
|
+
options: RootOptionsInternal,
|
|
13
|
+
): FiberRoot {
|
|
14
|
+
const rootFiber = createFiber(FiberTag.Root, null, null)
|
|
15
|
+
const root: FiberRoot = {
|
|
16
|
+
c: container as any,
|
|
17
|
+
r: rootFiber,
|
|
18
|
+
p: new Set(),
|
|
19
|
+
s: false,
|
|
20
|
+
re: options.onRecoverableError,
|
|
21
|
+
ce: options.onCaughtError,
|
|
22
|
+
ue: options.onUncaughtError,
|
|
23
|
+
i: options.identifierPrefix ?? ':r',
|
|
24
|
+
h: false,
|
|
25
|
+
}
|
|
26
|
+
rootFiber.root = root
|
|
27
|
+
rootFiber.sn = container
|
|
28
|
+
return root
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function attachRootFiber(
|
|
32
|
+
root: FiberRoot,
|
|
33
|
+
container: Element | Document | DocumentFragment,
|
|
34
|
+
): void {
|
|
35
|
+
const rootFiber = createFiber(FiberTag.Root, null, null)
|
|
36
|
+
root.c = container as any
|
|
37
|
+
rootFiber.root = root
|
|
38
|
+
rootFiber.sn = container
|
|
39
|
+
root.r = rootFiber
|
|
40
|
+
}
|
package/src/dom/root.ts
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FiberTag,
|
|
3
|
-
REACT_ELEMENT_TYPE,
|
|
4
|
-
createFiber,
|
|
5
|
-
type FiberRoot,
|
|
6
|
-
type ReactElement,
|
|
7
|
-
type ReactNode,
|
|
8
|
-
} from '../core'
|
|
1
|
+
import type { ReactNode } from '../core'
|
|
9
2
|
import { renderRoot, flushSyncWork, batchedUpdates } from './reconcile'
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
endHydration,
|
|
13
|
-
drainReplayQueue,
|
|
14
|
-
installHydrationScrollGuard,
|
|
15
|
-
} from './features/hydration'
|
|
3
|
+
import { hydrateRootImpl } from './features/hydration'
|
|
4
|
+
import { createFiberRoot } from './root-internal'
|
|
16
5
|
|
|
17
6
|
export interface RootOptions {
|
|
18
7
|
identifierPrefix?: string
|
|
@@ -27,21 +16,7 @@ export interface Root {
|
|
|
27
16
|
}
|
|
28
17
|
|
|
29
18
|
export function createRoot(container: Element | DocumentFragment, options: RootOptions = {}): Root {
|
|
30
|
-
const
|
|
31
|
-
rootFiber.dom = container
|
|
32
|
-
const root: FiberRoot = {
|
|
33
|
-
container,
|
|
34
|
-
current: rootFiber,
|
|
35
|
-
pending: new Set(),
|
|
36
|
-
scheduled: false,
|
|
37
|
-
onRecoverableError: options.onRecoverableError,
|
|
38
|
-
onCaughtError: options.onCaughtError,
|
|
39
|
-
onUncaughtError: options.onUncaughtError,
|
|
40
|
-
identifierPrefix: options.identifierPrefix ?? ':r',
|
|
41
|
-
hydrating: false,
|
|
42
|
-
}
|
|
43
|
-
rootFiber.root = root
|
|
44
|
-
rootFiber.stateNode = container
|
|
19
|
+
const root = createFiberRoot(container, options)
|
|
45
20
|
|
|
46
21
|
let firstRender = true
|
|
47
22
|
return {
|
|
@@ -72,142 +47,7 @@ export function hydrateRoot(
|
|
|
72
47
|
initialChildren: ReactNode,
|
|
73
48
|
options: RootOptions = {},
|
|
74
49
|
): Root {
|
|
75
|
-
|
|
76
|
-
// (e.g. TanStack Start's default client entry). In that case we adopt
|
|
77
|
-
// documentElement as a CHILD of the root, not as the root itself — otherwise
|
|
78
|
-
// we'd try to render <html> inside <html>.
|
|
79
|
-
const target = container as any as Element | Document
|
|
80
|
-
const rootFiber = createFiber(FiberTag.Root, null, null)
|
|
81
|
-
rootFiber.dom = target as unknown as Node
|
|
82
|
-
const root: FiberRoot = {
|
|
83
|
-
container: target as any,
|
|
84
|
-
current: rootFiber,
|
|
85
|
-
pending: new Set(),
|
|
86
|
-
scheduled: false,
|
|
87
|
-
onRecoverableError: options.onRecoverableError,
|
|
88
|
-
onCaughtError: options.onCaughtError,
|
|
89
|
-
onUncaughtError: options.onUncaughtError,
|
|
90
|
-
identifierPrefix: options.identifierPrefix ?? ':r',
|
|
91
|
-
hydrating: false,
|
|
92
|
-
}
|
|
93
|
-
rootFiber.root = root
|
|
94
|
-
rootFiber.stateNode = target
|
|
95
|
-
|
|
96
|
-
// Preserve the user's scroll position across hydration (see feature impl
|
|
97
|
-
// for the details). No-op in SSR; no-op in the stub.
|
|
98
|
-
installHydrationScrollGuard()
|
|
99
|
-
|
|
100
|
-
beginHydration(root)
|
|
101
|
-
try {
|
|
102
|
-
const normalizedInitialChildren =
|
|
103
|
-
isDocumentContainer(container) ? normalizeDocumentChildren(initialChildren) : initialChildren
|
|
104
|
-
flushSyncWork(() => {
|
|
105
|
-
renderRoot(root, normalizedInitialChildren)
|
|
106
|
-
})
|
|
107
|
-
} finally {
|
|
108
|
-
endHydration(root)
|
|
109
|
-
}
|
|
110
|
-
drainReplayQueue()
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
render(children) {
|
|
114
|
-
flushSyncWork(() => {
|
|
115
|
-
renderRoot(root, isDocumentContainer(container) ? normalizeDocumentChildren(children) : children)
|
|
116
|
-
})
|
|
117
|
-
},
|
|
118
|
-
unmount() {
|
|
119
|
-
flushSyncWork(() => {
|
|
120
|
-
renderRoot(root, null)
|
|
121
|
-
})
|
|
122
|
-
},
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const HEAD_TAGS = new Set(['base', 'link', 'meta', 'script', 'style', 'title'])
|
|
127
|
-
|
|
128
|
-
function isDocumentContainer(container: unknown): container is Document {
|
|
129
|
-
return !!container && (container as Node).nodeType === 9
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function normalizeDocumentChildren(children: ReactNode): ReactNode {
|
|
133
|
-
const list = toChildArray(children)
|
|
134
|
-
const htmlIndex = list.findIndex((child) => isHostElement(child, 'html'))
|
|
135
|
-
if (htmlIndex === -1) return children
|
|
136
|
-
|
|
137
|
-
const headNodes = list.filter(isHeadElement)
|
|
138
|
-
if (headNodes.length === 0) return children
|
|
139
|
-
|
|
140
|
-
const htmlElement = list[htmlIndex] as ReactElement
|
|
141
|
-
const normalizedHtml = hoistIntoHtmlHead(htmlElement, headNodes)
|
|
142
|
-
return list
|
|
143
|
-
.filter((child, index) => index === htmlIndex || !isHeadElement(child))
|
|
144
|
-
.map((child) => (child === htmlElement ? normalizedHtml : child))
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function hoistIntoHtmlHead(htmlElement: ReactElement, headNodes: ReactNode[]): ReactElement {
|
|
148
|
-
const htmlChildren = toChildArray(htmlElement.props?.children)
|
|
149
|
-
const headIndex = htmlChildren.findIndex((child) => isHostElement(child, 'head'))
|
|
150
|
-
let nextChildren: ReactNode[]
|
|
151
|
-
|
|
152
|
-
if (headIndex === -1) {
|
|
153
|
-
nextChildren = [
|
|
154
|
-
createHostElement('head', { children: headNodes }),
|
|
155
|
-
...htmlChildren,
|
|
156
|
-
]
|
|
157
|
-
} else {
|
|
158
|
-
const headElement = htmlChildren[headIndex] as ReactElement
|
|
159
|
-
const existingHeadChildren = toChildArray(headElement.props?.children)
|
|
160
|
-
const nextHead = {
|
|
161
|
-
...headElement,
|
|
162
|
-
props: {
|
|
163
|
-
...headElement.props,
|
|
164
|
-
children: [...headNodes, ...existingHeadChildren],
|
|
165
|
-
},
|
|
166
|
-
}
|
|
167
|
-
nextChildren = htmlChildren.map((child, index) => (index === headIndex ? nextHead : child))
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
...htmlElement,
|
|
172
|
-
props: {
|
|
173
|
-
...htmlElement.props,
|
|
174
|
-
children: nextChildren,
|
|
175
|
-
},
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function toChildArray(children: unknown): ReactNode[] {
|
|
180
|
-
if (children == null || typeof children === 'boolean') return []
|
|
181
|
-
if (Array.isArray(children)) return children as ReactNode[]
|
|
182
|
-
if (isReactElement(children)) return [children]
|
|
183
|
-
if (typeof children !== 'string' && isIterable(children)) return Array.from(children) as ReactNode[]
|
|
184
|
-
return [children as ReactNode]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function isHeadElement(value: ReactNode): boolean {
|
|
188
|
-
return isReactElement(value) && typeof value.type === 'string' && HEAD_TAGS.has(value.type)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function isHostElement(value: ReactNode, tag: string): boolean {
|
|
192
|
-
return isReactElement(value) && value.type === tag
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function isReactElement(value: unknown): value is ReactElement {
|
|
196
|
-
return !!value && typeof value === 'object' && (value as ReactElement).$$typeof === REACT_ELEMENT_TYPE
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function isIterable(value: unknown): value is Iterable<ReactNode> {
|
|
200
|
-
return !!value && typeof (value as { [Symbol.iterator]?: unknown })[Symbol.iterator] === 'function'
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function createHostElement(type: string, props: Record<string, unknown>): ReactElement {
|
|
204
|
-
return {
|
|
205
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
206
|
-
type,
|
|
207
|
-
key: null,
|
|
208
|
-
ref: null,
|
|
209
|
-
props,
|
|
210
|
-
}
|
|
50
|
+
return hydrateRootImpl(container, initialChildren, options)
|
|
211
51
|
}
|
|
212
52
|
|
|
213
53
|
export { flushSyncWork as flushSync, batchedUpdates }
|
package/src/dom/test-utils.ts
CHANGED
package/src/react/children.ts
CHANGED
|
@@ -78,7 +78,10 @@ export const Children = {
|
|
|
78
78
|
},
|
|
79
79
|
only(children: ReactNode): ReactElement {
|
|
80
80
|
if (!isValidElement(children)) {
|
|
81
|
-
|
|
81
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
82
|
+
throw new Error('Children.only expected a single React element.')
|
|
83
|
+
}
|
|
84
|
+
throw new Error()
|
|
82
85
|
}
|
|
83
86
|
return children
|
|
84
87
|
},
|
package/src/react/class.ts
CHANGED
|
@@ -29,7 +29,10 @@ export class Component<P = {}, S = {}> {
|
|
|
29
29
|
|
|
30
30
|
setState(updater: SetStateCallback<S, P>, callback?: () => void): void {
|
|
31
31
|
if (!this._enqueueUpdate) {
|
|
32
|
-
|
|
32
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
33
|
+
throw new Error('Cannot call setState on an um component.')
|
|
34
|
+
}
|
|
35
|
+
throw new Error()
|
|
33
36
|
}
|
|
34
37
|
this._enqueueUpdate(updater, callback)
|
|
35
38
|
}
|
|
@@ -40,7 +43,10 @@ export class Component<P = {}, S = {}> {
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
render(): ReactNode {
|
|
43
|
-
|
|
46
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
47
|
+
throw new Error('Component subclass must implement render().')
|
|
48
|
+
}
|
|
49
|
+
throw new Error()
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
componentDidMount?(): void
|
package/src/react/context.ts
CHANGED
|
@@ -32,7 +32,10 @@ export function createContext<T>(defaultValue: T): Context<T> {
|
|
|
32
32
|
} as Context<T>
|
|
33
33
|
|
|
34
34
|
const Provider: any = function Provider(_props: any): any {
|
|
35
|
-
|
|
35
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
36
|
+
throw new Error('Provider components are handled by the renderer.')
|
|
37
|
+
}
|
|
38
|
+
throw new Error()
|
|
36
39
|
}
|
|
37
40
|
Provider.$$typeof = REACT_PROVIDER_TYPE
|
|
38
41
|
Provider._context = context
|
package/src/react/index.ts
CHANGED
|
@@ -70,7 +70,7 @@ export { ReactSharedInternals } from './shared-internals'
|
|
|
70
70
|
export const cache = <T extends Function>(fn: T): T => fn
|
|
71
71
|
export const act = async (fn: () => any) => {
|
|
72
72
|
const r = fn()
|
|
73
|
-
if (r && typeof r.then
|
|
73
|
+
if (r && typeof r.then == 'function') await r
|
|
74
74
|
}
|
|
75
75
|
export function taintUniqueValue(_msg: string, _lifetime: any, _value: any): void {}
|
|
76
76
|
export function taintObjectReference(_msg: string, _object: any): void {}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fiber,
|
|
1
|
+
import type { Fiber, Hook } from '../core'
|
|
2
2
|
|
|
3
3
|
export interface Dispatcher {
|
|
4
4
|
useState<S>(initial: S | (() => S)): [S, (s: S | ((p: S) => S)) => void]
|
|
@@ -29,12 +29,9 @@ export interface Dispatcher {
|
|
|
29
29
|
|
|
30
30
|
interface SharedInternals {
|
|
31
31
|
H: Dispatcher | null
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
currentRoot: FiberRoot | null
|
|
36
|
-
currentHook: Hook | null
|
|
37
|
-
hookIndex: number
|
|
32
|
+
F: Fiber | null
|
|
33
|
+
K: Hook | null
|
|
34
|
+
I: number
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
// Stash the singleton on `globalThis` under a registered symbol. Module-scoped
|
|
@@ -54,12 +51,9 @@ export const ReactSharedInternals: SharedInternals =
|
|
|
54
51
|
g[KEY] ??
|
|
55
52
|
(g[KEY] = {
|
|
56
53
|
H: null,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
currentRoot: null,
|
|
61
|
-
currentHook: null,
|
|
62
|
-
hookIndex: 0,
|
|
54
|
+
F: null,
|
|
55
|
+
K: null,
|
|
56
|
+
I: 0,
|
|
63
57
|
})
|
|
64
58
|
|
|
65
59
|
export function getDispatcher(): Dispatcher {
|
|
@@ -73,7 +67,7 @@ export function getDispatcher(): Dispatcher {
|
|
|
73
67
|
'"use client" directive at the top of its file.',
|
|
74
68
|
)
|
|
75
69
|
}
|
|
76
|
-
throw new Error(
|
|
70
|
+
throw new Error()
|
|
77
71
|
}
|
|
78
72
|
return d
|
|
79
73
|
}
|
package/src/server/stream.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface ReadableStreamResult extends ReadableStream<Uint8Array> {
|
|
|
25
25
|
allReady: Promise<void>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export interface PipeableWritable {
|
|
29
|
+
write(chunk: string): unknown
|
|
30
|
+
end(): unknown
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
export interface OrchestratorState {
|
|
29
34
|
nextId: number
|
|
30
35
|
pending: Set<Promise<void>>
|
|
@@ -302,7 +307,7 @@ export function renderToReadableStream(
|
|
|
302
307
|
// ---------------------------------------------------------------------------
|
|
303
308
|
|
|
304
309
|
export interface PipeableHandle {
|
|
305
|
-
pipe<T extends
|
|
310
|
+
pipe<T extends PipeableWritable>(dest: T): T
|
|
306
311
|
abort(reason?: unknown): void
|
|
307
312
|
}
|
|
308
313
|
|
|
@@ -324,12 +329,12 @@ export function renderToPipeableStream(
|
|
|
324
329
|
}
|
|
325
330
|
|
|
326
331
|
const buffers: string[] = []
|
|
327
|
-
let dest:
|
|
332
|
+
let dest: PipeableWritable | null = null
|
|
328
333
|
let shellReady = false
|
|
329
334
|
let finished = false
|
|
330
335
|
let aborted = false
|
|
331
336
|
|
|
332
|
-
const flushTo = (w:
|
|
337
|
+
const flushTo = (w: PipeableWritable) => {
|
|
333
338
|
if (!buffers.length) return
|
|
334
339
|
for (const b of buffers) w.write(b)
|
|
335
340
|
buffers.length = 0
|
|
@@ -368,7 +373,7 @@ export function renderToPipeableStream(
|
|
|
368
373
|
})
|
|
369
374
|
|
|
370
375
|
return {
|
|
371
|
-
pipe<T extends
|
|
376
|
+
pipe<T extends PipeableWritable>(target: T): T {
|
|
372
377
|
dest = target
|
|
373
378
|
flushTo(target)
|
|
374
379
|
if (finished) target.end()
|