@creact-labs/creact 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -22
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +88 -0
- package/dist/index.d.ts +19 -44
- package/dist/index.js +20 -68
- package/dist/jsx/index.d.ts +2 -0
- package/dist/jsx/index.js +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +4 -0
- package/dist/jsx/jsx-dev-runtime.js +4 -0
- package/dist/jsx/jsx-runtime.d.ts +38 -0
- package/dist/jsx/jsx-runtime.js +38 -0
- package/dist/jsx/types.d.ts +12 -0
- package/dist/jsx/types.js +4 -0
- package/dist/primitives/context.d.ts +34 -0
- package/dist/primitives/context.js +63 -0
- package/dist/primitives/index.d.ts +3 -0
- package/dist/primitives/index.js +3 -0
- package/dist/primitives/instance.d.ts +72 -0
- package/dist/primitives/instance.js +235 -0
- package/dist/primitives/store.d.ts +22 -0
- package/dist/primitives/store.js +97 -0
- package/dist/provider/backend.d.ts +110 -0
- package/dist/provider/backend.js +37 -0
- package/dist/provider/interface.d.ts +48 -0
- package/dist/provider/interface.js +39 -0
- package/dist/reactive/effect.d.ts +11 -0
- package/dist/reactive/effect.js +42 -0
- package/dist/reactive/index.d.ts +3 -0
- package/dist/reactive/index.js +3 -0
- package/dist/reactive/signal.d.ts +32 -0
- package/dist/reactive/signal.js +60 -0
- package/dist/reactive/tracking.d.ts +41 -0
- package/dist/reactive/tracking.js +161 -0
- package/dist/runtime/fiber.d.ts +21 -0
- package/dist/runtime/fiber.js +16 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/reconcile.d.ts +66 -0
- package/dist/runtime/reconcile.js +210 -0
- package/dist/runtime/render.d.ts +42 -0
- package/dist/runtime/render.js +231 -0
- package/dist/runtime/run.d.ts +119 -0
- package/dist/runtime/run.js +334 -0
- package/dist/runtime/state-machine.d.ts +95 -0
- package/dist/runtime/state-machine.js +209 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +4 -0
- package/package.json +29 -24
- package/dist/cli/commands/BuildCommand.d.ts +0 -40
- package/dist/cli/commands/BuildCommand.js +0 -151
- package/dist/cli/commands/DeployCommand.d.ts +0 -38
- package/dist/cli/commands/DeployCommand.js +0 -194
- package/dist/cli/commands/DevCommand.d.ts +0 -52
- package/dist/cli/commands/DevCommand.js +0 -394
- package/dist/cli/commands/PlanCommand.d.ts +0 -39
- package/dist/cli/commands/PlanCommand.js +0 -164
- package/dist/cli/commands/index.d.ts +0 -36
- package/dist/cli/commands/index.js +0 -43
- package/dist/cli/core/ArgumentParser.d.ts +0 -46
- package/dist/cli/core/ArgumentParser.js +0 -127
- package/dist/cli/core/BaseCommand.d.ts +0 -75
- package/dist/cli/core/BaseCommand.js +0 -95
- package/dist/cli/core/CLIContext.d.ts +0 -68
- package/dist/cli/core/CLIContext.js +0 -183
- package/dist/cli/core/CommandRegistry.d.ts +0 -64
- package/dist/cli/core/CommandRegistry.js +0 -89
- package/dist/cli/core/index.d.ts +0 -36
- package/dist/cli/core/index.js +0 -43
- package/dist/cli/index.d.ts +0 -35
- package/dist/cli/index.js +0 -100
- package/dist/cli/output.d.ts +0 -204
- package/dist/cli/output.js +0 -437
- package/dist/cli/utils.d.ts +0 -59
- package/dist/cli/utils.js +0 -76
- package/dist/context/createContext.d.ts +0 -90
- package/dist/context/createContext.js +0 -113
- package/dist/context/index.d.ts +0 -30
- package/dist/context/index.js +0 -35
- package/dist/core/CReact.d.ts +0 -409
- package/dist/core/CReact.js +0 -1151
- package/dist/core/CloudDOMBuilder.d.ts +0 -447
- package/dist/core/CloudDOMBuilder.js +0 -1234
- package/dist/core/ContextDependencyTracker.d.ts +0 -165
- package/dist/core/ContextDependencyTracker.js +0 -448
- package/dist/core/ErrorRecoveryManager.d.ts +0 -145
- package/dist/core/ErrorRecoveryManager.js +0 -443
- package/dist/core/EventBus.d.ts +0 -91
- package/dist/core/EventBus.js +0 -185
- package/dist/core/ProviderOutputTracker.d.ts +0 -211
- package/dist/core/ProviderOutputTracker.js +0 -476
- package/dist/core/ReactiveUpdateQueue.d.ts +0 -76
- package/dist/core/ReactiveUpdateQueue.js +0 -121
- package/dist/core/Reconciler.d.ts +0 -415
- package/dist/core/Reconciler.js +0 -1044
- package/dist/core/RenderScheduler.d.ts +0 -153
- package/dist/core/RenderScheduler.js +0 -519
- package/dist/core/Renderer.d.ts +0 -336
- package/dist/core/Renderer.js +0 -944
- package/dist/core/Runtime.d.ts +0 -246
- package/dist/core/Runtime.js +0 -640
- package/dist/core/StateBindingManager.d.ts +0 -121
- package/dist/core/StateBindingManager.js +0 -309
- package/dist/core/StateMachine.d.ts +0 -441
- package/dist/core/StateMachine.js +0 -883
- package/dist/core/StructuralChangeDetector.d.ts +0 -140
- package/dist/core/StructuralChangeDetector.js +0 -363
- package/dist/core/Validator.d.ts +0 -127
- package/dist/core/Validator.js +0 -279
- package/dist/core/errors.d.ts +0 -153
- package/dist/core/errors.js +0 -202
- package/dist/core/index.d.ts +0 -38
- package/dist/core/index.js +0 -64
- package/dist/core/types.d.ts +0 -265
- package/dist/core/types.js +0 -48
- package/dist/hooks/context.d.ts +0 -147
- package/dist/hooks/context.js +0 -334
- package/dist/hooks/useContext.d.ts +0 -113
- package/dist/hooks/useContext.js +0 -169
- package/dist/hooks/useEffect.d.ts +0 -105
- package/dist/hooks/useEffect.js +0 -540
- package/dist/hooks/useInstance.d.ts +0 -139
- package/dist/hooks/useInstance.js +0 -455
- package/dist/hooks/useState.d.ts +0 -120
- package/dist/hooks/useState.js +0 -298
- package/dist/jsx.d.ts +0 -143
- package/dist/jsx.js +0 -76
- package/dist/providers/DummyBackendProvider.d.ts +0 -193
- package/dist/providers/DummyBackendProvider.js +0 -189
- package/dist/providers/DummyCloudProvider.d.ts +0 -128
- package/dist/providers/DummyCloudProvider.js +0 -157
- package/dist/providers/IBackendProvider.d.ts +0 -177
- package/dist/providers/IBackendProvider.js +0 -31
- package/dist/providers/ICloudProvider.d.ts +0 -230
- package/dist/providers/ICloudProvider.js +0 -31
- package/dist/providers/index.d.ts +0 -31
- package/dist/providers/index.js +0 -31
- package/dist/test-event-callbacks.d.ts +0 -0
- package/dist/test-event-callbacks.js +0 -1
- package/dist/utils/Logger.d.ts +0 -144
- package/dist/utils/Logger.js +0 -220
- package/dist/utils/Output.d.ts +0 -161
- package/dist/utils/Output.js +0 -401
- package/dist/utils/deepEqual.d.ts +0 -71
- package/dist/utils/deepEqual.js +0 -276
- package/dist/utils/naming.d.ts +0 -241
- package/dist/utils/naming.js +0 -376
|
@@ -1,1234 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
|
|
8
|
-
* You may obtain a copy of the License at
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
-
|
|
18
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
-
|
|
20
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
-
|
|
22
|
-
* See the License for the specific language governing permissions and
|
|
23
|
-
|
|
24
|
-
* limitations under the License.
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
* Copyright 2025 Daniel Coutinho Ribeiro
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
32
|
-
if (k2 === undefined) k2 = k;
|
|
33
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
34
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
35
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
36
|
-
}
|
|
37
|
-
Object.defineProperty(o, k2, desc);
|
|
38
|
-
}) : (function(o, m, k, k2) {
|
|
39
|
-
if (k2 === undefined) k2 = k;
|
|
40
|
-
o[k2] = m[k];
|
|
41
|
-
}));
|
|
42
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
43
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
44
|
-
}) : function(o, v) {
|
|
45
|
-
o["default"] = v;
|
|
46
|
-
});
|
|
47
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
48
|
-
var ownKeys = function(o) {
|
|
49
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
50
|
-
var ar = [];
|
|
51
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
52
|
-
return ar;
|
|
53
|
-
};
|
|
54
|
-
return ownKeys(o);
|
|
55
|
-
};
|
|
56
|
-
return function (mod) {
|
|
57
|
-
if (mod && mod.__esModule) return mod;
|
|
58
|
-
var result = {};
|
|
59
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
60
|
-
__setModuleDefault(result, mod);
|
|
61
|
-
return result;
|
|
62
|
-
};
|
|
63
|
-
})();
|
|
64
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
-
exports.CloudDOMBuilder = void 0;
|
|
66
|
-
const naming_1 = require("../utils/naming");
|
|
67
|
-
const ReactiveUpdateQueue_1 = require("./ReactiveUpdateQueue");
|
|
68
|
-
const Logger_1 = require("../utils/Logger");
|
|
69
|
-
const logger = Logger_1.LoggerFactory.getLogger('clouddom');
|
|
70
|
-
/**
|
|
71
|
-
* CloudDOMBuilder transforms a Fiber tree into a CloudDOM tree
|
|
72
|
-
*
|
|
73
|
-
* The CloudDOM tree represents actual cloud resources to be deployed:
|
|
74
|
-
* - Only includes components that called useInstance (have cloudDOMNode attached)
|
|
75
|
-
* - Filters out container components (no useInstance)
|
|
76
|
-
* - Builds parent-child relationships for deployment order
|
|
77
|
-
* - Generates resource IDs from hierarchical paths
|
|
78
|
-
*
|
|
79
|
-
* REQ-01: JSX → CloudDOM rendering
|
|
80
|
-
* REQ-04: Receives ICloudProvider via dependency injection (not used in build, but available for future extensions)
|
|
81
|
-
*/
|
|
82
|
-
class CloudDOMBuilder {
|
|
83
|
-
/**
|
|
84
|
-
* Constructor receives ICloudProvider via dependency injection
|
|
85
|
-
*
|
|
86
|
-
* REQ-04: Dependency injection pattern - provider is injected, not inherited
|
|
87
|
-
*
|
|
88
|
-
* @param cloudProvider - Cloud provider implementation (injected)
|
|
89
|
-
*/
|
|
90
|
-
constructor(cloudProvider) {
|
|
91
|
-
this.cloudProvider = cloudProvider;
|
|
92
|
-
/**
|
|
93
|
-
* Track existing nodes to handle re-render scenarios
|
|
94
|
-
* Maps node ID to fiber path for duplicate detection during re-renders
|
|
95
|
-
*/
|
|
96
|
-
this.existingNodeMap = new Map();
|
|
97
|
-
// Provider is stored for potential future use (e.g., provider-specific CloudDOM transformations)
|
|
98
|
-
// For POC, we don't use it during build, but it's available for extensions
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Set lifecycle hooks for integration with other components
|
|
102
|
-
*
|
|
103
|
-
* Supports async hooks for:
|
|
104
|
-
* - beforeBuild: Validation, pre-processing, telemetry
|
|
105
|
-
* - afterBuild: Provider preparation, post-processing, logging
|
|
106
|
-
*
|
|
107
|
-
* Example:
|
|
108
|
-
* ```typescript
|
|
109
|
-
* builder.setHooks({
|
|
110
|
-
* beforeBuild: async fiber => validator.validate(fiber),
|
|
111
|
-
* afterBuild: async tree => cloudProvider.prepare(tree),
|
|
112
|
-
* });
|
|
113
|
-
* ```
|
|
114
|
-
*
|
|
115
|
-
* @param hooks - Optional lifecycle hooks (sync or async)
|
|
116
|
-
*/
|
|
117
|
-
setHooks(hooks) {
|
|
118
|
-
this.beforeBuildHook = hooks.beforeBuild;
|
|
119
|
-
this.afterBuildHook = hooks.afterBuild;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Set reactive components for output synchronization
|
|
123
|
-
*
|
|
124
|
-
* @param stateBindingManager - State binding manager instance
|
|
125
|
-
* @param providerOutputTracker - Provider output tracker instance
|
|
126
|
-
* @param contextDependencyTracker - Context dependency tracker instance (optional)
|
|
127
|
-
*/
|
|
128
|
-
setReactiveComponents(stateBindingManager, providerOutputTracker, contextDependencyTracker) {
|
|
129
|
-
this.stateBindingManager = stateBindingManager;
|
|
130
|
-
this.providerOutputTracker = providerOutputTracker;
|
|
131
|
-
this.contextDependencyTracker = contextDependencyTracker;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Build CloudDOM tree from Fiber tree
|
|
135
|
-
*
|
|
136
|
-
* Traverses the Fiber tree and collects nodes that have cloudDOMNode attached
|
|
137
|
-
* (i.e., components that called useInstance). Container components without
|
|
138
|
-
* useInstance are filtered out, but their children are preserved.
|
|
139
|
-
*
|
|
140
|
-
* Supports async lifecycle hooks for validation and provider preparation.
|
|
141
|
-
*
|
|
142
|
-
* CRITICAL FIX: Preserves nodes from previousCloudDOM that weren't re-rendered.
|
|
143
|
-
* This prevents node loss during selective re-renders (e.g., multi-environment apps).
|
|
144
|
-
*
|
|
145
|
-
* REQ-01: Transform Fiber → CloudDOM
|
|
146
|
-
*
|
|
147
|
-
* @param fiber - Root Fiber node
|
|
148
|
-
* @param previousCloudDOM - Optional previous CloudDOM for incremental builds
|
|
149
|
-
* @returns Promise resolving to array of CloudDOM nodes (top-level resources)
|
|
150
|
-
*/
|
|
151
|
-
async build(fiber, previousCloudDOM) {
|
|
152
|
-
if (!fiber) {
|
|
153
|
-
throw new Error('[CloudDOMBuilder] Cannot build CloudDOM from null Fiber tree');
|
|
154
|
-
}
|
|
155
|
-
// Lifecycle hook: beforeBuild (supports async, isolated errors)
|
|
156
|
-
if (this.beforeBuildHook) {
|
|
157
|
-
try {
|
|
158
|
-
await this.beforeBuildHook(fiber);
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
// Re-throw validation errors (expected to halt build)
|
|
162
|
-
// But log and continue for non-critical hooks (telemetry, logging)
|
|
163
|
-
if (err instanceof Error && err.message.includes('Validation')) {
|
|
164
|
-
throw err;
|
|
165
|
-
}
|
|
166
|
-
logger.error('beforeBuild hook failed (non-critical):', err);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// Collect all CloudDOM nodes from the Fiber tree
|
|
170
|
-
const cloudDOMNodes = [];
|
|
171
|
-
this.collectCloudDOMNodes(fiber, cloudDOMNodes);
|
|
172
|
-
// CRITICAL FIX: If we have previous CloudDOM and this is an incremental build,
|
|
173
|
-
// preserve nodes that aren't in the current fiber tree
|
|
174
|
-
if (previousCloudDOM && previousCloudDOM.length > 0) {
|
|
175
|
-
const currentNodeIds = new Set(cloudDOMNodes.map(n => n.id));
|
|
176
|
-
// Find nodes from previous CloudDOM that aren't in current build
|
|
177
|
-
const preservedNodes = this.flattenCloudDOM(previousCloudDOM).filter(node => !currentNodeIds.has(node.id));
|
|
178
|
-
if (preservedNodes.length > 0) {
|
|
179
|
-
logger.info(`[CloudDOMBuilder] Preserving ${preservedNodes.length} nodes from previous CloudDOM that weren't re-rendered`);
|
|
180
|
-
cloudDOMNodes.push(...preservedNodes);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Validate collected nodes
|
|
184
|
-
this.validateCloudDOMNodes(cloudDOMNodes);
|
|
185
|
-
// Build parent-child relationships
|
|
186
|
-
// CRITICAL: Work with original nodes, not copies, so enhanced proxies see updates
|
|
187
|
-
const rootNodes = this.buildHierarchy(cloudDOMNodes);
|
|
188
|
-
// Detect circular references in hierarchy
|
|
189
|
-
this.detectCircularRefs(rootNodes);
|
|
190
|
-
// Sort root nodes for deterministic order
|
|
191
|
-
rootNodes.sort((a, b) => a.id.localeCompare(b.id));
|
|
192
|
-
// Debug trace in development mode
|
|
193
|
-
if (process.env.NODE_ENV === 'development') {
|
|
194
|
-
logger.debug(`Built ${rootNodes.length} root CloudDOM nodes from ${cloudDOMNodes.length} total nodes`);
|
|
195
|
-
logger.debug('Tree:', JSON.stringify(rootNodes, null, 2));
|
|
196
|
-
}
|
|
197
|
-
// Lifecycle hook: afterBuild (supports async, isolated errors)
|
|
198
|
-
if (this.afterBuildHook) {
|
|
199
|
-
try {
|
|
200
|
-
await this.afterBuildHook(rootNodes);
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
logger.error('afterBuild hook failed (non-critical):', err);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return rootNodes;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Flatten CloudDOM tree into a flat array of all nodes
|
|
210
|
-
*
|
|
211
|
-
* @param nodes - Root CloudDOM nodes
|
|
212
|
-
* @returns Flattened array of all nodes
|
|
213
|
-
*/
|
|
214
|
-
flattenCloudDOM(nodes) {
|
|
215
|
-
const flattened = [];
|
|
216
|
-
const walk = (nodeList) => {
|
|
217
|
-
for (const node of nodeList) {
|
|
218
|
-
flattened.push(node);
|
|
219
|
-
if (node.children && node.children.length > 0) {
|
|
220
|
-
walk(node.children);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
walk(nodes);
|
|
225
|
-
return flattened;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Build CloudDOM tree with error handling for CLI/CI environments
|
|
229
|
-
*
|
|
230
|
-
* Provides a safer entrypoint that handles errors gracefully without
|
|
231
|
-
* crashing the entire process. Useful for CI/CD pipelines.
|
|
232
|
-
*
|
|
233
|
-
* @param fiber - Root Fiber node
|
|
234
|
-
* @param previousCloudDOM - Optional previous CloudDOM for incremental builds
|
|
235
|
-
* @returns Promise resolving to array of CloudDOM nodes, or empty array on error
|
|
236
|
-
*/
|
|
237
|
-
async buildSafe(fiber, previousCloudDOM) {
|
|
238
|
-
try {
|
|
239
|
-
return await this.build(fiber, previousCloudDOM);
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
logger.error('Build failed:', error);
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Recursively collect CloudDOM nodes from Fiber tree
|
|
248
|
-
*
|
|
249
|
-
* Traverses the Fiber tree depth-first and collects all nodes that have
|
|
250
|
-
* cloudDOMNode or cloudDOMNodes attached (i.e., components that called useInstance).
|
|
251
|
-
*
|
|
252
|
-
* Normalizes paths and IDs during collection for consistency.
|
|
253
|
-
*
|
|
254
|
-
* @param fiber - Current Fiber node
|
|
255
|
-
* @param collected - Array to collect CloudDOM nodes into
|
|
256
|
-
*/
|
|
257
|
-
collectCloudDOMNodes(fiber, collected) {
|
|
258
|
-
// Extract state from useState hooks in this Fiber node
|
|
259
|
-
const stateValues = this.extractOutputsFromFiber(fiber);
|
|
260
|
-
// Check for cloudDOMNodes array (multiple nodes from useInstance calls)
|
|
261
|
-
if (fiber.cloudDOMNodes && Array.isArray(fiber.cloudDOMNodes)) {
|
|
262
|
-
for (const cloudNode of fiber.cloudDOMNodes) {
|
|
263
|
-
if (this.isValidCloudNode(cloudNode)) {
|
|
264
|
-
// Normalize path and regenerate ID for consistency
|
|
265
|
-
cloudNode.path = (0, naming_1.normalizePath)(cloudNode.path);
|
|
266
|
-
cloudNode.id = (0, naming_1.generateResourceId)(cloudNode.path);
|
|
267
|
-
// Attach state from useState hooks to CloudDOM node's state field
|
|
268
|
-
if (Object.keys(stateValues).length > 0) {
|
|
269
|
-
cloudNode.state = { ...stateValues };
|
|
270
|
-
}
|
|
271
|
-
collected.push(cloudNode);
|
|
272
|
-
logger.debug(`Collected CloudDOM node: ${cloudNode.id} from fiber: ${fiber.path?.join('.')}`);
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
const nodeId = cloudNode?.id ?? 'unknown';
|
|
276
|
-
logger.warn(`Skipping invalid CloudDOM node: ${nodeId}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
// CRITICAL FIX: If this fiber has no cloudDOMNodes but has children,
|
|
282
|
-
// it might be a component that wasn't re-executed during selective re-rendering.
|
|
283
|
-
// Log this for debugging purposes.
|
|
284
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
285
|
-
logger.debug(`Fiber ${fiber.path?.join('.')} has no cloudDOMNodes but has ${fiber.children.length} children`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// Legacy cloudDOMNode removed - only cloudDOMNodes array is supported
|
|
289
|
-
// Recursively collect from children
|
|
290
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
291
|
-
for (const child of fiber.children) {
|
|
292
|
-
this.collectCloudDOMNodes(child, collected);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Extract state from useState hooks in a Fiber node
|
|
298
|
-
*
|
|
299
|
-
* REQ-02: Extract outputs from useState calls
|
|
300
|
-
* REQ-06: Universal output access
|
|
301
|
-
* REQ-2.1: Store useState values without 'state.' prefix
|
|
302
|
-
*
|
|
303
|
-
* @param fiber - Fiber node to extract state from
|
|
304
|
-
* @returns Object mapping state keys to values (state1, state2, etc.)
|
|
305
|
-
*/
|
|
306
|
-
extractOutputsFromFiber(fiber) {
|
|
307
|
-
const stateValues = {};
|
|
308
|
-
// Debug logging
|
|
309
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
310
|
-
const safeStringify = (value) => {
|
|
311
|
-
try {
|
|
312
|
-
return JSON.stringify(value);
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return String(value);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
logger.debug(`extractOutputsFromFiber: fiber.path=${fiber.path?.join('.')}, hooks=${safeStringify(fiber.hooks)}`);
|
|
319
|
-
}
|
|
320
|
-
// Check if this Fiber has hooks from useState calls
|
|
321
|
-
if (fiber.hooks && Array.isArray(fiber.hooks) && fiber.hooks.length > 0) {
|
|
322
|
-
// Each hook in the array represents a useState call
|
|
323
|
-
// REQ-2.1: Store as state1, state2, etc. (no 'state.' prefix)
|
|
324
|
-
fiber.hooks.forEach((hookValue, index) => {
|
|
325
|
-
// Generate state key without prefix: state1, state2, etc.
|
|
326
|
-
const stateKey = `state${index + 1}`;
|
|
327
|
-
stateValues[stateKey] = hookValue;
|
|
328
|
-
});
|
|
329
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
330
|
-
logger.debug(`Extracted useState values without prefix:`, stateValues);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return stateValues;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Execute post-deployment effects for all Fiber nodes
|
|
337
|
-
* Called after successful deployment to run useEffect callbacks
|
|
338
|
-
*
|
|
339
|
-
* @param fiber - Root Fiber node
|
|
340
|
-
*/
|
|
341
|
-
async executePostDeploymentEffects(fiber) {
|
|
342
|
-
const { executeEffects } = await Promise.resolve().then(() => __importStar(require('../hooks/useEffect')));
|
|
343
|
-
logger.debug(`Executing effects for fiber: ${fiber.path?.join('.')}`);
|
|
344
|
-
logger.debug(`Fiber has effects: ${!!fiber.effects}`);
|
|
345
|
-
// Execute effects for this node
|
|
346
|
-
executeEffects(fiber);
|
|
347
|
-
// Recursively execute effects for children
|
|
348
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
349
|
-
for (const child of fiber.children) {
|
|
350
|
-
await this.executePostDeploymentEffects(child);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Sync Fiber hook state back to CloudDOM outputs after effects run
|
|
356
|
-
* This ensures useState changes in useEffect are reflected in the CloudDOM
|
|
357
|
-
*
|
|
358
|
-
* @param fiber - Root Fiber node
|
|
359
|
-
* @param cloudDOM - CloudDOM nodes to update
|
|
360
|
-
*/
|
|
361
|
-
syncFiberStateToCloudDOM(fiber, cloudDOM) {
|
|
362
|
-
logger.debug(`Syncing Fiber state to CloudDOM...`);
|
|
363
|
-
// Build a map of CloudDOM nodes by path for fast lookup
|
|
364
|
-
const cloudDOMMap = new Map();
|
|
365
|
-
const buildMap = (nodes) => {
|
|
366
|
-
for (const node of nodes) {
|
|
367
|
-
const pathKey = node.path.join('.');
|
|
368
|
-
cloudDOMMap.set(pathKey, node);
|
|
369
|
-
if (node.children && node.children.length > 0) {
|
|
370
|
-
buildMap(node.children);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
buildMap(cloudDOM);
|
|
375
|
-
// Recursively sync Fiber state to CloudDOM
|
|
376
|
-
this.syncFiberNodeToCloudDOM(fiber, cloudDOMMap);
|
|
377
|
-
logger.debug(`Fiber state sync completed`);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Sync a single Fiber node's state to its corresponding CloudDOM node
|
|
381
|
-
*/
|
|
382
|
-
syncFiberNodeToCloudDOM(fiber, cloudDOMMap) {
|
|
383
|
-
// Check if this Fiber node has corresponding CloudDOM nodes
|
|
384
|
-
if (fiber.cloudDOMNodes && Array.isArray(fiber.cloudDOMNodes)) {
|
|
385
|
-
for (const cloudNode of fiber.cloudDOMNodes) {
|
|
386
|
-
this.updateCloudDOMNodeOutputs(fiber, cloudNode);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Recursively sync children
|
|
390
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
391
|
-
for (const child of fiber.children) {
|
|
392
|
-
this.syncFiberNodeToCloudDOM(child, cloudDOMMap);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Update a CloudDOM node's state with the latest Fiber hook state
|
|
398
|
-
*
|
|
399
|
-
* REQ-1.2, 1.3: Separate useState values from provider outputs
|
|
400
|
-
* State is stored in separate `state` field, not in `outputs`
|
|
401
|
-
*/
|
|
402
|
-
updateCloudDOMNodeOutputs(fiber, cloudNode) {
|
|
403
|
-
// Extract fresh state values from the Fiber node
|
|
404
|
-
const stateValues = this.extractOutputsFromFiber(fiber);
|
|
405
|
-
if (Object.keys(stateValues).length > 0) {
|
|
406
|
-
// REQ-1.2: Write to state field, not outputs
|
|
407
|
-
cloudNode.state = stateValues;
|
|
408
|
-
logger.debug(`Synced state for ${cloudNode.id}:`, cloudNode.state);
|
|
409
|
-
}
|
|
410
|
-
// CRITICAL FIX: Sync state back to original nodes that components reference
|
|
411
|
-
this.syncOutputsToOriginalNodes(fiber, cloudNode);
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Sync state back to the original CloudDOM nodes that components reference
|
|
415
|
-
* This ensures that enhanced node proxies see the updated state
|
|
416
|
-
*/
|
|
417
|
-
syncOutputsToOriginalNodes(fiber, updatedNode) {
|
|
418
|
-
// Find the original nodes in the fiber that match this updated node
|
|
419
|
-
if (fiber.cloudDOMNodes && Array.isArray(fiber.cloudDOMNodes)) {
|
|
420
|
-
for (const originalNode of fiber.cloudDOMNodes) {
|
|
421
|
-
if (originalNode.id === updatedNode.id) {
|
|
422
|
-
// Update the original node's state with the latest values
|
|
423
|
-
if (updatedNode.state) {
|
|
424
|
-
originalNode.state = { ...updatedNode.state };
|
|
425
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
426
|
-
logger.debug(`Synced state to original node ${originalNode.id}:`, originalNode.state);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
// Legacy cloudDOMNode removed
|
|
434
|
-
// Recursively sync for child fibers
|
|
435
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
436
|
-
for (const child of fiber.children) {
|
|
437
|
-
this.syncOutputsToOriginalNodes(child, updatedNode);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Type guard to validate CloudDOM node structure
|
|
443
|
-
*
|
|
444
|
-
* @param node - Potential CloudDOM node
|
|
445
|
-
* @returns True if node is a valid CloudDOM node
|
|
446
|
-
*/
|
|
447
|
-
isValidCloudNode(node) {
|
|
448
|
-
// Filter out placeholder nodes - they shouldn't be deployed
|
|
449
|
-
if (node?.id?.includes?.('__placeholder__') || node?.constructType === 'Placeholder') {
|
|
450
|
-
return false;
|
|
451
|
-
}
|
|
452
|
-
return (typeof node?.id === 'string' &&
|
|
453
|
-
Array.isArray(node?.path) &&
|
|
454
|
-
node.path.length > 0 &&
|
|
455
|
-
typeof node?.construct !== 'undefined' &&
|
|
456
|
-
typeof node?.props === 'object' &&
|
|
457
|
-
Array.isArray(node?.children));
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Validate CloudDOM nodes for common issues
|
|
461
|
-
*
|
|
462
|
-
* Checks for:
|
|
463
|
-
* - Duplicate IDs (would cause silent overwrites in hierarchy building)
|
|
464
|
-
* - Invalid paths (empty or non-array)
|
|
465
|
-
* - Circular references in paths
|
|
466
|
-
*
|
|
467
|
-
* Enhanced for re-render scenarios:
|
|
468
|
-
* - Allows re-validation of existing nodes from the same fiber path
|
|
469
|
-
* - Prevents false positives during reactive re-renders
|
|
470
|
-
*
|
|
471
|
-
* @param nodes - CloudDOM nodes to validate
|
|
472
|
-
* @throws Error if duplicate IDs or circular references are found
|
|
473
|
-
*/
|
|
474
|
-
validateCloudDOMNodes(nodes) {
|
|
475
|
-
const seenIds = new Set();
|
|
476
|
-
const pathStrings = new Set();
|
|
477
|
-
for (const node of nodes) {
|
|
478
|
-
const currentFiberPath = node.path.join('.');
|
|
479
|
-
// Check for duplicate IDs with re-render support
|
|
480
|
-
if (seenIds.has(node.id)) {
|
|
481
|
-
// Check if this is a re-render of the same node from the same fiber path
|
|
482
|
-
const existingFiberPath = this.existingNodeMap.get(node.id);
|
|
483
|
-
if (existingFiberPath === currentFiberPath) {
|
|
484
|
-
// This is a re-render of the same node - allow it
|
|
485
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
486
|
-
logger.debug(`Re-validating existing node during re-render: ${node.id}`);
|
|
487
|
-
}
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
// This is a true duplicate from different fiber paths
|
|
491
|
-
throw new Error(`[CloudDOMBuilder] Duplicate CloudDOMNode id detected: '${node.id}' at ${this.formatPath(node)}. ` +
|
|
492
|
-
`Each resource must have a unique ID. ` +
|
|
493
|
-
`Use the 'key' prop to differentiate components with the same name.`);
|
|
494
|
-
}
|
|
495
|
-
seenIds.add(node.id);
|
|
496
|
-
// Track this node for future re-render validation
|
|
497
|
-
this.existingNodeMap.set(node.id, currentFiberPath);
|
|
498
|
-
// Validate path (should already be filtered in collectCloudDOMNodes, but double-check)
|
|
499
|
-
if (!Array.isArray(node.path) || node.path.length === 0) {
|
|
500
|
-
throw new Error(`[CloudDOMBuilder] CloudDOMNode '${node.id}' has invalid path. ` +
|
|
501
|
-
`Path must be a non-empty array of strings.`);
|
|
502
|
-
}
|
|
503
|
-
// Check for circular references (path pointing to itself)
|
|
504
|
-
const pathString = node.path.join('.');
|
|
505
|
-
if (pathStrings.has(pathString)) {
|
|
506
|
-
// Multiple nodes with same path could indicate circular reference
|
|
507
|
-
const existingNode = nodes.find((n) => n.path.join('.') === pathString && n.id !== node.id);
|
|
508
|
-
if (existingNode) {
|
|
509
|
-
throw new Error(`[CloudDOMBuilder] Circular dependency detected: nodes '${existingNode.id}' and '${node.id}' share the same path '${pathString}'`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
pathStrings.add(pathString);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Create deep defensive copy of CloudDOM nodes to avoid mutation
|
|
517
|
-
*
|
|
518
|
-
* Recursively copies:
|
|
519
|
-
* - Node properties
|
|
520
|
-
* - Props object
|
|
521
|
-
* - Outputs object
|
|
522
|
-
* - Children array (deep copy)
|
|
523
|
-
*
|
|
524
|
-
* This prevents mutations from later phases (e.g., provider injection,
|
|
525
|
-
* dependency graph tagging) from affecting the original nodes.
|
|
526
|
-
*
|
|
527
|
-
* @param nodes - Original CloudDOM nodes
|
|
528
|
-
* @returns Deep copy of nodes
|
|
529
|
-
*/
|
|
530
|
-
createDeepDefensiveCopy(nodes) {
|
|
531
|
-
return nodes.map((node) => ({
|
|
532
|
-
...node,
|
|
533
|
-
props: { ...node.props },
|
|
534
|
-
outputs: node.outputs ? { ...node.outputs } : undefined,
|
|
535
|
-
children: node.children ? this.createDeepDefensiveCopy(node.children) : [],
|
|
536
|
-
}));
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Build parent-child hierarchy from flat list of CloudDOM nodes
|
|
540
|
-
*
|
|
541
|
-
* Uses the path property to determine parent-child relationships:
|
|
542
|
-
* - A node is a child of another if its path starts with the parent's path
|
|
543
|
-
* - Returns only root nodes (nodes with no parent)
|
|
544
|
-
*
|
|
545
|
-
* Example:
|
|
546
|
-
* - ['registry'] is root
|
|
547
|
-
* - ['registry', 'service'] is child of ['registry']
|
|
548
|
-
* - ['registry', 'service', 'task'] is child of ['registry', 'service']
|
|
549
|
-
*
|
|
550
|
-
* Optimization: Groups nodes by depth for O(n) parent lookup instead of O(n²)
|
|
551
|
-
*
|
|
552
|
-
* @param nodes - Flat array of CloudDOM nodes
|
|
553
|
-
* @returns Array of root CloudDOM nodes with children attached
|
|
554
|
-
*/
|
|
555
|
-
buildHierarchy(nodes) {
|
|
556
|
-
if (nodes.length === 0) {
|
|
557
|
-
return [];
|
|
558
|
-
}
|
|
559
|
-
// Group nodes by depth (path length) for optimized parent lookup
|
|
560
|
-
const nodesByDepth = new Map();
|
|
561
|
-
for (const node of nodes) {
|
|
562
|
-
const depth = node.path.length;
|
|
563
|
-
if (!nodesByDepth.has(depth)) {
|
|
564
|
-
nodesByDepth.set(depth, []);
|
|
565
|
-
}
|
|
566
|
-
nodesByDepth.get(depth).push(node);
|
|
567
|
-
}
|
|
568
|
-
// Find root nodes and build parent-child relationships
|
|
569
|
-
const rootNodes = [];
|
|
570
|
-
for (const node of nodes) {
|
|
571
|
-
// Find parent by checking nodes at depth - 1
|
|
572
|
-
const parentNode = this.findParentOptimized(node, nodesByDepth);
|
|
573
|
-
if (parentNode) {
|
|
574
|
-
// Add this node as a child of its parent
|
|
575
|
-
if (!parentNode.children) {
|
|
576
|
-
parentNode.children = [];
|
|
577
|
-
}
|
|
578
|
-
parentNode.children.push(node);
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
// No parent found - this is a root node
|
|
582
|
-
rootNodes.push(node);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
return rootNodes;
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Find the parent node for a given node (optimized version)
|
|
589
|
-
*
|
|
590
|
-
* A node is the parent if:
|
|
591
|
-
* 1. Its path is a prefix of the child's path
|
|
592
|
-
* 2. Its path length is exactly one less than the child's path length (immediate parent)
|
|
593
|
-
*
|
|
594
|
-
* Example:
|
|
595
|
-
* - Child path: ['registry', 'service', 'task']
|
|
596
|
-
* - Parent path: ['registry', 'service'] ✓ (immediate parent)
|
|
597
|
-
* - Not parent: ['registry'] ✗ (grandparent, not immediate)
|
|
598
|
-
*
|
|
599
|
-
* Optimization: Uses nodesByDepth map to only search nodes at parent depth (O(n) instead of O(n²))
|
|
600
|
-
*
|
|
601
|
-
* @param node - Node to find parent for
|
|
602
|
-
* @param nodesByDepth - Map of nodes grouped by path depth
|
|
603
|
-
* @returns Parent node or undefined if no parent
|
|
604
|
-
*/
|
|
605
|
-
findParentOptimized(node, nodesByDepth) {
|
|
606
|
-
// Root nodes have no parent
|
|
607
|
-
if (node.path.length === 1) {
|
|
608
|
-
return undefined;
|
|
609
|
-
}
|
|
610
|
-
// Look for immediate parent at depth - 1
|
|
611
|
-
const parentDepth = node.path.length - 1;
|
|
612
|
-
const possibleParents = nodesByDepth.get(parentDepth);
|
|
613
|
-
if (!possibleParents) {
|
|
614
|
-
// No nodes at parent depth - this is an orphan node
|
|
615
|
-
if (process.env.NODE_ENV === 'development') {
|
|
616
|
-
logger.warn(`Orphaned node '${node.id}' (parent missing for path ${node.path.join('.')})`);
|
|
617
|
-
}
|
|
618
|
-
return undefined;
|
|
619
|
-
}
|
|
620
|
-
// Find parent by checking if path is a prefix
|
|
621
|
-
return possibleParents.find((parent) => parent.path.every((segment, i) => segment === node.path[i]));
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Get the cloud provider (for testing/debugging)
|
|
625
|
-
*
|
|
626
|
-
* @returns The injected cloud provider
|
|
627
|
-
*/
|
|
628
|
-
getCloudProvider() {
|
|
629
|
-
return this.cloudProvider;
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Convert CloudDOM tree to flat map for debugging and backend storage
|
|
633
|
-
*
|
|
634
|
-
* Useful for:
|
|
635
|
-
* - Backend state providers that need flat ID → node mapping
|
|
636
|
-
* - Debugging and inspection
|
|
637
|
-
* - Quick lookups by ID
|
|
638
|
-
*
|
|
639
|
-
* Type-safe: Preserves node subtype information for provider-specific extensions
|
|
640
|
-
*
|
|
641
|
-
* @param rootNodes - Root CloudDOM nodes
|
|
642
|
-
* @returns Flat map of ID → CloudDOM node
|
|
643
|
-
*/
|
|
644
|
-
toFlatMap(rootNodes) {
|
|
645
|
-
const map = {};
|
|
646
|
-
const walk = (node) => {
|
|
647
|
-
map[node.id] = node;
|
|
648
|
-
if (node.children && node.children.length > 0) {
|
|
649
|
-
node.children.forEach((child) => walk(child));
|
|
650
|
-
}
|
|
651
|
-
};
|
|
652
|
-
rootNodes.forEach(walk);
|
|
653
|
-
return map;
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Normalize path segments for consistent resource addressing
|
|
657
|
-
*
|
|
658
|
-
* Ensures consistent casing and formatting across environments:
|
|
659
|
-
* - Trims whitespace
|
|
660
|
-
* - Converts to lowercase
|
|
661
|
-
* - Replaces slashes and spaces with hyphens
|
|
662
|
-
*
|
|
663
|
-
* REQ-NF: Future safety for cross-OS consistency
|
|
664
|
-
*
|
|
665
|
-
* @param path - Path segments to normalize
|
|
666
|
-
* @returns Normalized path segments
|
|
667
|
-
*/
|
|
668
|
-
normalizePath(path) {
|
|
669
|
-
return path.map((segment) => segment
|
|
670
|
-
.trim()
|
|
671
|
-
.toLowerCase()
|
|
672
|
-
.replace(/[\/\s]+/g, '-')
|
|
673
|
-
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Format path for human-readable error messages
|
|
678
|
-
*
|
|
679
|
-
* @param node - CloudDOM node
|
|
680
|
-
* @returns Formatted path string (e.g., "registry > service > task")
|
|
681
|
-
*/
|
|
682
|
-
formatPath(node) {
|
|
683
|
-
return node.path.join(' > ');
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Detect circular references in CloudDOM hierarchy
|
|
687
|
-
*
|
|
688
|
-
* Uses depth-first search to detect cycles in parent-child relationships.
|
|
689
|
-
* This is a graph-level safety check beyond simple path validation.
|
|
690
|
-
*
|
|
691
|
-
* @param nodes - Root CloudDOM nodes
|
|
692
|
-
* @throws Error if circular hierarchy is detected
|
|
693
|
-
*/
|
|
694
|
-
detectCircularRefs(nodes) {
|
|
695
|
-
const visited = new Set();
|
|
696
|
-
const stack = new Set();
|
|
697
|
-
const dfs = (node) => {
|
|
698
|
-
if (stack.has(node.id)) {
|
|
699
|
-
throw new Error(`[CloudDOMBuilder] Circular hierarchy detected at '${node.id}' (path: ${this.formatPath(node)})`);
|
|
700
|
-
}
|
|
701
|
-
if (visited.has(node.id)) {
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
visited.add(node.id);
|
|
705
|
-
stack.add(node.id);
|
|
706
|
-
if (node.children && node.children.length > 0) {
|
|
707
|
-
node.children.forEach(dfs);
|
|
708
|
-
}
|
|
709
|
-
stack.delete(node.id);
|
|
710
|
-
};
|
|
711
|
-
nodes.forEach(dfs);
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Build efficient node map for CloudDOM comparison
|
|
715
|
-
* Creates a flat map of node ID to node for fast lookup during comparison
|
|
716
|
-
* Handles nested node structures properly
|
|
717
|
-
*
|
|
718
|
-
* REQ-1.4, 9.1: Efficient map building for CloudDOM comparison
|
|
719
|
-
*
|
|
720
|
-
* @param nodes - CloudDOM nodes to map
|
|
721
|
-
* @returns Map of node ID to CloudDOM node
|
|
722
|
-
*/
|
|
723
|
-
buildNodeMap(nodes) {
|
|
724
|
-
const nodeMap = new Map();
|
|
725
|
-
const walkNodes = (nodeList) => {
|
|
726
|
-
for (const node of nodeList) {
|
|
727
|
-
// Ensure consistent node identification
|
|
728
|
-
if (node.id) {
|
|
729
|
-
nodeMap.set(node.id, node);
|
|
730
|
-
}
|
|
731
|
-
// Recursively handle nested children
|
|
732
|
-
if (node.children && node.children.length > 0) {
|
|
733
|
-
walkNodes(node.children);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
walkNodes(nodes);
|
|
738
|
-
return nodeMap;
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Detect output changes after deployment by comparing previous and current CloudDOM
|
|
742
|
-
* This is called after deployment to identify which provider outputs have changed
|
|
743
|
-
*
|
|
744
|
-
* Enhanced to:
|
|
745
|
-
* - Compare previous and current CloudDOM states properly
|
|
746
|
-
* - Identify specific output keys that changed
|
|
747
|
-
* - Track affected fibers for each change
|
|
748
|
-
*
|
|
749
|
-
* REQ-1.4, 1.5, 9.1, 9.2, 9.3: Enhanced output change detection
|
|
750
|
-
*
|
|
751
|
-
* @param previous - Previous CloudDOM state
|
|
752
|
-
* @param current - Current CloudDOM state after deployment
|
|
753
|
-
* @returns Array of output changes detected
|
|
754
|
-
*/
|
|
755
|
-
detectOutputChanges(previous, current) {
|
|
756
|
-
const changes = [];
|
|
757
|
-
// REQ-9.1: Build efficient maps for comparison using buildNodeMap
|
|
758
|
-
const previousMap = this.buildNodeMap(previous);
|
|
759
|
-
const currentMap = this.buildNodeMap(current);
|
|
760
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
761
|
-
logger.debug(`Detecting output changes: ${previousMap.size} previous nodes, ${currentMap.size} current nodes`);
|
|
762
|
-
}
|
|
763
|
-
// REQ-9.2, 9.3: Compare previous and current states, identify specific changes
|
|
764
|
-
// Check for changed outputs in existing nodes and new outputs in new nodes
|
|
765
|
-
for (const [nodeId, currentNode] of currentMap.entries()) {
|
|
766
|
-
const previousNode = previousMap.get(nodeId);
|
|
767
|
-
// REQ-9.3: Track affected fibers for each change
|
|
768
|
-
const getAffectedFibers = () => {
|
|
769
|
-
if (!this.providerOutputTracker) {
|
|
770
|
-
return [];
|
|
771
|
-
}
|
|
772
|
-
return Array.from(this.providerOutputTracker.getBindingsForInstance(nodeId));
|
|
773
|
-
};
|
|
774
|
-
if (previousNode) {
|
|
775
|
-
// Node existed before - check for changed or new outputs
|
|
776
|
-
const currentOutputs = currentNode.outputs || {};
|
|
777
|
-
const previousOutputs = previousNode.outputs || {};
|
|
778
|
-
// Check for changed or new outputs
|
|
779
|
-
for (const [outputKey, currentValue] of Object.entries(currentOutputs)) {
|
|
780
|
-
const previousValue = previousOutputs[outputKey];
|
|
781
|
-
// REQ-9.2: Identify specific output keys that changed
|
|
782
|
-
if (previousValue !== currentValue) {
|
|
783
|
-
changes.push({
|
|
784
|
-
nodeId,
|
|
785
|
-
outputKey,
|
|
786
|
-
previousValue,
|
|
787
|
-
newValue: currentValue,
|
|
788
|
-
affectedFibers: getAffectedFibers(),
|
|
789
|
-
});
|
|
790
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
791
|
-
logger.debug(`Output changed: ${nodeId}.${outputKey} (${previousValue} → ${currentValue})`);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// Check for removed outputs
|
|
796
|
-
for (const [outputKey, previousValue] of Object.entries(previousOutputs)) {
|
|
797
|
-
if (!(outputKey in currentOutputs)) {
|
|
798
|
-
changes.push({
|
|
799
|
-
nodeId,
|
|
800
|
-
outputKey,
|
|
801
|
-
previousValue,
|
|
802
|
-
newValue: undefined,
|
|
803
|
-
affectedFibers: getAffectedFibers(),
|
|
804
|
-
});
|
|
805
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
806
|
-
logger.debug(`Output removed: ${nodeId}.${outputKey}`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
// New node - all outputs are new
|
|
813
|
-
const currentOutputs = currentNode.outputs || {};
|
|
814
|
-
for (const [outputKey, currentValue] of Object.entries(currentOutputs)) {
|
|
815
|
-
changes.push({
|
|
816
|
-
nodeId,
|
|
817
|
-
outputKey,
|
|
818
|
-
previousValue: undefined,
|
|
819
|
-
newValue: currentValue,
|
|
820
|
-
affectedFibers: getAffectedFibers(),
|
|
821
|
-
});
|
|
822
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
823
|
-
logger.debug(`New output: ${nodeId}.${outputKey} = ${currentValue}`);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
// Check for deleted nodes (all their outputs are removed)
|
|
829
|
-
for (const [nodeId, previousNode] of previousMap.entries()) {
|
|
830
|
-
if (!currentMap.has(nodeId)) {
|
|
831
|
-
const previousOutputs = previousNode.outputs || {};
|
|
832
|
-
const getAffectedFibers = () => {
|
|
833
|
-
if (!this.providerOutputTracker) {
|
|
834
|
-
return [];
|
|
835
|
-
}
|
|
836
|
-
return Array.from(this.providerOutputTracker.getBindingsForInstance(nodeId));
|
|
837
|
-
};
|
|
838
|
-
for (const [outputKey, previousValue] of Object.entries(previousOutputs)) {
|
|
839
|
-
changes.push({
|
|
840
|
-
nodeId,
|
|
841
|
-
outputKey,
|
|
842
|
-
previousValue,
|
|
843
|
-
newValue: undefined,
|
|
844
|
-
affectedFibers: getAffectedFibers(),
|
|
845
|
-
});
|
|
846
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
847
|
-
logger.debug(`Node deleted, output removed: ${nodeId}.${outputKey}`);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
853
|
-
logger.debug(`Detected ${changes.length} output changes`);
|
|
854
|
-
}
|
|
855
|
-
return changes;
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Synchronize provider outputs to bound state and trigger re-renders
|
|
859
|
-
* This is the main method called after deployment to update reactive state
|
|
860
|
-
*
|
|
861
|
-
* @param fiber - Root fiber node
|
|
862
|
-
* @param cloudDOM - Current CloudDOM after deployment
|
|
863
|
-
* @param previousCloudDOM - Previous CloudDOM state (optional)
|
|
864
|
-
* @returns Promise resolving to affected fibers that need re-rendering
|
|
865
|
-
*/
|
|
866
|
-
async syncOutputsAndReRender(fiber, cloudDOM, previousCloudDOM) {
|
|
867
|
-
if (!this.stateBindingManager || !this.providerOutputTracker) {
|
|
868
|
-
logger.warn('Reactive components not set, skipping output sync');
|
|
869
|
-
return [];
|
|
870
|
-
}
|
|
871
|
-
try {
|
|
872
|
-
// Step 1: Update provider output tracker with new outputs
|
|
873
|
-
// Note: Output syncing to original nodes is now done before effects in integrateWithPostDeploymentEffects
|
|
874
|
-
const outputChanges = [];
|
|
875
|
-
for (const node of cloudDOM) {
|
|
876
|
-
if (node.outputs) {
|
|
877
|
-
const nodeChanges = this.providerOutputTracker.updateInstanceOutputs(node.id, node.outputs);
|
|
878
|
-
outputChanges.push(...nodeChanges);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
// Step 2: If we have previous state, detect additional changes
|
|
882
|
-
if (previousCloudDOM) {
|
|
883
|
-
const additionalChanges = this.detectOutputChanges(previousCloudDOM, cloudDOM);
|
|
884
|
-
// Merge changes, avoiding duplicates
|
|
885
|
-
for (const change of additionalChanges) {
|
|
886
|
-
const exists = outputChanges.some((existing) => existing.nodeId === change.nodeId && existing.outputKey === change.outputKey);
|
|
887
|
-
if (!exists) {
|
|
888
|
-
outputChanges.push(change);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
// Step 3: Apply output changes to bound state
|
|
893
|
-
const stateAffectedFibers = this.applyOutputChangesToState(outputChanges);
|
|
894
|
-
// Step 4: Update context provider values (NEW - enables context reactivity)
|
|
895
|
-
// This re-evaluates context provider values that depend on outputs
|
|
896
|
-
console.log('[CONTEXT REACTIVITY] Updating context provider values after output changes...');
|
|
897
|
-
const contextAffectedFibers = this.updateContextProviderValues(fiber);
|
|
898
|
-
// Merge affected fibers from both state and context updates
|
|
899
|
-
const allAffectedFibers = [...stateAffectedFibers];
|
|
900
|
-
for (const contextFiber of contextAffectedFibers) {
|
|
901
|
-
if (!allAffectedFibers.includes(contextFiber)) {
|
|
902
|
-
allAffectedFibers.push(contextFiber);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
// Debug logging
|
|
906
|
-
console.log(`[CONTEXT REACTIVITY] Synced ${outputChanges.length} output changes`);
|
|
907
|
-
console.log(`[CONTEXT REACTIVITY] State affected: ${stateAffectedFibers.length} fibers`);
|
|
908
|
-
logger.debug(` Context affected: ${contextAffectedFibers.length} fibers`);
|
|
909
|
-
logger.debug(` Total affected: ${allAffectedFibers.length} fibers`);
|
|
910
|
-
// NEW (Gap 2): Flush ReactiveUpdateQueue to get all dirty fibers
|
|
911
|
-
// This includes fibers enqueued by applyOutputChangesToState
|
|
912
|
-
const reactiveQueue = (0, ReactiveUpdateQueue_1.getReactiveUpdateQueue)();
|
|
913
|
-
const queuedFibers = reactiveQueue.flush();
|
|
914
|
-
if (queuedFibers.length > 0) {
|
|
915
|
-
logger.debug(`Flushed ${queuedFibers.length} fibers from ReactiveUpdateQueue`);
|
|
916
|
-
// Merge queued fibers with affected fibers (avoid duplicates)
|
|
917
|
-
for (const queuedFiber of queuedFibers) {
|
|
918
|
-
if (!allAffectedFibers.includes(queuedFiber)) {
|
|
919
|
-
allAffectedFibers.push(queuedFiber);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
logger.debug(`Total affected fibers after queue flush: ${allAffectedFibers.length}`);
|
|
924
|
-
return allAffectedFibers;
|
|
925
|
-
}
|
|
926
|
-
catch (error) {
|
|
927
|
-
logger.error('Error during output synchronization:', error);
|
|
928
|
-
return [];
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Sync CloudDOM outputs to the original nodes that components reference
|
|
933
|
-
* This is critical for reactivity to work - components need to see updated outputs
|
|
934
|
-
*/
|
|
935
|
-
syncCloudDOMOutputsToOriginalNodes(fiber, cloudDOM) {
|
|
936
|
-
logger.debug('syncCloudDOMOutputsToOriginalNodes called');
|
|
937
|
-
logger.debug(`CloudDOM nodes to sync: ${cloudDOM.length}`);
|
|
938
|
-
// Build a map of CloudDOM nodes by ID for fast lookup
|
|
939
|
-
const cloudDOMMap = new Map();
|
|
940
|
-
const buildMap = (nodes) => {
|
|
941
|
-
for (const node of nodes) {
|
|
942
|
-
cloudDOMMap.set(node.id, node);
|
|
943
|
-
if (node.outputs && Object.keys(node.outputs).length > 0) {
|
|
944
|
-
logger.debug(`CloudDOM node ${node.id} has outputs:`, Object.keys(node.outputs));
|
|
945
|
-
}
|
|
946
|
-
if (node.children && node.children.length > 0) {
|
|
947
|
-
buildMap(node.children);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
buildMap(cloudDOM);
|
|
952
|
-
logger.debug(`Built CloudDOM map with ${cloudDOMMap.size} nodes`);
|
|
953
|
-
// Recursively sync outputs to original nodes
|
|
954
|
-
this.syncOutputsToOriginalNodesRecursive(fiber, cloudDOMMap);
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* Recursively sync outputs to original nodes in the fiber tree
|
|
958
|
-
*/
|
|
959
|
-
syncOutputsToOriginalNodesRecursive(fiber, cloudDOMMap) {
|
|
960
|
-
// Sync outputs for nodes in this fiber
|
|
961
|
-
if (fiber.cloudDOMNodes && Array.isArray(fiber.cloudDOMNodes)) {
|
|
962
|
-
for (const originalNode of fiber.cloudDOMNodes) {
|
|
963
|
-
const updatedNode = cloudDOMMap.get(originalNode.id);
|
|
964
|
-
if (updatedNode && updatedNode.outputs) {
|
|
965
|
-
if (!originalNode.outputs) {
|
|
966
|
-
originalNode.outputs = {};
|
|
967
|
-
}
|
|
968
|
-
// Merge outputs from the deployed CloudDOM back to the original node
|
|
969
|
-
const beforeOutputs = { ...originalNode.outputs };
|
|
970
|
-
Object.assign(originalNode.outputs, updatedNode.outputs);
|
|
971
|
-
logger.debug(`✓ Synced outputs to original node ${originalNode.id}`);
|
|
972
|
-
logger.debug(` Before:`, beforeOutputs);
|
|
973
|
-
logger.debug(` After:`, originalNode.outputs);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
// Legacy cloudDOMNode removed
|
|
978
|
-
// Recursively sync for child fibers
|
|
979
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
980
|
-
for (const child of fiber.children) {
|
|
981
|
-
this.syncOutputsToOriginalNodesRecursive(child, cloudDOMMap);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Update context provider values when outputs change
|
|
987
|
-
* This enables context reactivity by finding components that create context values from outputs
|
|
988
|
-
*
|
|
989
|
-
* Strategy:
|
|
990
|
-
* 1. Find all fibers that have both cloudDOMNodes (use outputs) AND render a Context.Provider child
|
|
991
|
-
* 2. These are the components that create context values from outputs
|
|
992
|
-
* 3. Mark them for re-render so they re-evaluate their context values
|
|
993
|
-
*
|
|
994
|
-
* @param fiber - Root fiber to search
|
|
995
|
-
* @returns Array of fibers that need re-rendering (components creating context values from outputs)
|
|
996
|
-
*/
|
|
997
|
-
updateContextProviderValues(fiber) {
|
|
998
|
-
const affectedFibers = [];
|
|
999
|
-
// Recursively find components that create context values from outputs
|
|
1000
|
-
this.findContextValueCreators(fiber, affectedFibers);
|
|
1001
|
-
return affectedFibers;
|
|
1002
|
-
}
|
|
1003
|
-
/**
|
|
1004
|
-
* Recursively find components that create context values from outputs
|
|
1005
|
-
* These are components that:
|
|
1006
|
-
* 1. Have cloudDOMNodes (use useInstance - depend on outputs)
|
|
1007
|
-
* 2. Have children that are Context.Provider components
|
|
1008
|
-
*/
|
|
1009
|
-
findContextValueCreators(fiber, affectedFibers) {
|
|
1010
|
-
// IMPORTANT: Don't automatically mark context value creators as affected
|
|
1011
|
-
// Only mark them if their specific outputs changed (handled by ProviderOutputTracker)
|
|
1012
|
-
// This prevents unnecessary re-renders that lose context values from parent providers
|
|
1013
|
-
// Recursively process children only (don't mark anything as affected)
|
|
1014
|
-
if (fiber.children && fiber.children.length > 0) {
|
|
1015
|
-
for (const child of fiber.children) {
|
|
1016
|
-
this.findContextValueCreators(child, affectedFibers);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Apply output changes to bound state using StateBindingManager
|
|
1022
|
-
* This updates useState values that are bound to provider outputs
|
|
1023
|
-
*
|
|
1024
|
-
* Enhanced to:
|
|
1025
|
-
* - Apply detected changes to bound state via StateBindingManager
|
|
1026
|
-
* - Handle binding updates without creating loops
|
|
1027
|
-
* - Return affected fibers for re-rendering
|
|
1028
|
-
*
|
|
1029
|
-
* REQ-9.4, 9.5: Apply output changes to state and return affected fibers
|
|
1030
|
-
*
|
|
1031
|
-
* @param changes - Array of output changes to apply
|
|
1032
|
-
* @returns Array of fibers affected by the changes
|
|
1033
|
-
*/
|
|
1034
|
-
applyOutputChangesToState(changes) {
|
|
1035
|
-
if (!this.stateBindingManager) {
|
|
1036
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
1037
|
-
logger.debug('No StateBindingManager available, skipping state updates');
|
|
1038
|
-
}
|
|
1039
|
-
return [];
|
|
1040
|
-
}
|
|
1041
|
-
if (changes.length === 0) {
|
|
1042
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
1043
|
-
logger.debug('No output changes to apply');
|
|
1044
|
-
}
|
|
1045
|
-
return [];
|
|
1046
|
-
}
|
|
1047
|
-
try {
|
|
1048
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
1049
|
-
logger.debug(`Applying ${changes.length} output changes to bound state`);
|
|
1050
|
-
}
|
|
1051
|
-
// REQ-9.4: Process changes through state binding manager
|
|
1052
|
-
// This uses internal setState to prevent circular dependencies (REQ-4.2, 4.4)
|
|
1053
|
-
const affectedFibers = this.stateBindingManager.processOutputChanges(changes);
|
|
1054
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
1055
|
-
logger.debug(`${affectedFibers.length} fibers affected by output changes`);
|
|
1056
|
-
}
|
|
1057
|
-
// Notify provider output tracker about the changes for tracking
|
|
1058
|
-
if (this.providerOutputTracker) {
|
|
1059
|
-
try {
|
|
1060
|
-
this.providerOutputTracker.notifyOutputChanges(changes);
|
|
1061
|
-
}
|
|
1062
|
-
catch (error) {
|
|
1063
|
-
logger.warn('Error notifying provider output tracker:', error);
|
|
1064
|
-
// Continue despite notification failure
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
// NEW (Gap 2): Enqueue affected fibers to ReactiveUpdateQueue
|
|
1068
|
-
// This batches re-renders and prevents duplicate scheduling
|
|
1069
|
-
const reactiveQueue = (0, ReactiveUpdateQueue_1.getReactiveUpdateQueue)();
|
|
1070
|
-
affectedFibers.forEach((fiber) => {
|
|
1071
|
-
reactiveQueue.enqueue(fiber);
|
|
1072
|
-
});
|
|
1073
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
1074
|
-
logger.debug(`Enqueued ${affectedFibers.length} fibers to ReactiveUpdateQueue`);
|
|
1075
|
-
}
|
|
1076
|
-
// Execute reactive effects for affected fibers
|
|
1077
|
-
// This triggers useEffect callbacks that depend on changed outputs
|
|
1078
|
-
this.executeReactiveEffects(affectedFibers, changes);
|
|
1079
|
-
// REQ-9.5: Return affected fibers for re-rendering
|
|
1080
|
-
return affectedFibers;
|
|
1081
|
-
}
|
|
1082
|
-
catch (error) {
|
|
1083
|
-
logger.error('Error applying output changes to state:', error);
|
|
1084
|
-
// Return empty array to prevent re-render failures from blocking deployment
|
|
1085
|
-
// The error has been logged for debugging
|
|
1086
|
-
return [];
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
/**
|
|
1090
|
-
* Execute reactive effects for fibers affected by output changes
|
|
1091
|
-
* This triggers useEffect callbacks that depend on changed provider outputs
|
|
1092
|
-
*
|
|
1093
|
-
* @param affectedFibers - Fibers that were affected by output changes
|
|
1094
|
-
* @param changes - Output changes that occurred
|
|
1095
|
-
*/
|
|
1096
|
-
async executeReactiveEffects(affectedFibers, changes) {
|
|
1097
|
-
if (affectedFibers.length === 0) {
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
try {
|
|
1101
|
-
const { executeEffectsOnOutputChange } = await Promise.resolve().then(() => __importStar(require('../hooks/useEffect')));
|
|
1102
|
-
const { generateBindingKey } = await Promise.resolve().then(() => __importStar(require('../utils/naming')));
|
|
1103
|
-
// Convert output changes to binding keys for effect matching
|
|
1104
|
-
const changedOutputKeys = new Set();
|
|
1105
|
-
for (const change of changes) {
|
|
1106
|
-
const bindingKey = generateBindingKey(change.nodeId, change.outputKey);
|
|
1107
|
-
changedOutputKeys.add(bindingKey);
|
|
1108
|
-
}
|
|
1109
|
-
// Execute reactive effects for each affected fiber
|
|
1110
|
-
for (const fiber of affectedFibers) {
|
|
1111
|
-
executeEffectsOnOutputChange(fiber, changedOutputKeys);
|
|
1112
|
-
}
|
|
1113
|
-
if (process.env.CREACT_DEBUG === 'true') {
|
|
1114
|
-
logger.debug(`Executed reactive effects for ${affectedFibers.length} fibers`);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
catch (error) {
|
|
1118
|
-
logger.error('Error executing reactive effects:', error);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Integrate with post-deployment effects to trigger output synchronization
|
|
1123
|
-
* This method should be called after deployment completes
|
|
1124
|
-
*
|
|
1125
|
-
* Phase ordering (REQ-1.1, 1.2, 1.5):
|
|
1126
|
-
* 1. Sync outputs to original nodes BEFORE executing effects
|
|
1127
|
-
* 2. Execute effects (they can now see updated outputs)
|
|
1128
|
-
* 3. Sync outputs and trigger re-renders for reactive changes
|
|
1129
|
-
* 4. Sync state changes back to CloudDOM
|
|
1130
|
-
*
|
|
1131
|
-
* @param fiber - Root fiber node
|
|
1132
|
-
* @param cloudDOM - Current CloudDOM after deployment
|
|
1133
|
-
* @param previousCloudDOM - Previous CloudDOM state (optional)
|
|
1134
|
-
* @returns Promise resolving to affected fibers that need re-rendering
|
|
1135
|
-
*/
|
|
1136
|
-
async integrateWithPostDeploymentEffects(fiber, cloudDOM, previousCloudDOM) {
|
|
1137
|
-
logger.info('*** Starting post-deployment integration ***');
|
|
1138
|
-
logger.debug(`CloudDOM nodes: ${cloudDOM.length}`);
|
|
1139
|
-
logger.debug(`Fiber path: ${fiber.path?.join('.')}`);
|
|
1140
|
-
try {
|
|
1141
|
-
// PHASE 1: Sync outputs to original nodes FIRST (REQ-1.1, 1.4)
|
|
1142
|
-
// This ensures effects have access to current provider outputs
|
|
1143
|
-
logger.debug('PHASE 1: Syncing outputs to original nodes...');
|
|
1144
|
-
try {
|
|
1145
|
-
this.syncCloudDOMOutputsToOriginalNodes(fiber, cloudDOM);
|
|
1146
|
-
logger.debug('✓ Phase 1 complete: Outputs synced to original nodes');
|
|
1147
|
-
}
|
|
1148
|
-
catch (error) {
|
|
1149
|
-
logger.error('✗ Phase 1 failed: Error syncing outputs to original nodes:', error);
|
|
1150
|
-
throw new Error(`Output sync failed: ${error.message}`);
|
|
1151
|
-
}
|
|
1152
|
-
// PHASE 2: Execute post-deployment effects (REQ-1.2, 1.5)
|
|
1153
|
-
// Effects now have access to current outputs from Phase 1
|
|
1154
|
-
logger.debug('PHASE 2: Executing post-deployment effects...');
|
|
1155
|
-
try {
|
|
1156
|
-
await this.executePostDeploymentEffects(fiber);
|
|
1157
|
-
logger.debug('✓ Phase 2 complete: Effects executed with current outputs');
|
|
1158
|
-
}
|
|
1159
|
-
catch (error) {
|
|
1160
|
-
logger.error('✗ Phase 2 failed: Error executing effects:', error);
|
|
1161
|
-
// Continue to next phase even if effects fail (non-critical)
|
|
1162
|
-
logger.warn('Continuing to next phase despite effect execution failure');
|
|
1163
|
-
}
|
|
1164
|
-
// PHASE 3: Sync outputs and trigger re-renders (REQ-1.5)
|
|
1165
|
-
// Detect output changes and update bound state
|
|
1166
|
-
logger.debug('PHASE 3: Syncing outputs and triggering re-renders...');
|
|
1167
|
-
let affectedFibers = [];
|
|
1168
|
-
try {
|
|
1169
|
-
affectedFibers = await this.syncOutputsAndReRender(fiber, cloudDOM, previousCloudDOM);
|
|
1170
|
-
logger.debug(`✓ Phase 3 complete: ${affectedFibers.length} fibers affected by output changes`);
|
|
1171
|
-
}
|
|
1172
|
-
catch (error) {
|
|
1173
|
-
logger.error('✗ Phase 3 failed: Error syncing outputs and re-renders:', error);
|
|
1174
|
-
// Continue to next phase even if re-render sync fails (non-critical)
|
|
1175
|
-
logger.warn('Continuing to next phase despite re-render sync failure');
|
|
1176
|
-
}
|
|
1177
|
-
// PHASE 4: Sync state changes back to CloudDOM
|
|
1178
|
-
// Persist any state changes from effects
|
|
1179
|
-
logger.debug('PHASE 4: Syncing fiber state to CloudDOM...');
|
|
1180
|
-
try {
|
|
1181
|
-
this.syncFiberStateToCloudDOM(fiber, cloudDOM);
|
|
1182
|
-
logger.debug('✓ Phase 4 complete: Fiber state synced to CloudDOM');
|
|
1183
|
-
}
|
|
1184
|
-
catch (error) {
|
|
1185
|
-
logger.error('✗ Phase 4 failed: Error syncing fiber state:', error);
|
|
1186
|
-
// Continue despite failure (state will be synced on next deployment)
|
|
1187
|
-
logger.warn('State sync failed but deployment can continue');
|
|
1188
|
-
}
|
|
1189
|
-
logger.info(`*** Post-deployment integration complete: ${affectedFibers.length} affected fibers ***`);
|
|
1190
|
-
return affectedFibers;
|
|
1191
|
-
}
|
|
1192
|
-
catch (error) {
|
|
1193
|
-
logger.error('✗✗✗ Critical error in post-deployment integration:', error);
|
|
1194
|
-
logger.error('Stack trace:', error.stack);
|
|
1195
|
-
// Return empty array to allow deployment to complete
|
|
1196
|
-
// The error has been logged for debugging
|
|
1197
|
-
return [];
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Clear the existing node map (for testing or fresh builds)
|
|
1202
|
-
* This allows starting with a clean slate for node validation
|
|
1203
|
-
*/
|
|
1204
|
-
clearExistingNodes() {
|
|
1205
|
-
this.existingNodeMap.clear();
|
|
1206
|
-
}
|
|
1207
|
-
/**
|
|
1208
|
-
* Get the existing node map (for debugging/inspection)
|
|
1209
|
-
* Returns a copy to prevent external mutation
|
|
1210
|
-
*/
|
|
1211
|
-
getExistingNodes() {
|
|
1212
|
-
return new Map(this.existingNodeMap);
|
|
1213
|
-
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Create a JSON replacer function that handles Symbols safely
|
|
1216
|
-
* Converts Symbols to their string representation for debugging
|
|
1217
|
-
*/
|
|
1218
|
-
createSymbolReplacer() {
|
|
1219
|
-
return (key, value) => {
|
|
1220
|
-
if (typeof value === 'symbol') {
|
|
1221
|
-
return String(value);
|
|
1222
|
-
}
|
|
1223
|
-
if (typeof value === 'function' && value._contextId && typeof value._contextId === 'symbol') {
|
|
1224
|
-
// Handle context provider/consumer functions with Symbol IDs
|
|
1225
|
-
return {
|
|
1226
|
-
...value,
|
|
1227
|
-
_contextId: String(value._contextId),
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
return value;
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
exports.CloudDOMBuilder = CloudDOMBuilder;
|