@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,145 @@
|
|
|
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
|
+
import { CircularDependencyError } from './errors';
|
|
32
|
+
/**
|
|
33
|
+
* Recovery strategy for different types of errors
|
|
34
|
+
*/
|
|
35
|
+
export type RecoveryStrategy = 'rollback' | 'isolate' | 'retry' | 'skip' | 'abort';
|
|
36
|
+
/**
|
|
37
|
+
* Recovery action result
|
|
38
|
+
*/
|
|
39
|
+
export interface RecoveryResult {
|
|
40
|
+
success: boolean;
|
|
41
|
+
strategy: RecoveryStrategy;
|
|
42
|
+
message: string;
|
|
43
|
+
recoveredFibers?: FiberNode[];
|
|
44
|
+
failedFibers?: FiberNode[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* ErrorRecoveryManager - Handles error recovery and rollback semantics
|
|
48
|
+
*
|
|
49
|
+
* Provides:
|
|
50
|
+
* - Rollback semantics for failed re-renders
|
|
51
|
+
* - Error isolation for component failures
|
|
52
|
+
* - Graceful degradation for partial failures
|
|
53
|
+
* - Recovery strategies based on error type
|
|
54
|
+
*/
|
|
55
|
+
export declare class ErrorRecoveryManager {
|
|
56
|
+
private componentSnapshots;
|
|
57
|
+
private contextSnapshots;
|
|
58
|
+
private eventHooks?;
|
|
59
|
+
private maxRetries;
|
|
60
|
+
private retryDelay;
|
|
61
|
+
constructor(eventHooks?: CReactEvents);
|
|
62
|
+
/**
|
|
63
|
+
* Create a snapshot of component state before risky operations
|
|
64
|
+
*/
|
|
65
|
+
createComponentSnapshot(fiber: FiberNode): void;
|
|
66
|
+
/**
|
|
67
|
+
* Create snapshots for multiple components
|
|
68
|
+
*/
|
|
69
|
+
createComponentSnapshots(fibers: FiberNode[]): void;
|
|
70
|
+
/**
|
|
71
|
+
* Create a snapshot of context value before changes
|
|
72
|
+
*/
|
|
73
|
+
createContextSnapshot(contextId: symbol, value: any): void;
|
|
74
|
+
/**
|
|
75
|
+
* Rollback a component to its previous snapshot
|
|
76
|
+
*/
|
|
77
|
+
rollbackComponent(fiber: FiberNode): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Rollback multiple components to their snapshots
|
|
80
|
+
*/
|
|
81
|
+
rollbackComponents(fibers: FiberNode[]): RecoveryResult;
|
|
82
|
+
/**
|
|
83
|
+
* Rollback context value to its previous snapshot
|
|
84
|
+
*/
|
|
85
|
+
rollbackContextValue(contextId: symbol): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Handle re-render errors with appropriate recovery strategy
|
|
88
|
+
*/
|
|
89
|
+
handleReRenderError(error: Error, affectedFibers: FiberNode[], reason: ReRenderReason): Promise<RecoveryResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Handle state update errors with isolation
|
|
92
|
+
*/
|
|
93
|
+
handleStateUpdateError(error: Error, fiber: FiberNode, hookIndex: number): RecoveryResult;
|
|
94
|
+
/**
|
|
95
|
+
* Handle context propagation errors
|
|
96
|
+
*/
|
|
97
|
+
handleContextPropagationError(error: Error, contextId: symbol, affectedFibers: FiberNode[]): Promise<RecoveryResult>;
|
|
98
|
+
/**
|
|
99
|
+
* Handle circular dependency errors
|
|
100
|
+
*/
|
|
101
|
+
handleCircularDependencyError(error: CircularDependencyError, affectedFibers: FiberNode[]): RecoveryResult;
|
|
102
|
+
/**
|
|
103
|
+
* Determine the appropriate recovery strategy based on error type and context
|
|
104
|
+
*/
|
|
105
|
+
private determineRecoveryStrategy;
|
|
106
|
+
/**
|
|
107
|
+
* Isolate failed components and continue with successful ones
|
|
108
|
+
*/
|
|
109
|
+
private isolateFailedComponents;
|
|
110
|
+
/**
|
|
111
|
+
* Retry operation with exponential backoff
|
|
112
|
+
*/
|
|
113
|
+
private retryOperation;
|
|
114
|
+
/**
|
|
115
|
+
* Skip failed components and continue with others
|
|
116
|
+
*/
|
|
117
|
+
private skipFailedComponents;
|
|
118
|
+
/**
|
|
119
|
+
* Find the fiber that's causing a circular dependency
|
|
120
|
+
*/
|
|
121
|
+
private findCycleCausingFiber;
|
|
122
|
+
/**
|
|
123
|
+
* Deep clone an object for snapshots
|
|
124
|
+
*/
|
|
125
|
+
private deepClone;
|
|
126
|
+
/**
|
|
127
|
+
* Clean up old snapshots to prevent memory leaks
|
|
128
|
+
*/
|
|
129
|
+
cleanupOldSnapshots(maxAge?: number): void;
|
|
130
|
+
/**
|
|
131
|
+
* Get recovery statistics
|
|
132
|
+
*/
|
|
133
|
+
getRecoveryStats(): {
|
|
134
|
+
activeSnapshots: number;
|
|
135
|
+
contextSnapshots: number;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Clear all snapshots (for testing/cleanup)
|
|
139
|
+
*/
|
|
140
|
+
clearAllSnapshots(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Set retry configuration
|
|
143
|
+
*/
|
|
144
|
+
setRetryConfig(maxRetries: number, retryDelay: number): void;
|
|
145
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
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.ErrorRecoveryManager = void 0;
|
|
33
|
+
const errors_1 = require("./errors");
|
|
34
|
+
const Logger_1 = require("../utils/Logger");
|
|
35
|
+
const logger = Logger_1.LoggerFactory.getLogger('runtime');
|
|
36
|
+
/**
|
|
37
|
+
* ErrorRecoveryManager - Handles error recovery and rollback semantics
|
|
38
|
+
*
|
|
39
|
+
* Provides:
|
|
40
|
+
* - Rollback semantics for failed re-renders
|
|
41
|
+
* - Error isolation for component failures
|
|
42
|
+
* - Graceful degradation for partial failures
|
|
43
|
+
* - Recovery strategies based on error type
|
|
44
|
+
*/
|
|
45
|
+
class ErrorRecoveryManager {
|
|
46
|
+
constructor(eventHooks) {
|
|
47
|
+
this.componentSnapshots = new WeakMap();
|
|
48
|
+
this.contextSnapshots = new Map();
|
|
49
|
+
this.maxRetries = 3;
|
|
50
|
+
this.retryDelay = 100; // ms
|
|
51
|
+
this.eventHooks = eventHooks;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a snapshot of component state before risky operations
|
|
55
|
+
*/
|
|
56
|
+
createComponentSnapshot(fiber) {
|
|
57
|
+
const snapshot = {
|
|
58
|
+
fiber,
|
|
59
|
+
hooks: fiber.hooks ? [...fiber.hooks] : [],
|
|
60
|
+
reactiveState: fiber.reactiveState ? { ...fiber.reactiveState } : undefined,
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
};
|
|
63
|
+
this.componentSnapshots.set(fiber, snapshot);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create snapshots for multiple components
|
|
67
|
+
*/
|
|
68
|
+
createComponentSnapshots(fibers) {
|
|
69
|
+
fibers.forEach((fiber) => this.createComponentSnapshot(fiber));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a snapshot of context value before changes
|
|
73
|
+
*/
|
|
74
|
+
createContextSnapshot(contextId, value) {
|
|
75
|
+
this.contextSnapshots.set(contextId, {
|
|
76
|
+
value: this.deepClone(value),
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rollback a component to its previous snapshot
|
|
82
|
+
*/
|
|
83
|
+
rollbackComponent(fiber) {
|
|
84
|
+
const snapshot = this.componentSnapshots.get(fiber);
|
|
85
|
+
if (!snapshot) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
// Restore hooks
|
|
90
|
+
if (fiber.hooks && snapshot.hooks) {
|
|
91
|
+
fiber.hooks.splice(0, fiber.hooks.length, ...snapshot.hooks);
|
|
92
|
+
}
|
|
93
|
+
// Restore reactive state
|
|
94
|
+
if (fiber.reactiveState && snapshot.reactiveState) {
|
|
95
|
+
Object.assign(fiber.reactiveState, snapshot.reactiveState);
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.warn(`Failed to rollback component ${fiber.path.join('.')}:`, error);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Rollback multiple components to their snapshots
|
|
106
|
+
*/
|
|
107
|
+
rollbackComponents(fibers) {
|
|
108
|
+
const recoveredFibers = [];
|
|
109
|
+
const failedFibers = [];
|
|
110
|
+
for (const fiber of fibers) {
|
|
111
|
+
if (this.rollbackComponent(fiber)) {
|
|
112
|
+
recoveredFibers.push(fiber);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
failedFibers.push(fiber);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
success: failedFibers.length === 0,
|
|
120
|
+
strategy: 'rollback',
|
|
121
|
+
message: `Rolled back ${recoveredFibers.length}/${fibers.length} components`,
|
|
122
|
+
recoveredFibers,
|
|
123
|
+
failedFibers,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Rollback context value to its previous snapshot
|
|
128
|
+
*/
|
|
129
|
+
rollbackContextValue(contextId) {
|
|
130
|
+
const snapshot = this.contextSnapshots.get(contextId);
|
|
131
|
+
if (!snapshot) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
// The actual context value rollback would be handled by ContextDependencyTracker
|
|
136
|
+
// This method just validates that we have a snapshot to rollback to
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
logger.warn(`Failed to rollback context ${String(contextId)}:`, error);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Handle re-render errors with appropriate recovery strategy
|
|
146
|
+
*/
|
|
147
|
+
async handleReRenderError(error, affectedFibers, reason) {
|
|
148
|
+
// Determine recovery strategy based on error type and reason
|
|
149
|
+
const strategy = this.determineRecoveryStrategy(error, reason);
|
|
150
|
+
switch (strategy) {
|
|
151
|
+
case 'rollback':
|
|
152
|
+
return this.rollbackComponents(affectedFibers);
|
|
153
|
+
case 'isolate':
|
|
154
|
+
return this.isolateFailedComponents(error, affectedFibers);
|
|
155
|
+
case 'retry':
|
|
156
|
+
return await this.retryOperation(error, affectedFibers, reason);
|
|
157
|
+
case 'skip':
|
|
158
|
+
return this.skipFailedComponents(affectedFibers);
|
|
159
|
+
case 'abort':
|
|
160
|
+
default:
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
strategy: 'abort',
|
|
164
|
+
message: `Aborting due to unrecoverable error: ${error.message}`,
|
|
165
|
+
failedFibers: affectedFibers,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle state update errors with isolation
|
|
171
|
+
*/
|
|
172
|
+
handleStateUpdateError(error, fiber, hookIndex) {
|
|
173
|
+
try {
|
|
174
|
+
// Try to rollback the specific hook
|
|
175
|
+
const snapshot = this.componentSnapshots.get(fiber);
|
|
176
|
+
if (snapshot && snapshot.hooks[hookIndex] !== undefined) {
|
|
177
|
+
if (fiber.hooks) {
|
|
178
|
+
fiber.hooks[hookIndex] = snapshot.hooks[hookIndex];
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
success: true,
|
|
182
|
+
strategy: 'rollback',
|
|
183
|
+
message: `Rolled back state hook ${hookIndex} for component ${fiber.path.join('.')}`,
|
|
184
|
+
recoveredFibers: [fiber],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// If no snapshot, isolate the component
|
|
188
|
+
return this.isolateFailedComponents(error, [fiber]);
|
|
189
|
+
}
|
|
190
|
+
catch (recoveryError) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
strategy: 'abort',
|
|
194
|
+
message: `Failed to recover from state update error: ${recoveryError}`,
|
|
195
|
+
failedFibers: [fiber],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Handle context propagation errors
|
|
201
|
+
*/
|
|
202
|
+
async handleContextPropagationError(error, contextId, affectedFibers) {
|
|
203
|
+
try {
|
|
204
|
+
// Try to rollback context value
|
|
205
|
+
const contextRollback = this.rollbackContextValue(contextId);
|
|
206
|
+
if (contextRollback) {
|
|
207
|
+
// Rollback affected components
|
|
208
|
+
const componentRollback = this.rollbackComponents(affectedFibers);
|
|
209
|
+
return {
|
|
210
|
+
success: componentRollback.success,
|
|
211
|
+
strategy: 'rollback',
|
|
212
|
+
message: `Rolled back context and ${componentRollback.recoveredFibers?.length || 0} components`,
|
|
213
|
+
recoveredFibers: componentRollback.recoveredFibers,
|
|
214
|
+
failedFibers: componentRollback.failedFibers,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// If context rollback fails, isolate affected components
|
|
218
|
+
return this.isolateFailedComponents(error, affectedFibers);
|
|
219
|
+
}
|
|
220
|
+
catch (recoveryError) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
strategy: 'abort',
|
|
224
|
+
message: `Failed to recover from context propagation error: ${recoveryError}`,
|
|
225
|
+
failedFibers: affectedFibers,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Handle circular dependency errors
|
|
231
|
+
*/
|
|
232
|
+
handleCircularDependencyError(error, affectedFibers) {
|
|
233
|
+
try {
|
|
234
|
+
// For circular dependencies, we need to break the cycle
|
|
235
|
+
// Find the fiber that's causing the cycle and isolate it
|
|
236
|
+
const cycleFiber = this.findCycleCausingFiber(error.cyclePath, affectedFibers);
|
|
237
|
+
if (cycleFiber) {
|
|
238
|
+
// Isolate the cycle-causing fiber
|
|
239
|
+
const isolationResult = this.isolateFailedComponents(error, [cycleFiber]);
|
|
240
|
+
// Continue with remaining fibers
|
|
241
|
+
const remainingFibers = affectedFibers.filter((f) => f !== cycleFiber);
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
strategy: 'isolate',
|
|
245
|
+
message: `Isolated cycle-causing component ${cycleFiber.path.join('.')}`,
|
|
246
|
+
recoveredFibers: remainingFibers,
|
|
247
|
+
failedFibers: [cycleFiber],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// If we can't identify the cycle cause, rollback all
|
|
251
|
+
return this.rollbackComponents(affectedFibers);
|
|
252
|
+
}
|
|
253
|
+
catch (recoveryError) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
strategy: 'abort',
|
|
257
|
+
message: `Failed to recover from circular dependency: ${recoveryError}`,
|
|
258
|
+
failedFibers: affectedFibers,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Determine the appropriate recovery strategy based on error type and context
|
|
264
|
+
*/
|
|
265
|
+
determineRecoveryStrategy(error, reason) {
|
|
266
|
+
// Circular dependency errors should be isolated
|
|
267
|
+
if (error instanceof errors_1.CircularDependencyError) {
|
|
268
|
+
return 'isolate';
|
|
269
|
+
}
|
|
270
|
+
// State update errors can usually be rolled back
|
|
271
|
+
if (error instanceof errors_1.StateUpdateError) {
|
|
272
|
+
return 'rollback';
|
|
273
|
+
}
|
|
274
|
+
// Context propagation errors should be rolled back
|
|
275
|
+
if (error instanceof errors_1.ContextPropagationError) {
|
|
276
|
+
return 'rollback';
|
|
277
|
+
}
|
|
278
|
+
// For manual re-renders, we can retry
|
|
279
|
+
if (reason === 'manual') {
|
|
280
|
+
return 'retry';
|
|
281
|
+
}
|
|
282
|
+
// For output updates, we can skip and continue
|
|
283
|
+
if (reason === 'output-update') {
|
|
284
|
+
return 'skip';
|
|
285
|
+
}
|
|
286
|
+
// For hot reload, we can retry
|
|
287
|
+
if (reason === 'hot-reload') {
|
|
288
|
+
return 'retry';
|
|
289
|
+
}
|
|
290
|
+
// Default to rollback for other cases
|
|
291
|
+
return 'rollback';
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Isolate failed components and continue with successful ones
|
|
295
|
+
*/
|
|
296
|
+
isolateFailedComponents(error, failedFibers) {
|
|
297
|
+
// Mark failed components as isolated
|
|
298
|
+
failedFibers.forEach((fiber) => {
|
|
299
|
+
if (fiber.reactiveState) {
|
|
300
|
+
fiber.reactiveState.isDirty = false;
|
|
301
|
+
fiber.reactiveState.updatePending = false;
|
|
302
|
+
// Add isolation marker
|
|
303
|
+
fiber.reactiveState.isolated = true;
|
|
304
|
+
fiber.reactiveState.isolationReason = error.message;
|
|
305
|
+
fiber.reactiveState.isolationTime = Date.now();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
this.eventHooks?.onError(error, failedFibers[0]);
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
strategy: 'isolate',
|
|
312
|
+
message: `Isolated ${failedFibers.length} failed components`,
|
|
313
|
+
failedFibers,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Retry operation with exponential backoff
|
|
318
|
+
*/
|
|
319
|
+
async retryOperation(error, fibers, reason, attempt = 1) {
|
|
320
|
+
if (attempt > this.maxRetries) {
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
strategy: 'retry',
|
|
324
|
+
message: `Max retries (${this.maxRetries}) exceeded`,
|
|
325
|
+
failedFibers: fibers,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// Wait with exponential backoff
|
|
329
|
+
const delay = this.retryDelay * Math.pow(2, attempt - 1);
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
331
|
+
try {
|
|
332
|
+
// The actual retry would be handled by the calling code
|
|
333
|
+
// This method just provides the retry logic framework
|
|
334
|
+
return {
|
|
335
|
+
success: true,
|
|
336
|
+
strategy: 'retry',
|
|
337
|
+
message: `Retry attempt ${attempt} scheduled`,
|
|
338
|
+
recoveredFibers: fibers,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
catch (retryError) {
|
|
342
|
+
// Recursive retry
|
|
343
|
+
return this.retryOperation(error, fibers, reason, attempt + 1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Skip failed components and continue with others
|
|
348
|
+
*/
|
|
349
|
+
skipFailedComponents(fibers) {
|
|
350
|
+
// Mark components as skipped
|
|
351
|
+
fibers.forEach((fiber) => {
|
|
352
|
+
if (fiber.reactiveState) {
|
|
353
|
+
fiber.reactiveState.isDirty = false;
|
|
354
|
+
fiber.reactiveState.updatePending = false;
|
|
355
|
+
// Add skip marker
|
|
356
|
+
fiber.reactiveState.skipped = true;
|
|
357
|
+
fiber.reactiveState.skipTime = Date.now();
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
success: true,
|
|
362
|
+
strategy: 'skip',
|
|
363
|
+
message: `Skipped ${fibers.length} components`,
|
|
364
|
+
recoveredFibers: [],
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Find the fiber that's causing a circular dependency
|
|
369
|
+
*/
|
|
370
|
+
findCycleCausingFiber(cyclePath, fibers) {
|
|
371
|
+
// Look for a fiber whose path matches part of the cycle path
|
|
372
|
+
for (const fiber of fibers) {
|
|
373
|
+
const fiberPathStr = fiber.path.join('.');
|
|
374
|
+
if (cyclePath.includes(fiberPathStr)) {
|
|
375
|
+
return fiber;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Deep clone an object for snapshots
|
|
382
|
+
*/
|
|
383
|
+
deepClone(obj) {
|
|
384
|
+
if (obj === null || typeof obj !== 'object') {
|
|
385
|
+
return obj;
|
|
386
|
+
}
|
|
387
|
+
if (obj instanceof Date) {
|
|
388
|
+
return new Date(obj.getTime());
|
|
389
|
+
}
|
|
390
|
+
if (obj instanceof Array) {
|
|
391
|
+
return obj.map((item) => this.deepClone(item));
|
|
392
|
+
}
|
|
393
|
+
if (typeof obj === 'object') {
|
|
394
|
+
const cloned = {};
|
|
395
|
+
for (const key in obj) {
|
|
396
|
+
if (obj.hasOwnProperty(key)) {
|
|
397
|
+
cloned[key] = this.deepClone(obj[key]);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return cloned;
|
|
401
|
+
}
|
|
402
|
+
return obj;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Clean up old snapshots to prevent memory leaks
|
|
406
|
+
*/
|
|
407
|
+
cleanupOldSnapshots(maxAge = 300000) {
|
|
408
|
+
// 5 minutes default
|
|
409
|
+
const now = Date.now();
|
|
410
|
+
// Clean up context snapshots
|
|
411
|
+
Array.from(this.contextSnapshots.entries()).forEach(([contextId, snapshot]) => {
|
|
412
|
+
if (now - snapshot.timestamp > maxAge) {
|
|
413
|
+
this.contextSnapshots.delete(contextId);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
// Note: Component snapshots use WeakMap, so they'll be garbage collected
|
|
417
|
+
// automatically when the fiber is no longer referenced
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Get recovery statistics
|
|
421
|
+
*/
|
|
422
|
+
getRecoveryStats() {
|
|
423
|
+
return {
|
|
424
|
+
activeSnapshots: 0, // WeakMap doesn't have size
|
|
425
|
+
contextSnapshots: this.contextSnapshots.size,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Clear all snapshots (for testing/cleanup)
|
|
430
|
+
*/
|
|
431
|
+
clearAllSnapshots() {
|
|
432
|
+
this.contextSnapshots.clear();
|
|
433
|
+
// WeakMap will be cleared automatically when fibers are GC'd
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Set retry configuration
|
|
437
|
+
*/
|
|
438
|
+
setRetryConfig(maxRetries, retryDelay) {
|
|
439
|
+
this.maxRetries = maxRetries;
|
|
440
|
+
this.retryDelay = retryDelay;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
exports.ErrorRecoveryManager = ErrorRecoveryManager;
|
|
@@ -0,0 +1,91 @@
|
|
|
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 { CloudDOMNode } from './types';
|
|
31
|
+
/**
|
|
32
|
+
* CloudDOM Event Bus - Centralized event system for deployment lifecycle
|
|
33
|
+
*
|
|
34
|
+
* Provides a clean way to trigger CloudDOM event callbacks without
|
|
35
|
+
* coupling to specific classes like CloudDOMBuilder or StateMachine.
|
|
36
|
+
*
|
|
37
|
+
* REQ-2.3: CloudDOM event callbacks for deployment lifecycle
|
|
38
|
+
* REQ-3.2: Error handling with component context
|
|
39
|
+
*/
|
|
40
|
+
export declare class CloudDOMEventBus {
|
|
41
|
+
/**
|
|
42
|
+
* Trigger CloudDOM event callbacks for a resource
|
|
43
|
+
*
|
|
44
|
+
* Called during deployment lifecycle to notify parent components
|
|
45
|
+
* about their child resources' deployment events.
|
|
46
|
+
*
|
|
47
|
+
* @param node - CloudDOM node with event callbacks
|
|
48
|
+
* @param phase - Deployment phase ('deploy', 'error', 'destroy')
|
|
49
|
+
* @param error - Error object (only for 'error' phase)
|
|
50
|
+
*/
|
|
51
|
+
static triggerEventCallbacks(node: CloudDOMNode, phase: 'deploy' | 'error' | 'destroy', error?: Error): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Trigger event callbacks for all nodes in a CloudDOM tree
|
|
54
|
+
*
|
|
55
|
+
* Recursively walks the tree and triggers callbacks for each node.
|
|
56
|
+
*
|
|
57
|
+
* @param nodes - CloudDOM nodes to trigger events for
|
|
58
|
+
* @param phase - Deployment phase
|
|
59
|
+
* @param error - Error object (only for 'error' phase)
|
|
60
|
+
*/
|
|
61
|
+
static triggerEventCallbacksRecursive(nodes: CloudDOMNode[], phase: 'deploy' | 'error' | 'destroy', error?: Error): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Trigger event callbacks for a specific list of nodes by ID
|
|
64
|
+
*
|
|
65
|
+
* Useful for triggering events for specific resources during deployment.
|
|
66
|
+
*
|
|
67
|
+
* @param allNodes - All CloudDOM nodes to search in
|
|
68
|
+
* @param nodeIds - IDs of nodes to trigger events for
|
|
69
|
+
* @param phase - Deployment phase
|
|
70
|
+
* @param error - Error object (only for 'error' phase)
|
|
71
|
+
*/
|
|
72
|
+
static triggerEventCallbacksForNodes(allNodes: CloudDOMNode[], nodeIds: string[], phase: 'deploy' | 'error' | 'destroy', error?: Error): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Check if any nodes in a tree have event callbacks
|
|
75
|
+
*
|
|
76
|
+
* Useful for optimization - skip event processing if no callbacks exist.
|
|
77
|
+
*
|
|
78
|
+
* @param nodes - CloudDOM nodes to check
|
|
79
|
+
* @returns True if any node has event callbacks
|
|
80
|
+
*/
|
|
81
|
+
static hasEventCallbacks(nodes: CloudDOMNode[]): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Get all nodes with event callbacks from a tree
|
|
84
|
+
*
|
|
85
|
+
* Useful for debugging and introspection.
|
|
86
|
+
*
|
|
87
|
+
* @param nodes - CloudDOM nodes to search
|
|
88
|
+
* @returns Array of nodes that have event callbacks
|
|
89
|
+
*/
|
|
90
|
+
static getNodesWithEventCallbacks(nodes: CloudDOMNode[]): CloudDOMNode[];
|
|
91
|
+
}
|