@creact-labs/creact 0.1.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 +212 -0
- package/README.md +379 -0
- package/dist/cli/commands/BuildCommand.d.ts +40 -0
- package/dist/cli/commands/BuildCommand.js +151 -0
- package/dist/cli/commands/DeployCommand.d.ts +38 -0
- package/dist/cli/commands/DeployCommand.js +194 -0
- package/dist/cli/commands/DevCommand.d.ts +52 -0
- package/dist/cli/commands/DevCommand.js +385 -0
- package/dist/cli/commands/PlanCommand.d.ts +39 -0
- package/dist/cli/commands/PlanCommand.js +164 -0
- package/dist/cli/commands/index.d.ts +36 -0
- package/dist/cli/commands/index.js +43 -0
- package/dist/cli/core/ArgumentParser.d.ts +46 -0
- package/dist/cli/core/ArgumentParser.js +127 -0
- package/dist/cli/core/BaseCommand.d.ts +75 -0
- package/dist/cli/core/BaseCommand.js +95 -0
- package/dist/cli/core/CLIContext.d.ts +68 -0
- package/dist/cli/core/CLIContext.js +183 -0
- package/dist/cli/core/CommandRegistry.d.ts +64 -0
- package/dist/cli/core/CommandRegistry.js +89 -0
- package/dist/cli/core/index.d.ts +36 -0
- package/dist/cli/core/index.js +43 -0
- package/dist/cli/index.d.ts +35 -0
- package/dist/cli/index.js +100 -0
- package/dist/cli/output.d.ts +204 -0
- package/dist/cli/output.js +437 -0
- package/dist/cli/utils.d.ts +59 -0
- package/dist/cli/utils.js +76 -0
- package/dist/context/createContext.d.ts +90 -0
- package/dist/context/createContext.js +113 -0
- package/dist/context/index.d.ts +30 -0
- package/dist/context/index.js +35 -0
- package/dist/core/CReact.d.ts +409 -0
- package/dist/core/CReact.js +1127 -0
- package/dist/core/CloudDOMBuilder.d.ts +429 -0
- package/dist/core/CloudDOMBuilder.js +1198 -0
- package/dist/core/ContextDependencyTracker.d.ts +165 -0
- package/dist/core/ContextDependencyTracker.js +448 -0
- package/dist/core/ErrorRecoveryManager.d.ts +145 -0
- package/dist/core/ErrorRecoveryManager.js +443 -0
- package/dist/core/EventBus.d.ts +91 -0
- package/dist/core/EventBus.js +185 -0
- package/dist/core/ProviderOutputTracker.d.ts +211 -0
- package/dist/core/ProviderOutputTracker.js +476 -0
- package/dist/core/ReactiveUpdateQueue.d.ts +76 -0
- package/dist/core/ReactiveUpdateQueue.js +121 -0
- package/dist/core/Reconciler.d.ts +415 -0
- package/dist/core/Reconciler.js +1037 -0
- package/dist/core/RenderScheduler.d.ts +153 -0
- package/dist/core/RenderScheduler.js +519 -0
- package/dist/core/Renderer.d.ts +276 -0
- package/dist/core/Renderer.js +791 -0
- package/dist/core/Runtime.d.ts +246 -0
- package/dist/core/Runtime.js +640 -0
- package/dist/core/StateBindingManager.d.ts +121 -0
- package/dist/core/StateBindingManager.js +309 -0
- package/dist/core/StateMachine.d.ts +424 -0
- package/dist/core/StateMachine.js +787 -0
- package/dist/core/StructuralChangeDetector.d.ts +140 -0
- package/dist/core/StructuralChangeDetector.js +363 -0
- package/dist/core/Validator.d.ts +127 -0
- package/dist/core/Validator.js +279 -0
- package/dist/core/errors.d.ts +153 -0
- package/dist/core/errors.js +202 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +64 -0
- package/dist/core/types.d.ts +263 -0
- package/dist/core/types.js +48 -0
- package/dist/hooks/context.d.ts +147 -0
- package/dist/hooks/context.js +334 -0
- package/dist/hooks/useContext.d.ts +113 -0
- package/dist/hooks/useContext.js +169 -0
- package/dist/hooks/useEffect.d.ts +105 -0
- package/dist/hooks/useEffect.js +540 -0
- package/dist/hooks/useInstance.d.ts +139 -0
- package/dist/hooks/useInstance.js +441 -0
- package/dist/hooks/useState.d.ts +120 -0
- package/dist/hooks/useState.js +298 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +70 -0
- package/dist/jsx.d.ts +64 -0
- package/dist/jsx.js +76 -0
- package/dist/providers/DummyBackendProvider.d.ts +193 -0
- package/dist/providers/DummyBackendProvider.js +189 -0
- package/dist/providers/DummyCloudProvider.d.ts +128 -0
- package/dist/providers/DummyCloudProvider.js +157 -0
- package/dist/providers/IBackendProvider.d.ts +177 -0
- package/dist/providers/IBackendProvider.js +31 -0
- package/dist/providers/ICloudProvider.d.ts +146 -0
- package/dist/providers/ICloudProvider.js +31 -0
- package/dist/providers/index.d.ts +31 -0
- package/dist/providers/index.js +31 -0
- package/dist/test-event-callbacks.d.ts +0 -0
- package/dist/test-event-callbacks.js +1 -0
- package/dist/utils/Logger.d.ts +144 -0
- package/dist/utils/Logger.js +220 -0
- package/dist/utils/Output.d.ts +161 -0
- package/dist/utils/Output.js +401 -0
- package/dist/utils/deepEqual.d.ts +71 -0
- package/dist/utils/deepEqual.js +276 -0
- package/dist/utils/naming.d.ts +241 -0
- package/dist/utils/naming.js +376 -0
- package/package.json +87 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
*
|
|
10
|
+
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
*
|
|
14
|
+
|
|
15
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
|
|
23
|
+
* limitations under the License.
|
|
24
|
+
|
|
25
|
+
*
|
|
26
|
+
|
|
27
|
+
* Copyright 2025 Daniel Coutinho Ribeiro
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
import { FiberNode, ReRenderReason, CReactEvents } from './types';
|
|
31
|
+
/**
|
|
32
|
+
* RenderScheduler - Manages batched re-rendering of components
|
|
33
|
+
*
|
|
34
|
+
* Provides:
|
|
35
|
+
* - Batched re-rendering to avoid excessive renders
|
|
36
|
+
* - Circular dependency detection
|
|
37
|
+
* - Performance safeguards and rate limiting
|
|
38
|
+
* - Event hook integration for tooling
|
|
39
|
+
*/
|
|
40
|
+
export declare class RenderScheduler {
|
|
41
|
+
private pendingReRenders;
|
|
42
|
+
private batchTimeout;
|
|
43
|
+
private eventHooks?;
|
|
44
|
+
private renderChain;
|
|
45
|
+
private maxRenderDepth;
|
|
46
|
+
private rateLimitMap;
|
|
47
|
+
private maxRendersPerSecond;
|
|
48
|
+
private errorRecoveryManager;
|
|
49
|
+
private failedRenders;
|
|
50
|
+
private maxRetries;
|
|
51
|
+
private backoffMultiplier;
|
|
52
|
+
private baseBackoffMs;
|
|
53
|
+
constructor(eventHooks?: CReactEvents);
|
|
54
|
+
/**
|
|
55
|
+
* Schedule a component for re-rendering with mark-and-sweep model
|
|
56
|
+
* Uses batching to avoid excessive renders and prevents duplicate scheduling
|
|
57
|
+
*/
|
|
58
|
+
schedule(fiber: FiberNode, reason: ReRenderReason, contextId?: symbol): void;
|
|
59
|
+
/**
|
|
60
|
+
* Schedule the next batch of re-renders
|
|
61
|
+
* Uses setTimeout to batch multiple changes in the same tick
|
|
62
|
+
*/
|
|
63
|
+
private scheduleBatch;
|
|
64
|
+
/**
|
|
65
|
+
* Execute all pending re-renders in a batch with error handling and recovery
|
|
66
|
+
*/
|
|
67
|
+
private flushBatch;
|
|
68
|
+
/**
|
|
69
|
+
* Execute re-renders for the given fibers
|
|
70
|
+
* Integrates with the Renderer to perform actual re-rendering
|
|
71
|
+
*/
|
|
72
|
+
private executeReRenders;
|
|
73
|
+
/**
|
|
74
|
+
* Detect circular dependencies in the render chain
|
|
75
|
+
*/
|
|
76
|
+
private detectCircularDependencies;
|
|
77
|
+
/**
|
|
78
|
+
* Check if a fiber has circular dependencies using DFS
|
|
79
|
+
*/
|
|
80
|
+
private hasCycle;
|
|
81
|
+
/**
|
|
82
|
+
* Sort fibers by dependency order (dependencies before dependents)
|
|
83
|
+
*/
|
|
84
|
+
private sortByDependencies;
|
|
85
|
+
/**
|
|
86
|
+
* Check if a fiber is rate limited
|
|
87
|
+
*/
|
|
88
|
+
private isRateLimited;
|
|
89
|
+
/**
|
|
90
|
+
* Get pending re-renders (for testing/debugging)
|
|
91
|
+
*/
|
|
92
|
+
getPendingReRenders(): Set<FiberNode>;
|
|
93
|
+
/**
|
|
94
|
+
* Clear all pending re-renders (for testing/cleanup)
|
|
95
|
+
*/
|
|
96
|
+
clearPending(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Set performance limits (for testing/configuration)
|
|
99
|
+
*/
|
|
100
|
+
setLimits(maxRenderDepth: number, maxRendersPerSecond: number): void;
|
|
101
|
+
/**
|
|
102
|
+
* Create snapshots of fiber states for rollback
|
|
103
|
+
*/
|
|
104
|
+
private createFiberSnapshots;
|
|
105
|
+
/**
|
|
106
|
+
* Filter out fibers that have failed too many times
|
|
107
|
+
*/
|
|
108
|
+
private filterEligibleFibers;
|
|
109
|
+
/**
|
|
110
|
+
* Execute re-render for a single fiber
|
|
111
|
+
*/
|
|
112
|
+
private executeReRender;
|
|
113
|
+
/**
|
|
114
|
+
* Record a failure for a fiber
|
|
115
|
+
*/
|
|
116
|
+
private recordFailure;
|
|
117
|
+
/**
|
|
118
|
+
* Clear failure record for a fiber (after successful render)
|
|
119
|
+
*/
|
|
120
|
+
private clearFailureRecord;
|
|
121
|
+
/**
|
|
122
|
+
* Handle partial failures with graceful degradation
|
|
123
|
+
*/
|
|
124
|
+
private handlePartialFailures;
|
|
125
|
+
/**
|
|
126
|
+
* Determine if successful renders should be rolled back due to dependency failures
|
|
127
|
+
*/
|
|
128
|
+
private shouldRollbackSuccessfulRenders;
|
|
129
|
+
/**
|
|
130
|
+
* Rollback specific fibers to their snapshots
|
|
131
|
+
*/
|
|
132
|
+
private rollbackFibers;
|
|
133
|
+
/**
|
|
134
|
+
* Rollback all changes (critical error recovery)
|
|
135
|
+
*/
|
|
136
|
+
private rollbackAllChanges;
|
|
137
|
+
/**
|
|
138
|
+
* Schedule retries for failed renders with exponential backoff
|
|
139
|
+
*/
|
|
140
|
+
private scheduleRetries;
|
|
141
|
+
/**
|
|
142
|
+
* Get failure statistics (for monitoring/debugging)
|
|
143
|
+
*/
|
|
144
|
+
getFailureStats(): {
|
|
145
|
+
totalFailures: number;
|
|
146
|
+
fibersWithFailures: number;
|
|
147
|
+
averageFailureCount: number;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Clear all failure records (for testing/cleanup)
|
|
151
|
+
*/
|
|
152
|
+
clearFailureRecords(): void;
|
|
153
|
+
}
|
|
@@ -0,0 +1,519 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.RenderScheduler = void 0;
|
|
33
|
+
const ErrorRecoveryManager_1 = require("./ErrorRecoveryManager");
|
|
34
|
+
const errors_1 = require("./errors");
|
|
35
|
+
const CReact_1 = require("./CReact");
|
|
36
|
+
const Logger_1 = require("../utils/Logger");
|
|
37
|
+
const logger = Logger_1.LoggerFactory.getLogger('renderer');
|
|
38
|
+
/**
|
|
39
|
+
* RenderScheduler - Manages batched re-rendering of components
|
|
40
|
+
*
|
|
41
|
+
* Provides:
|
|
42
|
+
* - Batched re-rendering to avoid excessive renders
|
|
43
|
+
* - Circular dependency detection
|
|
44
|
+
* - Performance safeguards and rate limiting
|
|
45
|
+
* - Event hook integration for tooling
|
|
46
|
+
*/
|
|
47
|
+
class RenderScheduler {
|
|
48
|
+
constructor(eventHooks) {
|
|
49
|
+
this.pendingReRenders = new Set();
|
|
50
|
+
this.batchTimeout = null;
|
|
51
|
+
this.renderChain = [];
|
|
52
|
+
this.maxRenderDepth = 50; // Prevent infinite loops
|
|
53
|
+
this.rateLimitMap = new Map();
|
|
54
|
+
this.maxRendersPerSecond = 10;
|
|
55
|
+
// Error handling and recovery
|
|
56
|
+
this.failedRenders = new Map();
|
|
57
|
+
this.maxRetries = 3;
|
|
58
|
+
this.backoffMultiplier = 2;
|
|
59
|
+
this.baseBackoffMs = 100;
|
|
60
|
+
this.eventHooks = eventHooks;
|
|
61
|
+
this.errorRecoveryManager = new ErrorRecoveryManager_1.ErrorRecoveryManager(eventHooks);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Schedule a component for re-rendering with mark-and-sweep model
|
|
65
|
+
* Uses batching to avoid excessive renders and prevents duplicate scheduling
|
|
66
|
+
*/
|
|
67
|
+
schedule(fiber, reason, contextId) {
|
|
68
|
+
// Rate limiting check
|
|
69
|
+
if (this.isRateLimited(fiber)) {
|
|
70
|
+
logger.warn(`Rate limiting re-render for component at path: ${fiber.path.join('.')}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Initialize reactive state if needed
|
|
74
|
+
if (!fiber.reactiveState) {
|
|
75
|
+
fiber.reactiveState = {
|
|
76
|
+
renderCount: 0,
|
|
77
|
+
isDirty: false,
|
|
78
|
+
updatePending: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Mark-and-sweep: avoid duplicate scheduling
|
|
82
|
+
if (fiber.reactiveState.updatePending) {
|
|
83
|
+
// Update context tracking for debugging
|
|
84
|
+
if (contextId && reason === 'context-change') {
|
|
85
|
+
if (!fiber.reactiveState.pendingContexts) {
|
|
86
|
+
fiber.reactiveState.pendingContexts = new Set();
|
|
87
|
+
}
|
|
88
|
+
fiber.reactiveState.pendingContexts.add(contextId);
|
|
89
|
+
}
|
|
90
|
+
return; // Already scheduled, skip duplicate
|
|
91
|
+
}
|
|
92
|
+
// Mark as pending update
|
|
93
|
+
fiber.reactiveState.updatePending = true;
|
|
94
|
+
fiber.reactiveState.lastRenderReason = reason;
|
|
95
|
+
fiber.reactiveState.lastRenderTime = Date.now();
|
|
96
|
+
fiber.reactiveState.isDirty = true;
|
|
97
|
+
// Track context for debugging
|
|
98
|
+
if (contextId && reason === 'context-change') {
|
|
99
|
+
fiber.reactiveState.pendingContexts = new Set([contextId]);
|
|
100
|
+
}
|
|
101
|
+
// Emit telemetry hook
|
|
102
|
+
this.eventHooks?.onFiberReRenderScheduled?.(fiber, reason, contextId);
|
|
103
|
+
this.pendingReRenders.add(fiber);
|
|
104
|
+
this.scheduleBatch();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Schedule the next batch of re-renders
|
|
108
|
+
* Uses setTimeout to batch multiple changes in the same tick
|
|
109
|
+
*/
|
|
110
|
+
scheduleBatch() {
|
|
111
|
+
if (this.batchTimeout)
|
|
112
|
+
return;
|
|
113
|
+
this.batchTimeout = setTimeout(() => {
|
|
114
|
+
this.flushBatch();
|
|
115
|
+
}, 0); // Next tick batching
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Execute all pending re-renders in a batch with error handling and recovery
|
|
119
|
+
*/
|
|
120
|
+
async flushBatch() {
|
|
121
|
+
const fibers = Array.from(this.pendingReRenders);
|
|
122
|
+
this.pendingReRenders.clear();
|
|
123
|
+
this.batchTimeout = null;
|
|
124
|
+
if (fibers.length === 0)
|
|
125
|
+
return;
|
|
126
|
+
// Create snapshots for rollback
|
|
127
|
+
const fiberSnapshots = this.createFiberSnapshots(fibers);
|
|
128
|
+
const successfulRenders = [];
|
|
129
|
+
const failedRenders = [];
|
|
130
|
+
try {
|
|
131
|
+
// Detect circular dependencies
|
|
132
|
+
this.detectCircularDependencies(fibers);
|
|
133
|
+
// Filter out fibers that have failed too many times
|
|
134
|
+
const eligibleFibers = this.filterEligibleFibers(fibers);
|
|
135
|
+
if (eligibleFibers.length === 0) {
|
|
136
|
+
logger.warn('All fibers filtered out due to repeated failures');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Sort by dependency order (dependents after dependencies)
|
|
140
|
+
const sortedFibers = this.sortByDependencies(eligibleFibers);
|
|
141
|
+
// Execute re-renders with individual error isolation
|
|
142
|
+
for (const fiber of sortedFibers) {
|
|
143
|
+
try {
|
|
144
|
+
// Emit render start event
|
|
145
|
+
this.eventHooks?.onRenderStart(fiber);
|
|
146
|
+
this.renderChain.push(fiber);
|
|
147
|
+
// Execute single fiber re-render
|
|
148
|
+
await this.executeReRender(fiber);
|
|
149
|
+
// Mark as successful
|
|
150
|
+
successfulRenders.push(fiber);
|
|
151
|
+
this.clearFailureRecord(fiber);
|
|
152
|
+
// Update reactive state
|
|
153
|
+
if (fiber.reactiveState) {
|
|
154
|
+
fiber.reactiveState.renderCount++;
|
|
155
|
+
fiber.reactiveState.isDirty = false;
|
|
156
|
+
}
|
|
157
|
+
// Emit render complete event
|
|
158
|
+
this.eventHooks?.onRenderComplete(fiber);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// Record failure
|
|
162
|
+
this.recordFailure(fiber, error);
|
|
163
|
+
failedRenders.push({ fiber, error: error });
|
|
164
|
+
// Emit error event
|
|
165
|
+
this.eventHooks?.onError(error, fiber);
|
|
166
|
+
// Continue with other fibers (error isolation)
|
|
167
|
+
logger.warn(`Fiber render failed: ${fiber.path.join('.')}, continuing with others`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Handle partial failures with graceful degradation
|
|
171
|
+
if (failedRenders.length > 0) {
|
|
172
|
+
await this.handlePartialFailures(failedRenders, successfulRenders, fiberSnapshots);
|
|
173
|
+
}
|
|
174
|
+
// Clear render chain
|
|
175
|
+
this.renderChain = [];
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
// Critical error - rollback all changes
|
|
179
|
+
logger.error('Critical error during batch render, rolling back all changes');
|
|
180
|
+
await this.rollbackAllChanges(fiberSnapshots);
|
|
181
|
+
this.eventHooks?.onError(error);
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Execute re-renders for the given fibers
|
|
187
|
+
* Integrates with the Renderer to perform actual re-rendering
|
|
188
|
+
*/
|
|
189
|
+
async executeReRenders(fibers) {
|
|
190
|
+
if (fibers.length === 0)
|
|
191
|
+
return;
|
|
192
|
+
try {
|
|
193
|
+
// Get the CReact instance to access the renderer
|
|
194
|
+
const creact = (0, CReact_1.getCReactInstance)();
|
|
195
|
+
if (!creact) {
|
|
196
|
+
logger.warn('No CReact instance available for re-rendering');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Use the CReact rerender method to perform actual re-rendering
|
|
200
|
+
await creact.rerender('default', fibers);
|
|
201
|
+
// Update render count for successfully rendered fibers
|
|
202
|
+
for (const fiber of fibers) {
|
|
203
|
+
if (fiber.reactiveState) {
|
|
204
|
+
fiber.reactiveState.renderCount++;
|
|
205
|
+
fiber.reactiveState.isDirty = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
logger.error('Failed to execute re-renders:', error);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Detect circular dependencies in the render chain
|
|
216
|
+
*/
|
|
217
|
+
detectCircularDependencies(fibers) {
|
|
218
|
+
const visited = new Set();
|
|
219
|
+
const recursionStack = new Set();
|
|
220
|
+
for (const fiber of fibers) {
|
|
221
|
+
if (this.hasCycle(fiber, visited, recursionStack)) {
|
|
222
|
+
const cyclePath = Array.from(recursionStack).map((f) => f.path.join('.'));
|
|
223
|
+
throw new errors_1.CircularDependencyError(`Circular dependency detected in re-render chain: ${cyclePath.join(' -> ')}`, cyclePath);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Check render chain depth
|
|
227
|
+
if (this.renderChain.length > this.maxRenderDepth) {
|
|
228
|
+
const chainPath = this.renderChain.map((f) => f.path.join('.')).join(' -> ');
|
|
229
|
+
throw new Error(`Maximum render depth exceeded (${this.maxRenderDepth}): ${chainPath}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Check if a fiber has circular dependencies using DFS
|
|
234
|
+
*/
|
|
235
|
+
hasCycle(fiber, visited, recursionStack) {
|
|
236
|
+
if (recursionStack.has(fiber)) {
|
|
237
|
+
return true; // Back edge found - cycle detected
|
|
238
|
+
}
|
|
239
|
+
if (visited.has(fiber)) {
|
|
240
|
+
return false; // Already processed
|
|
241
|
+
}
|
|
242
|
+
visited.add(fiber);
|
|
243
|
+
recursionStack.add(fiber);
|
|
244
|
+
// Check dependencies
|
|
245
|
+
if (fiber.dependencies) {
|
|
246
|
+
for (const dependency of Array.from(fiber.dependencies)) {
|
|
247
|
+
if (this.hasCycle(dependency, visited, recursionStack)) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
recursionStack.delete(fiber);
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Sort fibers by dependency order (dependencies before dependents)
|
|
257
|
+
*/
|
|
258
|
+
sortByDependencies(fibers) {
|
|
259
|
+
const sorted = [];
|
|
260
|
+
const visited = new Set();
|
|
261
|
+
const temp = new Set();
|
|
262
|
+
const visit = (fiber) => {
|
|
263
|
+
if (temp.has(fiber)) {
|
|
264
|
+
throw new Error(`Circular dependency detected during sort: ${fiber.path.join('.')}`);
|
|
265
|
+
}
|
|
266
|
+
if (visited.has(fiber)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
temp.add(fiber);
|
|
270
|
+
// Visit dependencies first
|
|
271
|
+
if (fiber.dependencies) {
|
|
272
|
+
for (const dependency of Array.from(fiber.dependencies)) {
|
|
273
|
+
if (fibers.includes(dependency)) {
|
|
274
|
+
visit(dependency);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
temp.delete(fiber);
|
|
279
|
+
visited.add(fiber);
|
|
280
|
+
sorted.push(fiber);
|
|
281
|
+
};
|
|
282
|
+
for (const fiber of fibers) {
|
|
283
|
+
if (!visited.has(fiber)) {
|
|
284
|
+
visit(fiber);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return sorted;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if a fiber is rate limited
|
|
291
|
+
*/
|
|
292
|
+
isRateLimited(fiber) {
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
const lastRenderTime = this.rateLimitMap.get(fiber) || 0;
|
|
295
|
+
const timeSinceLastRender = now - lastRenderTime;
|
|
296
|
+
const minInterval = 1000 / this.maxRendersPerSecond; // ms between renders
|
|
297
|
+
if (timeSinceLastRender < minInterval) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
this.rateLimitMap.set(fiber, now);
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get pending re-renders (for testing/debugging)
|
|
305
|
+
*/
|
|
306
|
+
getPendingReRenders() {
|
|
307
|
+
return new Set(this.pendingReRenders);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Clear all pending re-renders (for testing/cleanup)
|
|
311
|
+
*/
|
|
312
|
+
clearPending() {
|
|
313
|
+
if (this.batchTimeout) {
|
|
314
|
+
clearTimeout(this.batchTimeout);
|
|
315
|
+
this.batchTimeout = null;
|
|
316
|
+
}
|
|
317
|
+
this.pendingReRenders.clear();
|
|
318
|
+
this.renderChain = [];
|
|
319
|
+
// Clear updatePending flags from all fibers
|
|
320
|
+
Array.from(this.pendingReRenders).forEach((fiber) => {
|
|
321
|
+
if (fiber.reactiveState) {
|
|
322
|
+
fiber.reactiveState.updatePending = false;
|
|
323
|
+
fiber.reactiveState.pendingContexts?.clear();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Set performance limits (for testing/configuration)
|
|
329
|
+
*/
|
|
330
|
+
setLimits(maxRenderDepth, maxRendersPerSecond) {
|
|
331
|
+
this.maxRenderDepth = maxRenderDepth;
|
|
332
|
+
this.maxRendersPerSecond = maxRendersPerSecond;
|
|
333
|
+
}
|
|
334
|
+
// Error Handling and Recovery Methods
|
|
335
|
+
/**
|
|
336
|
+
* Create snapshots of fiber states for rollback
|
|
337
|
+
*/
|
|
338
|
+
createFiberSnapshots(fibers) {
|
|
339
|
+
const snapshots = new Map();
|
|
340
|
+
fibers.forEach((fiber) => {
|
|
341
|
+
snapshots.set(fiber, {
|
|
342
|
+
hooks: fiber.hooks ? [...fiber.hooks] : undefined,
|
|
343
|
+
state: fiber.state ? { ...fiber.state } : undefined,
|
|
344
|
+
reactiveState: fiber.reactiveState ? { ...fiber.reactiveState } : undefined,
|
|
345
|
+
cloudDOMNodes: fiber.cloudDOMNodes ? [...fiber.cloudDOMNodes] : undefined,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
return snapshots;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Filter out fibers that have failed too many times
|
|
352
|
+
*/
|
|
353
|
+
filterEligibleFibers(fibers) {
|
|
354
|
+
const now = Date.now();
|
|
355
|
+
return fibers.filter((fiber) => {
|
|
356
|
+
const failureRecord = this.failedRenders.get(fiber);
|
|
357
|
+
if (!failureRecord) {
|
|
358
|
+
return true; // Never failed, eligible
|
|
359
|
+
}
|
|
360
|
+
if (failureRecord.count >= this.maxRetries) {
|
|
361
|
+
// Check if enough time has passed for retry with backoff
|
|
362
|
+
const backoffTime = this.baseBackoffMs * Math.pow(this.backoffMultiplier, failureRecord.count - 1);
|
|
363
|
+
const timeSinceLastFailure = now - failureRecord.lastFailure;
|
|
364
|
+
if (timeSinceLastFailure < backoffTime) {
|
|
365
|
+
logger.warn(`Fiber ${fiber.path.join('.')} still in backoff period`);
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Execute re-render for a single fiber
|
|
374
|
+
*/
|
|
375
|
+
async executeReRender(fiber) {
|
|
376
|
+
try {
|
|
377
|
+
// Get the CReact instance to access the renderer
|
|
378
|
+
const creact = (0, CReact_1.getCReactInstance)();
|
|
379
|
+
if (!creact) {
|
|
380
|
+
logger.warn('No CReact instance available for re-rendering');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
// Note: Previously skipped re-renders during initial build, but now that
|
|
384
|
+
// CloudDOMBuilder properly handles re-render scenarios, we allow all re-renders
|
|
385
|
+
// Use selective re-rendering to avoid rebuilding the entire tree
|
|
386
|
+
const renderer = creact.renderer;
|
|
387
|
+
if (renderer && renderer.reRenderComponents) {
|
|
388
|
+
renderer.reRenderComponents([fiber], 'manual');
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
logger.warn('Renderer not available for selective re-rendering');
|
|
392
|
+
}
|
|
393
|
+
// Update render count
|
|
394
|
+
if (fiber.reactiveState) {
|
|
395
|
+
fiber.reactiveState.renderCount++;
|
|
396
|
+
fiber.reactiveState.isDirty = false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
logger.error('Failed to execute re-render for fiber:', fiber.path.join('.'), error);
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Record a failure for a fiber
|
|
406
|
+
*/
|
|
407
|
+
recordFailure(fiber, error) {
|
|
408
|
+
const existing = this.failedRenders.get(fiber);
|
|
409
|
+
if (existing) {
|
|
410
|
+
existing.count++;
|
|
411
|
+
existing.lastFailure = Date.now();
|
|
412
|
+
existing.error = error;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.failedRenders.set(fiber, {
|
|
416
|
+
count: 1,
|
|
417
|
+
lastFailure: Date.now(),
|
|
418
|
+
error,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Clear failure record for a fiber (after successful render)
|
|
424
|
+
*/
|
|
425
|
+
clearFailureRecord(fiber) {
|
|
426
|
+
this.failedRenders.delete(fiber);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Handle partial failures with graceful degradation
|
|
430
|
+
*/
|
|
431
|
+
async handlePartialFailures(failedRenders, successfulRenders, fiberSnapshots) {
|
|
432
|
+
logger.warn(`Partial failure: ${failedRenders.length} failed, ${successfulRenders.length} succeeded`);
|
|
433
|
+
// Check if we should rollback successful renders due to dependencies
|
|
434
|
+
const shouldRollback = this.shouldRollbackSuccessfulRenders(failedRenders, successfulRenders);
|
|
435
|
+
if (shouldRollback) {
|
|
436
|
+
logger.warn('Rolling back successful renders due to dependency failures');
|
|
437
|
+
await this.rollbackFibers(successfulRenders, fiberSnapshots);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
logger.info('Continuing with partial success (graceful degradation)');
|
|
441
|
+
}
|
|
442
|
+
// Schedule retry for failed renders with backoff
|
|
443
|
+
this.scheduleRetries(failedRenders);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Determine if successful renders should be rolled back due to dependency failures
|
|
447
|
+
*/
|
|
448
|
+
shouldRollbackSuccessfulRenders(failedRenders, successfulRenders) {
|
|
449
|
+
// Check if any successful render depends on a failed render
|
|
450
|
+
for (const successfulFiber of successfulRenders) {
|
|
451
|
+
if (successfulFiber.dependencies) {
|
|
452
|
+
const dependencyArray = Array.from(successfulFiber.dependencies);
|
|
453
|
+
for (const dependency of dependencyArray) {
|
|
454
|
+
if (failedRenders.some((failed) => failed.fiber === dependency)) {
|
|
455
|
+
return true; // Successful fiber depends on failed fiber
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Rollback specific fibers to their snapshots
|
|
464
|
+
*/
|
|
465
|
+
async rollbackFibers(fibers, snapshots) {
|
|
466
|
+
for (const fiber of fibers) {
|
|
467
|
+
const snapshot = snapshots.get(fiber);
|
|
468
|
+
if (snapshot) {
|
|
469
|
+
// Restore fiber state
|
|
470
|
+
fiber.hooks = snapshot.hooks ? [...snapshot.hooks] : undefined;
|
|
471
|
+
fiber.state = snapshot.state ? { ...snapshot.state } : undefined;
|
|
472
|
+
fiber.reactiveState = snapshot.reactiveState ? { ...snapshot.reactiveState } : undefined;
|
|
473
|
+
fiber.cloudDOMNodes = snapshot.cloudDOMNodes ? [...snapshot.cloudDOMNodes] : undefined;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Rollback all changes (critical error recovery)
|
|
479
|
+
*/
|
|
480
|
+
async rollbackAllChanges(snapshots) {
|
|
481
|
+
const fibers = Array.from(snapshots.keys());
|
|
482
|
+
await this.rollbackFibers(fibers, snapshots);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Schedule retries for failed renders with exponential backoff
|
|
486
|
+
*/
|
|
487
|
+
scheduleRetries(failedRenders) {
|
|
488
|
+
for (const { fiber } of failedRenders) {
|
|
489
|
+
const failureRecord = this.failedRenders.get(fiber);
|
|
490
|
+
if (failureRecord && failureRecord.count < this.maxRetries) {
|
|
491
|
+
const backoffTime = this.baseBackoffMs * Math.pow(this.backoffMultiplier, failureRecord.count);
|
|
492
|
+
setTimeout(() => {
|
|
493
|
+
logger.info(`Retrying failed render for ${fiber.path.join('.')}`);
|
|
494
|
+
this.schedule(fiber, 'manual'); // Retry with manual reason
|
|
495
|
+
}, backoffTime);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get failure statistics (for monitoring/debugging)
|
|
501
|
+
*/
|
|
502
|
+
getFailureStats() {
|
|
503
|
+
const totalFailures = Array.from(this.failedRenders.values()).reduce((sum, record) => sum + record.count, 0);
|
|
504
|
+
const fibersWithFailures = this.failedRenders.size;
|
|
505
|
+
const averageFailureCount = fibersWithFailures > 0 ? totalFailures / fibersWithFailures : 0;
|
|
506
|
+
return {
|
|
507
|
+
totalFailures,
|
|
508
|
+
fibersWithFailures,
|
|
509
|
+
averageFailureCount,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Clear all failure records (for testing/cleanup)
|
|
514
|
+
*/
|
|
515
|
+
clearFailureRecords() {
|
|
516
|
+
this.failedRenders.clear();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
exports.RenderScheduler = RenderScheduler;
|