@smithers-orchestrator/react-reconciler 0.16.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/LICENSE +21 -0
- package/package.json +64 -0
- package/src/HostContainer.ts +5 -0
- package/src/OutputSnapshot.ts +1 -0
- package/src/SmithersCtxOptions.ts +1 -0
- package/src/SmithersRendererOptions.ts +5 -0
- package/src/context.js +32 -0
- package/src/core-peer.js +33 -0
- package/src/core-types.js +1 -0
- package/src/devtools/SmithersDevTools.js +360 -0
- package/src/devtools/index.js +12 -0
- package/src/devtools/preload.js +11 -0
- package/src/dom/renderer.js +1 -0
- package/src/driver.js +19 -0
- package/src/index.d.ts +87 -0
- package/src/index.js +3 -0
- package/src/jsx-runtime.js +3 -0
- package/src/reconciler.js +387 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 William Cory
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smithers-orchestrator/react-reconciler",
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"description": "Custom React reconciler that renders Smithers host trees",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/index.d.ts",
|
|
10
|
+
"import": "./src/index.js",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./context": {
|
|
14
|
+
"types": "./src/index.d.ts",
|
|
15
|
+
"import": "./src/context.js",
|
|
16
|
+
"default": "./src/context.js"
|
|
17
|
+
},
|
|
18
|
+
"./devtools": {
|
|
19
|
+
"types": "./src/index.d.ts",
|
|
20
|
+
"import": "./src/devtools/index.js",
|
|
21
|
+
"default": "./src/devtools/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./devtools/preload": {
|
|
24
|
+
"import": "./src/devtools/preload.js",
|
|
25
|
+
"default": "./src/devtools/preload.js"
|
|
26
|
+
},
|
|
27
|
+
"./driver": {
|
|
28
|
+
"types": "./src/index.d.ts",
|
|
29
|
+
"import": "./src/driver.js",
|
|
30
|
+
"default": "./src/driver.js"
|
|
31
|
+
},
|
|
32
|
+
"./jsx-runtime": {
|
|
33
|
+
"types": "./src/index.d.ts",
|
|
34
|
+
"import": "./src/jsx-runtime.js",
|
|
35
|
+
"default": "./src/jsx-runtime.js"
|
|
36
|
+
},
|
|
37
|
+
"./*": {
|
|
38
|
+
"types": "./src/index.d.ts",
|
|
39
|
+
"import": "./src/*.js",
|
|
40
|
+
"default": "./src/*.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"src/"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"react": "^18.3.0 || ^19.0.0",
|
|
48
|
+
"react-reconciler": "^0.31.0",
|
|
49
|
+
"bippy": "^0.5.32",
|
|
50
|
+
"@smithers-orchestrator/devtools": "0.16.0",
|
|
51
|
+
"@smithers-orchestrator/errors": "0.16.0",
|
|
52
|
+
"@smithers-orchestrator/driver": "0.16.0",
|
|
53
|
+
"@smithers-orchestrator/graph": "0.16.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/bun": "latest",
|
|
57
|
+
"typescript": "~5.9.3"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "rm -f src/index.d.ts && tsup --dts-only",
|
|
61
|
+
"test": "bun test tests",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { OutputSnapshot } from "@smithers-orchestrator/driver/OutputSnapshot";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { SmithersCtxOptions } from "@smithers-orchestrator/driver/SmithersCtx";
|
package/src/context.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("./OutputSnapshot.ts").OutputSnapshot} OutputSnapshot */
|
|
3
|
+
/** @typedef {import("./SmithersCtxOptions.ts").SmithersCtxOptions} SmithersCtxOptions */
|
|
4
|
+
// @smithers-type-exports-end
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { SmithersCtx } from "@smithers-orchestrator/driver/SmithersCtx";
|
|
8
|
+
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
9
|
+
export { SmithersCtx } from "@smithers-orchestrator/driver/SmithersCtx";
|
|
10
|
+
/** @type {React.Context<SmithersCtx<any> | null>} */
|
|
11
|
+
export const SmithersContext = React.createContext(null);
|
|
12
|
+
SmithersContext.displayName = "SmithersContext";
|
|
13
|
+
/**
|
|
14
|
+
* @template Schema
|
|
15
|
+
* @returns {{ SmithersContext: React.Context<SmithersCtx<Schema> | null>, useCtx: () => SmithersCtx<Schema> }}
|
|
16
|
+
*/
|
|
17
|
+
export function createSmithersContext() {
|
|
18
|
+
/** @type {React.Context<SmithersCtx<Schema> | null>} */
|
|
19
|
+
const Context = React.createContext(null);
|
|
20
|
+
Context.displayName = "SmithersContext";
|
|
21
|
+
/**
|
|
22
|
+
* @returns {SmithersCtx<Schema>}
|
|
23
|
+
*/
|
|
24
|
+
function useCtx() {
|
|
25
|
+
const ctx = React.useContext(Context);
|
|
26
|
+
if (!ctx) {
|
|
27
|
+
throw new SmithersError("CONTEXT_OUTSIDE_WORKFLOW", "useCtx() must be called inside a <Workflow> created by createSmithers()");
|
|
28
|
+
}
|
|
29
|
+
return ctx;
|
|
30
|
+
}
|
|
31
|
+
return { SmithersContext: Context, useCtx };
|
|
32
|
+
}
|
package/src/core-peer.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").ExtractGraph} ExtractGraph */
|
|
3
|
+
const GRAPH_SPECIFIER = "@smithers-orchestrator/graph";
|
|
4
|
+
const LOCAL_GRAPH_SPECIFIER = "../../graph/src/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} specifier
|
|
7
|
+
* @returns {Promise<CoreModule | null>}
|
|
8
|
+
*/
|
|
9
|
+
async function importCoreModule(specifier) {
|
|
10
|
+
try {
|
|
11
|
+
return (await import(specifier));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* @returns {Promise<ExtractGraph>}
|
|
19
|
+
*/
|
|
20
|
+
export async function resolveExtractGraph() {
|
|
21
|
+
const modules = [
|
|
22
|
+
await importCoreModule(GRAPH_SPECIFIER),
|
|
23
|
+
await importCoreModule(LOCAL_GRAPH_SPECIFIER),
|
|
24
|
+
];
|
|
25
|
+
for (const mod of modules) {
|
|
26
|
+
const fn = mod?.extractGraph;
|
|
27
|
+
if (typeof fn === "function") {
|
|
28
|
+
return fn;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error("Unable to load extractGraph from @smithers-orchestrator/graph. " +
|
|
32
|
+
"Install @smithers-orchestrator/graph and ensure it exports extractGraph.");
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@smithers-orchestrator/graph";
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsEventBus} DevToolsEventBus */
|
|
3
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsEventHandler} DevToolsEventHandler */
|
|
4
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsSnapshot} DevToolsSnapshot */
|
|
5
|
+
/** @typedef {import("@smithers-orchestrator/devtools").RunExecutionState} RunExecutionState */
|
|
6
|
+
/** @typedef {import("@smithers-orchestrator/devtools").SmithersNodeType} SmithersNodeType */
|
|
7
|
+
/** @typedef {import("@smithers-orchestrator/devtools").TaskExecutionState} TaskExecutionState */
|
|
8
|
+
// @smithers-type-exports-end
|
|
9
|
+
|
|
10
|
+
import { instrument, secure, installRDTHook, traverseFiber, getDisplayName, isHostFiber, getFiberId, setFiberId, } from "bippy";
|
|
11
|
+
import { SmithersDevToolsCore, printTree, } from "@smithers-orchestrator/devtools";
|
|
12
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsNode} DevToolsNode */
|
|
13
|
+
/** @typedef {import("@smithers-orchestrator/devtools").SmithersDevToolsCore} SmithersDevToolsCoreType */
|
|
14
|
+
/** @typedef {import("@smithers-orchestrator/devtools").SmithersDevToolsOptions} SmithersDevToolsOptions */
|
|
15
|
+
/** @typedef {import("bippy").Fiber} Fiber */
|
|
16
|
+
/** @typedef {import("bippy").FiberRoot} FiberRoot */
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// React host tag mapping
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const HOST_TAG_MAP = {
|
|
22
|
+
"smithers:workflow": "workflow",
|
|
23
|
+
"smithers:task": "task",
|
|
24
|
+
"smithers:sequence": "sequence",
|
|
25
|
+
"smithers:parallel": "parallel",
|
|
26
|
+
"smithers:merge-queue": "merge-queue",
|
|
27
|
+
"smithers:branch": "branch",
|
|
28
|
+
"smithers:ralph": "loop",
|
|
29
|
+
"smithers:worktree": "worktree",
|
|
30
|
+
"smithers:approval": "approval",
|
|
31
|
+
"smithers:timer": "timer",
|
|
32
|
+
"smithers:subflow": "subflow",
|
|
33
|
+
"smithers:wait-for-event": "wait-for-event",
|
|
34
|
+
"smithers:saga": "saga",
|
|
35
|
+
"smithers:try-catch-finally": "try-catch",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* @param {Fiber} fiber
|
|
39
|
+
* @returns {SmithersNodeType | null}
|
|
40
|
+
*/
|
|
41
|
+
function resolveNodeType(fiber) {
|
|
42
|
+
// Only match host fibers (smithers:* tags).
|
|
43
|
+
// Composite fibers (Task, Workflow, etc.) are pass-throughs that always
|
|
44
|
+
// create a host fiber underneath, so matching both would double-count.
|
|
45
|
+
if (isHostFiber(fiber)) {
|
|
46
|
+
const tag = fiber.type;
|
|
47
|
+
return HOST_TAG_MAP[tag] ?? null;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Fiber to DevToolsNode
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
/**
|
|
55
|
+
* @param {Fiber} fiber
|
|
56
|
+
* @returns {Record<string, unknown>}
|
|
57
|
+
*/
|
|
58
|
+
function extractSerializableProps(fiber) {
|
|
59
|
+
const raw = fiber.memoizedProps;
|
|
60
|
+
if (!raw || typeof raw !== "object")
|
|
61
|
+
return {};
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
64
|
+
if (key.startsWith("__"))
|
|
65
|
+
continue;
|
|
66
|
+
if (key === "children")
|
|
67
|
+
continue;
|
|
68
|
+
if (typeof value === "function") {
|
|
69
|
+
out[key] = "[Function]";
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (typeof value === "object" && value !== null) {
|
|
73
|
+
// Agent objects, Zod schemas, Drizzle tables: record a stable label only.
|
|
74
|
+
if ("modelId" in value || "model" in value) {
|
|
75
|
+
out[key] = `[Agent: ${value.modelId ?? value.model ?? "unknown"}]`;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if ("shape" in value) {
|
|
79
|
+
out[key] = "[ZodSchema]";
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
out[key] = "[Object]";
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
out[key] = value;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* @param {Fiber} fiber
|
|
91
|
+
* @returns {DevToolsNode["task"] | undefined}
|
|
92
|
+
*/
|
|
93
|
+
function extractTaskInfo(fiber) {
|
|
94
|
+
const raw = fiber.memoizedProps;
|
|
95
|
+
if (!raw || typeof raw !== "object")
|
|
96
|
+
return undefined;
|
|
97
|
+
const nodeId = raw.id;
|
|
98
|
+
if (typeof nodeId !== "string")
|
|
99
|
+
return undefined;
|
|
100
|
+
const kind = raw.__smithersKind === "agent"
|
|
101
|
+
? "agent"
|
|
102
|
+
: raw.__smithersKind === "compute"
|
|
103
|
+
? "compute"
|
|
104
|
+
: "static";
|
|
105
|
+
const agent = raw.agent;
|
|
106
|
+
let agentName;
|
|
107
|
+
if (agent) {
|
|
108
|
+
if (Array.isArray(agent)) {
|
|
109
|
+
agentName = agent
|
|
110
|
+
.map((a) => a?.modelId ?? a?.model ?? "?")
|
|
111
|
+
.join(" → ");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
agentName = agent.modelId ?? agent.model ?? "agent";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
nodeId,
|
|
119
|
+
kind,
|
|
120
|
+
agent: agentName,
|
|
121
|
+
label: raw.label,
|
|
122
|
+
outputTableName: typeof raw.output === "string" ? raw.output : undefined,
|
|
123
|
+
iteration: raw.iteration,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* @param {Fiber} fiber
|
|
128
|
+
* @param {number} depth
|
|
129
|
+
* @returns {DevToolsNode | null}
|
|
130
|
+
*/
|
|
131
|
+
function fiberToNode(fiber, depth) {
|
|
132
|
+
const nodeType = resolveNodeType(fiber);
|
|
133
|
+
if (!nodeType)
|
|
134
|
+
return null;
|
|
135
|
+
const id = getFiberId(fiber) ?? setFiberId(fiber);
|
|
136
|
+
const children = [];
|
|
137
|
+
let child = fiber.child;
|
|
138
|
+
while (child) {
|
|
139
|
+
const childNode = fiberToNode(child, depth + 1);
|
|
140
|
+
if (childNode) {
|
|
141
|
+
children.push(childNode);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Recurse through non-Smithers fibers to find nested Smithers nodes.
|
|
145
|
+
let grandchild = child.child;
|
|
146
|
+
while (grandchild) {
|
|
147
|
+
const gc = fiberToNode(grandchild, depth + 1);
|
|
148
|
+
if (gc)
|
|
149
|
+
children.push(gc);
|
|
150
|
+
grandchild = grandchild.sibling;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
child = child.sibling;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
id,
|
|
157
|
+
type: nodeType,
|
|
158
|
+
name: getDisplayName(fiber) ?? fiber.type ?? "unknown",
|
|
159
|
+
props: extractSerializableProps(fiber),
|
|
160
|
+
task: nodeType === "task" ? extractTaskInfo(fiber) : undefined,
|
|
161
|
+
children,
|
|
162
|
+
depth,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Walk fiber root to find Smithers root
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
/**
|
|
169
|
+
* @param {FiberRoot | null | undefined} fiberRoot
|
|
170
|
+
* @returns {Fiber | null}
|
|
171
|
+
*/
|
|
172
|
+
function findSmithersRoot(fiberRoot) {
|
|
173
|
+
const current = fiberRoot?.current;
|
|
174
|
+
if (!current)
|
|
175
|
+
return null;
|
|
176
|
+
let result = null;
|
|
177
|
+
traverseFiber(current, (fiber) => {
|
|
178
|
+
const nodeType = resolveNodeType(fiber);
|
|
179
|
+
if (nodeType === "workflow") {
|
|
180
|
+
result = fiber;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
});
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// React adapter
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
export class SmithersDevTools {
|
|
191
|
+
/** @type {SmithersDevToolsOptions} */
|
|
192
|
+
options;
|
|
193
|
+
/** @type {SmithersDevToolsCoreType} */
|
|
194
|
+
core;
|
|
195
|
+
_active = false;
|
|
196
|
+
_cleanup = null;
|
|
197
|
+
/**
|
|
198
|
+
* @param {SmithersDevToolsOptions} [options]
|
|
199
|
+
*/
|
|
200
|
+
constructor(options = {}) {
|
|
201
|
+
this.options = options;
|
|
202
|
+
this.core = new SmithersDevToolsCore(options);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Start instrumenting. Installs the React DevTools global hook (if not
|
|
206
|
+
* already present) and begins listening for fiber commits.
|
|
207
|
+
*
|
|
208
|
+
* For best results, call this before `SmithersRenderer` is first imported.
|
|
209
|
+
* If the renderer is already loaded, it will still work as long as the
|
|
210
|
+
* renderer called `injectIntoDevTools` (which it does by default).
|
|
211
|
+
*/
|
|
212
|
+
start() {
|
|
213
|
+
if (this._active)
|
|
214
|
+
return this;
|
|
215
|
+
this._active = true;
|
|
216
|
+
// Avoid reinstalling the hook after renderers have already injected
|
|
217
|
+
// themselves, otherwise we lose their registrations.
|
|
218
|
+
if (!("__REACT_DEVTOOLS_GLOBAL_HOOK__" in globalThis)) {
|
|
219
|
+
installRDTHook();
|
|
220
|
+
}
|
|
221
|
+
const self = this;
|
|
222
|
+
const verbose = this.options.verbose ?? false;
|
|
223
|
+
const hookHost = globalThis;
|
|
224
|
+
const hook = hookHost.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
225
|
+
const previousRootHandler = hook?.onCommitFiberRoot;
|
|
226
|
+
const previousUnmountHandler = hook?.onCommitFiberUnmount;
|
|
227
|
+
instrument(secure({
|
|
228
|
+
/**
|
|
229
|
+
* @param {number} rendererID
|
|
230
|
+
* @param {FiberRoot} root
|
|
231
|
+
* @returns {void}
|
|
232
|
+
*/
|
|
233
|
+
onCommitFiberRoot(rendererID, root) {
|
|
234
|
+
const smithersRoot = findSmithersRoot(root);
|
|
235
|
+
const tree = smithersRoot ? fiberToNode(smithersRoot, 0) : null;
|
|
236
|
+
const snapshot = self.core.captureSnapshot(tree);
|
|
237
|
+
if (verbose && tree) {
|
|
238
|
+
console.log("\n🔍 [smithers-devtools] Commit detected:\n" +
|
|
239
|
+
printTree(tree) +
|
|
240
|
+
` ${snapshot.nodeCount} nodes, ${snapshot.taskCount} tasks\n`);
|
|
241
|
+
}
|
|
242
|
+
self.core.emitCommit(snapshot);
|
|
243
|
+
},
|
|
244
|
+
/**
|
|
245
|
+
* @param {number} _rendererID
|
|
246
|
+
* @param {Fiber} fiber
|
|
247
|
+
* @returns {void}
|
|
248
|
+
*/
|
|
249
|
+
onCommitFiberUnmount(_rendererID, fiber) {
|
|
250
|
+
const nodeType = resolveNodeType(fiber);
|
|
251
|
+
if (nodeType && verbose) {
|
|
252
|
+
const name = getDisplayName(fiber) ?? fiber.type;
|
|
253
|
+
console.log(`🗑️ [smithers-devtools] Unmounted: ${nodeType} (${name})`);
|
|
254
|
+
}
|
|
255
|
+
self.core.emitUnmount();
|
|
256
|
+
},
|
|
257
|
+
}));
|
|
258
|
+
const installedRootHandler = hook?.onCommitFiberRoot;
|
|
259
|
+
const installedUnmountHandler = hook?.onCommitFiberUnmount;
|
|
260
|
+
this._cleanup = () => {
|
|
261
|
+
if (!hook)
|
|
262
|
+
return;
|
|
263
|
+
if (hook.onCommitFiberRoot === installedRootHandler) {
|
|
264
|
+
if (previousRootHandler) {
|
|
265
|
+
hook.onCommitFiberRoot = previousRootHandler;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
delete hook.onCommitFiberRoot;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (hook.onCommitFiberUnmount === installedUnmountHandler) {
|
|
272
|
+
if (previousUnmountHandler) {
|
|
273
|
+
hook.onCommitFiberUnmount = previousUnmountHandler;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
delete hook.onCommitFiberUnmount;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
/** Stop instrumenting and clean up. */
|
|
283
|
+
stop() {
|
|
284
|
+
this._cleanup?.();
|
|
285
|
+
this._cleanup = null;
|
|
286
|
+
this._active = false;
|
|
287
|
+
this.core.detachEventBuses();
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Attach to a Smithers EventBus to track task execution state.
|
|
291
|
+
* Listens for SmithersEvent emissions and builds up a run state model.
|
|
292
|
+
* @param {DevToolsEventBus} bus
|
|
293
|
+
* @returns {this}
|
|
294
|
+
*/
|
|
295
|
+
attachEventBus(bus) {
|
|
296
|
+
this.core.attachEventBus(bus);
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get execution state for a specific run.
|
|
301
|
+
* @param {string} runId
|
|
302
|
+
* @returns {RunExecutionState | undefined}
|
|
303
|
+
*/
|
|
304
|
+
getRun(runId) {
|
|
305
|
+
return this.core.getRun(runId);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get all tracked runs.
|
|
309
|
+
* @returns {Map<string, RunExecutionState>}
|
|
310
|
+
*/
|
|
311
|
+
get runs() {
|
|
312
|
+
return this.core.runs;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get task execution state by nodeId within a run. Searches all iterations.
|
|
316
|
+
* @param {string} runId
|
|
317
|
+
* @param {string} nodeId
|
|
318
|
+
* @param {number} [iteration]
|
|
319
|
+
* @returns {TaskExecutionState | undefined}
|
|
320
|
+
*/
|
|
321
|
+
getTaskState(runId, nodeId, iteration) {
|
|
322
|
+
return this.core.getTaskState(runId, nodeId, iteration);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get the last captured snapshot.
|
|
326
|
+
* @returns {DevToolsSnapshot | null}
|
|
327
|
+
*/
|
|
328
|
+
get snapshot() {
|
|
329
|
+
return this.core.snapshot;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get the current tree (shorthand).
|
|
333
|
+
* @returns {DevToolsNode | null}
|
|
334
|
+
*/
|
|
335
|
+
get tree() {
|
|
336
|
+
return this.core.tree;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Pretty-print the current tree to a string.
|
|
340
|
+
* @returns {string}
|
|
341
|
+
*/
|
|
342
|
+
printTree() {
|
|
343
|
+
return this.core.printTree();
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Find a node by task nodeId.
|
|
347
|
+
* @param {string} nodeId
|
|
348
|
+
* @returns {DevToolsNode | null}
|
|
349
|
+
*/
|
|
350
|
+
findTask(nodeId) {
|
|
351
|
+
return this.core.findTask(nodeId);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* List all tasks in the current tree.
|
|
355
|
+
* @returns {DevToolsNode[]}
|
|
356
|
+
*/
|
|
357
|
+
listTasks() {
|
|
358
|
+
return this.core.listTasks();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @smithers-type-exports-begin
|
|
2
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsEventBus} DevToolsEventBus */
|
|
3
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsEventHandler} DevToolsEventHandler */
|
|
4
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsNode} DevToolsNode */
|
|
5
|
+
/** @typedef {import("@smithers-orchestrator/devtools").DevToolsSnapshot} DevToolsSnapshot */
|
|
6
|
+
/** @typedef {import("@smithers-orchestrator/devtools").RunExecutionState} RunExecutionState */
|
|
7
|
+
/** @typedef {import("@smithers-orchestrator/devtools").SmithersDevToolsOptions} SmithersDevToolsOptions */
|
|
8
|
+
/** @typedef {import("@smithers-orchestrator/devtools").SmithersNodeType} SmithersNodeType */
|
|
9
|
+
/** @typedef {import("@smithers-orchestrator/devtools").TaskExecutionState} TaskExecutionState */
|
|
10
|
+
// @smithers-type-exports-end
|
|
11
|
+
|
|
12
|
+
export { SmithersDevTools } from "./SmithersDevTools.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preload script that installs the React DevTools global hook before
|
|
3
|
+
* any React code runs. Use this as a Bun preload:
|
|
4
|
+
*
|
|
5
|
+
* bun --preload ./src/devtools/preload.js run myworkflow.tsx
|
|
6
|
+
*
|
|
7
|
+
* Or in bunfig.toml:
|
|
8
|
+
* preload = ["./src/devtools/preload.js"]
|
|
9
|
+
*/
|
|
10
|
+
import { installRDTHook } from "bippy";
|
|
11
|
+
installRDTHook();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../reconciler.js";
|
package/src/driver.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { WorkflowDriver, } from "@smithers-orchestrator/driver";
|
|
2
|
+
import { SmithersRenderer } from "./reconciler.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @template [Schema=unknown]
|
|
6
|
+
* @extends {WorkflowDriver<Schema>}
|
|
7
|
+
*/
|
|
8
|
+
export class ReactWorkflowDriver extends WorkflowDriver {
|
|
9
|
+
/**
|
|
10
|
+
* @param {import("@smithers-orchestrator/driver").WorkflowDriverOptions<Schema>} options
|
|
11
|
+
*/
|
|
12
|
+
constructor(options) {
|
|
13
|
+
const renderer = options.renderer ?? new SmithersRenderer();
|
|
14
|
+
super({
|
|
15
|
+
...options,
|
|
16
|
+
renderer,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as _smithers_orchestrator_graph_types from '@smithers-orchestrator/graph/types';
|
|
2
|
+
import { ExtractGraph as ExtractGraph$1, HostNode as HostNode$1 } from '@smithers-orchestrator/graph/types';
|
|
3
|
+
import * as React$1 from 'react';
|
|
4
|
+
import React__default from 'react';
|
|
5
|
+
import { WorkflowDriver } from '@smithers-orchestrator/driver';
|
|
6
|
+
import { SmithersCtx } from '@smithers-orchestrator/driver/SmithersCtx';
|
|
7
|
+
export { SmithersCtx } from '@smithers-orchestrator/driver/SmithersCtx';
|
|
8
|
+
|
|
9
|
+
type SmithersRendererOptions$1 = {
|
|
10
|
+
extractGraph?: ExtractGraph$1;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type HostContainer$1 = {
|
|
14
|
+
root: HostNode$1 | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
declare class SmithersRenderer {
|
|
18
|
+
/**
|
|
19
|
+
* @param {SmithersRendererOptions} [options]
|
|
20
|
+
*/
|
|
21
|
+
constructor(options?: SmithersRendererOptions);
|
|
22
|
+
/** @type {HostContainer} */
|
|
23
|
+
container: HostContainer;
|
|
24
|
+
/** @type {unknown} */
|
|
25
|
+
root: unknown;
|
|
26
|
+
/** @type {ExtractGraph | undefined} */
|
|
27
|
+
extractGraph: ExtractGraph | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* @param {React.ReactElement} element
|
|
30
|
+
* @param {ExtractOptions} [opts]
|
|
31
|
+
* @returns {Promise<WorkflowGraph>}
|
|
32
|
+
*/
|
|
33
|
+
render(element: React.ReactElement, opts?: ExtractOptions): Promise<WorkflowGraph>;
|
|
34
|
+
/**
|
|
35
|
+
* @returns {HostNode | null}
|
|
36
|
+
*/
|
|
37
|
+
getRoot(): HostNode | null;
|
|
38
|
+
}
|
|
39
|
+
type ExtractGraph = _smithers_orchestrator_graph_types.ExtractGraph;
|
|
40
|
+
type ExtractOptions = _smithers_orchestrator_graph_types.ExtractOptions;
|
|
41
|
+
type HostContainer = HostContainer$1;
|
|
42
|
+
type MutableHostElement = _smithers_orchestrator_graph_types.HostElement & {
|
|
43
|
+
props: Record<string, string>;
|
|
44
|
+
rawProps: Record<string, unknown>;
|
|
45
|
+
children: HostNode[];
|
|
46
|
+
};
|
|
47
|
+
type HostNode = _smithers_orchestrator_graph_types.HostNode;
|
|
48
|
+
type MutableHostText = _smithers_orchestrator_graph_types.HostText & {
|
|
49
|
+
text: string;
|
|
50
|
+
};
|
|
51
|
+
type React = React$1.default;
|
|
52
|
+
type SmithersRendererOptions = SmithersRendererOptions$1;
|
|
53
|
+
type WorkflowGraph = _smithers_orchestrator_graph_types.WorkflowGraph;
|
|
54
|
+
/**
|
|
55
|
+
* Minimal local shape for a react-reconciler instance. `@types/react-reconciler`
|
|
56
|
+
* is not installed here, so we describe only the methods we call.
|
|
57
|
+
*/
|
|
58
|
+
type ReconcilerInstance = {
|
|
59
|
+
createContainer: (rootContainer: unknown, tag: number, hydrationCallbacks: unknown, isStrictMode: boolean, concurrentUpdatesByDefaultOverride: unknown, identifierPrefix: string, onUncaughtError: unknown, onCaughtError: unknown, onRecoverableError: unknown, transitionCallbacks: unknown) => unknown;
|
|
60
|
+
updateContainerSync: (element: unknown, container: unknown, parentComponent: unknown, callback: () => void) => void;
|
|
61
|
+
flushSyncWork: () => void;
|
|
62
|
+
injectIntoDevTools: (devtools: unknown) => void;
|
|
63
|
+
defaultOnUncaughtError: unknown;
|
|
64
|
+
defaultOnCaughtError: unknown;
|
|
65
|
+
defaultOnRecoverableError: unknown;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @template [Schema=unknown]
|
|
70
|
+
* @extends {WorkflowDriver<Schema>}
|
|
71
|
+
*/
|
|
72
|
+
declare class ReactWorkflowDriver<Schema = unknown> extends WorkflowDriver<Schema> {
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @template Schema
|
|
77
|
+
* @returns {{ SmithersContext: React.Context<SmithersCtx<Schema> | null>, useCtx: () => SmithersCtx<Schema> }}
|
|
78
|
+
*/
|
|
79
|
+
declare function createSmithersContext<Schema>(): {
|
|
80
|
+
SmithersContext: React__default.Context<SmithersCtx<Schema> | null>;
|
|
81
|
+
useCtx: () => SmithersCtx<Schema>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** @type {React.Context<SmithersCtx<any> | null>} */
|
|
85
|
+
declare const SmithersContext: React__default.Context<SmithersCtx<any> | null>;
|
|
86
|
+
|
|
87
|
+
export { type ExtractGraph, type ExtractOptions, type HostContainer, type HostNode, type MutableHostElement, type MutableHostText, type React, ReactWorkflowDriver, type ReconcilerInstance, SmithersContext, SmithersRenderer, type SmithersRendererOptions, type WorkflowGraph, createSmithersContext };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import Reconciler from "react-reconciler";
|
|
2
|
+
import { installRDTHook } from "bippy";
|
|
3
|
+
import { resolveExtractGraph } from "./core-peer.js";
|
|
4
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").ExtractGraph} ExtractGraph */
|
|
5
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").ExtractOptions} ExtractOptions */
|
|
6
|
+
/** @typedef {import("./HostContainer.ts").HostContainer} HostContainer */
|
|
7
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").HostElement & { props: Record<string, string>; rawProps: Record<string, unknown>; children: HostNode[] }} MutableHostElement */
|
|
8
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").HostNode} HostNode */
|
|
9
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").HostText & { text: string }} MutableHostText */
|
|
10
|
+
/** @typedef {import("react").default} React */
|
|
11
|
+
/** @typedef {import("./SmithersRendererOptions.ts").SmithersRendererOptions} SmithersRendererOptions */
|
|
12
|
+
/** @typedef {import("@smithers-orchestrator/graph/types").WorkflowGraph} WorkflowGraph */
|
|
13
|
+
/**
|
|
14
|
+
* Minimal local shape for a react-reconciler instance. `@types/react-reconciler`
|
|
15
|
+
* is not installed here, so we describe only the methods we call.
|
|
16
|
+
* @typedef {{
|
|
17
|
+
* createContainer: (
|
|
18
|
+
* rootContainer: unknown,
|
|
19
|
+
* tag: number,
|
|
20
|
+
* hydrationCallbacks: unknown,
|
|
21
|
+
* isStrictMode: boolean,
|
|
22
|
+
* concurrentUpdatesByDefaultOverride: unknown,
|
|
23
|
+
* identifierPrefix: string,
|
|
24
|
+
* onUncaughtError: unknown,
|
|
25
|
+
* onCaughtError: unknown,
|
|
26
|
+
* onRecoverableError: unknown,
|
|
27
|
+
* transitionCallbacks: unknown,
|
|
28
|
+
* ) => unknown;
|
|
29
|
+
* updateContainerSync: (element: unknown, container: unknown, parentComponent: unknown, callback: () => void) => void;
|
|
30
|
+
* flushSyncWork: () => void;
|
|
31
|
+
* injectIntoDevTools: (devtools: unknown) => void;
|
|
32
|
+
* defaultOnUncaughtError: unknown;
|
|
33
|
+
* defaultOnCaughtError: unknown;
|
|
34
|
+
* defaultOnRecoverableError: unknown;
|
|
35
|
+
* }} ReconcilerInstance */
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} type
|
|
39
|
+
* @param {Record<string, unknown>} props
|
|
40
|
+
* @returns {MutableHostElement}
|
|
41
|
+
*/
|
|
42
|
+
function createElement(type, props) {
|
|
43
|
+
const { children: _children, ...rest } = props || {};
|
|
44
|
+
const stringProps = {};
|
|
45
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
46
|
+
if (value === undefined || typeof value === "function")
|
|
47
|
+
continue;
|
|
48
|
+
if (key.startsWith("__"))
|
|
49
|
+
continue;
|
|
50
|
+
if (typeof value === "string" ||
|
|
51
|
+
typeof value === "number" ||
|
|
52
|
+
typeof value === "boolean") {
|
|
53
|
+
stringProps[key] = String(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
kind: "element",
|
|
58
|
+
tag: type,
|
|
59
|
+
props: stringProps,
|
|
60
|
+
rawProps: props ?? {},
|
|
61
|
+
children: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
let currentUpdatePriority = 1;
|
|
65
|
+
const hostConfig = {
|
|
66
|
+
supportsMutation: true,
|
|
67
|
+
supportsPersistence: false,
|
|
68
|
+
supportsHydration: false,
|
|
69
|
+
isPrimaryRenderer: true,
|
|
70
|
+
supportsMicrotasks: true,
|
|
71
|
+
/**
|
|
72
|
+
* @returns {Record<string, unknown>}
|
|
73
|
+
*/
|
|
74
|
+
getRootHostContext() {
|
|
75
|
+
return {};
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* @returns {Record<string, unknown>}
|
|
79
|
+
*/
|
|
80
|
+
getChildHostContext() {
|
|
81
|
+
return {};
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* @param {unknown} instance
|
|
85
|
+
* @returns {unknown}
|
|
86
|
+
*/
|
|
87
|
+
getPublicInstance(instance) {
|
|
88
|
+
return instance;
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} type
|
|
92
|
+
* @param {Record<string, unknown>} props
|
|
93
|
+
* @returns {MutableHostElement}
|
|
94
|
+
*/
|
|
95
|
+
createInstance(type, props) {
|
|
96
|
+
return createElement(type, props);
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* @param {string} text
|
|
100
|
+
* @returns {MutableHostText}
|
|
101
|
+
*/
|
|
102
|
+
createTextInstance(text) {
|
|
103
|
+
return { kind: "text", text };
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* @param {MutableHostElement} parent
|
|
107
|
+
* @param {HostNode} child
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
appendInitialChild(parent, child) {
|
|
111
|
+
parent.children.push(child);
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* @param {MutableHostElement} parent
|
|
115
|
+
* @param {HostNode} child
|
|
116
|
+
* @returns {void}
|
|
117
|
+
*/
|
|
118
|
+
appendChild(parent, child) {
|
|
119
|
+
parent.children.push(child);
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* @param {HostContainer} container
|
|
123
|
+
* @param {HostNode} child
|
|
124
|
+
* @returns {void}
|
|
125
|
+
*/
|
|
126
|
+
appendChildToContainer(container, child) {
|
|
127
|
+
container.root = child;
|
|
128
|
+
},
|
|
129
|
+
/**
|
|
130
|
+
* @param {MutableHostElement} parent
|
|
131
|
+
* @param {HostNode} child
|
|
132
|
+
* @returns {void}
|
|
133
|
+
*/
|
|
134
|
+
removeChild(parent, child) {
|
|
135
|
+
const idx = parent.children.indexOf(child);
|
|
136
|
+
if (idx >= 0)
|
|
137
|
+
parent.children.splice(idx, 1);
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* @param {HostContainer} container
|
|
141
|
+
* @returns {void}
|
|
142
|
+
*/
|
|
143
|
+
removeChildFromContainer(container) {
|
|
144
|
+
container.root = null;
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* @param {MutableHostElement} parent
|
|
148
|
+
* @param {HostNode} child
|
|
149
|
+
* @param {HostNode} beforeChild
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
insertBefore(parent, child, beforeChild) {
|
|
153
|
+
const idx = parent.children.indexOf(beforeChild);
|
|
154
|
+
if (idx >= 0)
|
|
155
|
+
parent.children.splice(idx, 0, child);
|
|
156
|
+
else
|
|
157
|
+
parent.children.push(child);
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* @param {HostContainer} container
|
|
161
|
+
* @param {HostNode} child
|
|
162
|
+
* @param {HostNode} _beforeChild
|
|
163
|
+
* @returns {void}
|
|
164
|
+
*/
|
|
165
|
+
insertInContainerBefore(container, child, _beforeChild) {
|
|
166
|
+
container.root = child;
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* @param {MutableHostElement} _instance
|
|
170
|
+
* @param {string} _type
|
|
171
|
+
* @param {unknown} oldProps
|
|
172
|
+
* @param {unknown} newProps
|
|
173
|
+
* @returns {unknown}
|
|
174
|
+
*/
|
|
175
|
+
prepareUpdate(_instance, _type, oldProps, newProps) {
|
|
176
|
+
if (oldProps === newProps)
|
|
177
|
+
return null;
|
|
178
|
+
return newProps;
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* @param {MutableHostElement} instance
|
|
182
|
+
* @param {unknown[]} args
|
|
183
|
+
* @returns {void}
|
|
184
|
+
*/
|
|
185
|
+
commitUpdate(instance, ...args) {
|
|
186
|
+
let nextProps = null;
|
|
187
|
+
const first = args[0];
|
|
188
|
+
const second = args[1];
|
|
189
|
+
if (typeof second === "string" && first && typeof first === "object") {
|
|
190
|
+
nextProps = first;
|
|
191
|
+
}
|
|
192
|
+
else if (typeof first === "string") {
|
|
193
|
+
const maybeNewProps = args[2];
|
|
194
|
+
if (maybeNewProps && typeof maybeNewProps === "object") {
|
|
195
|
+
nextProps = maybeNewProps;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (first && typeof first === "object") {
|
|
199
|
+
nextProps = first;
|
|
200
|
+
}
|
|
201
|
+
if (!nextProps)
|
|
202
|
+
return;
|
|
203
|
+
const next = createElement(instance.tag, nextProps);
|
|
204
|
+
instance.props = next.props;
|
|
205
|
+
instance.rawProps = next.rawProps;
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* @param {MutableHostText} textInstance
|
|
209
|
+
* @param {string} _oldText
|
|
210
|
+
* @param {string} newText
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
commitTextUpdate(textInstance, _oldText, newText) {
|
|
214
|
+
textInstance.text = newText;
|
|
215
|
+
},
|
|
216
|
+
/**
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
*/
|
|
219
|
+
finalizeInitialChildren() {
|
|
220
|
+
return false;
|
|
221
|
+
},
|
|
222
|
+
/**
|
|
223
|
+
* @returns {null}
|
|
224
|
+
*/
|
|
225
|
+
prepareForCommit() {
|
|
226
|
+
return null;
|
|
227
|
+
},
|
|
228
|
+
/**
|
|
229
|
+
* @returns {void}
|
|
230
|
+
*/
|
|
231
|
+
resetAfterCommit() { },
|
|
232
|
+
/**
|
|
233
|
+
* @returns {boolean}
|
|
234
|
+
*/
|
|
235
|
+
shouldSetTextContent() {
|
|
236
|
+
return false;
|
|
237
|
+
},
|
|
238
|
+
/**
|
|
239
|
+
* @param {HostContainer} container
|
|
240
|
+
* @returns {void}
|
|
241
|
+
*/
|
|
242
|
+
clearContainer(container) {
|
|
243
|
+
container.root = null;
|
|
244
|
+
},
|
|
245
|
+
/**
|
|
246
|
+
* @returns {number}
|
|
247
|
+
*/
|
|
248
|
+
getCurrentEventPriority() {
|
|
249
|
+
return 1;
|
|
250
|
+
},
|
|
251
|
+
/**
|
|
252
|
+
* @returns {boolean}
|
|
253
|
+
*/
|
|
254
|
+
shouldAttemptEagerTransition() {
|
|
255
|
+
return false;
|
|
256
|
+
},
|
|
257
|
+
/**
|
|
258
|
+
* @returns {boolean}
|
|
259
|
+
*/
|
|
260
|
+
maySuspendCommit() {
|
|
261
|
+
return false;
|
|
262
|
+
},
|
|
263
|
+
/**
|
|
264
|
+
* @returns {void}
|
|
265
|
+
*/
|
|
266
|
+
preloadInstance() { },
|
|
267
|
+
/**
|
|
268
|
+
* @returns {void}
|
|
269
|
+
*/
|
|
270
|
+
startSuspendingCommit() { },
|
|
271
|
+
/**
|
|
272
|
+
* @returns {void}
|
|
273
|
+
*/
|
|
274
|
+
suspendInstance() { },
|
|
275
|
+
/**
|
|
276
|
+
* @returns {null}
|
|
277
|
+
*/
|
|
278
|
+
waitForCommitToBeReady() {
|
|
279
|
+
return null;
|
|
280
|
+
},
|
|
281
|
+
/**
|
|
282
|
+
* @returns {void}
|
|
283
|
+
*/
|
|
284
|
+
resetFormInstance() { },
|
|
285
|
+
/**
|
|
286
|
+
* @returns {void}
|
|
287
|
+
*/
|
|
288
|
+
detachDeletedInstance() { },
|
|
289
|
+
/**
|
|
290
|
+
* @param {"error" | "warn" | "info" | "log"} type
|
|
291
|
+
* @param {unknown[]} args
|
|
292
|
+
* @returns {() => void}
|
|
293
|
+
*/
|
|
294
|
+
bindToConsole(type, args) {
|
|
295
|
+
return () => {
|
|
296
|
+
const fn = console[type] ?? console.log;
|
|
297
|
+
fn(...args);
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
/**
|
|
301
|
+
* @returns {number}
|
|
302
|
+
*/
|
|
303
|
+
getCurrentUpdatePriority() {
|
|
304
|
+
return currentUpdatePriority;
|
|
305
|
+
},
|
|
306
|
+
/**
|
|
307
|
+
* @param {number} priority
|
|
308
|
+
* @returns {void}
|
|
309
|
+
*/
|
|
310
|
+
setCurrentUpdatePriority(priority) {
|
|
311
|
+
currentUpdatePriority = priority;
|
|
312
|
+
},
|
|
313
|
+
/**
|
|
314
|
+
* @returns {number}
|
|
315
|
+
*/
|
|
316
|
+
resolveUpdatePriority() {
|
|
317
|
+
return currentUpdatePriority;
|
|
318
|
+
},
|
|
319
|
+
/**
|
|
320
|
+
* @param {(...args: unknown[]) => void} fn
|
|
321
|
+
* @param {number} [delay]
|
|
322
|
+
* @returns {ReturnType<typeof setTimeout>}
|
|
323
|
+
*/
|
|
324
|
+
scheduleTimeout(fn, delay) {
|
|
325
|
+
return setTimeout(fn, delay ?? 0);
|
|
326
|
+
},
|
|
327
|
+
/**
|
|
328
|
+
* @param {() => void} fn
|
|
329
|
+
* @returns {void}
|
|
330
|
+
*/
|
|
331
|
+
scheduleMicrotask(fn) {
|
|
332
|
+
queueMicrotask(fn);
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* @param {ReturnType<typeof setTimeout>} id
|
|
336
|
+
* @returns {void}
|
|
337
|
+
*/
|
|
338
|
+
cancelTimeout(id) {
|
|
339
|
+
clearTimeout(id);
|
|
340
|
+
},
|
|
341
|
+
noTimeout: -1,
|
|
342
|
+
};
|
|
343
|
+
/** @type {ReconcilerInstance} */
|
|
344
|
+
const reconciler = /** @type {ReconcilerInstance} */ (Reconciler(hostConfig));
|
|
345
|
+
const hookHost = globalThis;
|
|
346
|
+
if (!hookHost.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
|
|
347
|
+
installRDTHook();
|
|
348
|
+
}
|
|
349
|
+
reconciler.injectIntoDevTools({
|
|
350
|
+
bundleType: typeof process !== "undefined" && process.env.NODE_ENV === "production" ? 0 : 1,
|
|
351
|
+
version: "0.0.0",
|
|
352
|
+
rendererPackageName: "@smithers-orchestrator/core-react",
|
|
353
|
+
findFiberByHostInstance: () => null,
|
|
354
|
+
});
|
|
355
|
+
export class SmithersRenderer {
|
|
356
|
+
/** @type {HostContainer} */
|
|
357
|
+
container;
|
|
358
|
+
/** @type {unknown} */
|
|
359
|
+
root;
|
|
360
|
+
/** @type {ExtractGraph | undefined} */
|
|
361
|
+
extractGraph;
|
|
362
|
+
/**
|
|
363
|
+
* @param {SmithersRendererOptions} [options]
|
|
364
|
+
*/
|
|
365
|
+
constructor(options = {}) {
|
|
366
|
+
this.extractGraph = options.extractGraph;
|
|
367
|
+
this.container = { root: null };
|
|
368
|
+
this.root = reconciler.createContainer(this.container, 0, null, false, null, "", reconciler.defaultOnUncaughtError, reconciler.defaultOnCaughtError, reconciler.defaultOnRecoverableError, null);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* @param {React.ReactElement} element
|
|
372
|
+
* @param {ExtractOptions} [opts]
|
|
373
|
+
* @returns {Promise<WorkflowGraph>}
|
|
374
|
+
*/
|
|
375
|
+
async render(element, opts) {
|
|
376
|
+
reconciler.updateContainerSync(element, this.root, null, () => { });
|
|
377
|
+
reconciler.flushSyncWork();
|
|
378
|
+
const extractGraph = this.extractGraph ?? (await resolveExtractGraph());
|
|
379
|
+
return extractGraph(this.container.root, opts);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* @returns {HostNode | null}
|
|
383
|
+
*/
|
|
384
|
+
getRoot() {
|
|
385
|
+
return this.container.root;
|
|
386
|
+
}
|
|
387
|
+
}
|