@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.
Files changed (103) hide show
  1. package/LICENSE +212 -0
  2. package/README.md +379 -0
  3. package/dist/cli/commands/BuildCommand.d.ts +40 -0
  4. package/dist/cli/commands/BuildCommand.js +151 -0
  5. package/dist/cli/commands/DeployCommand.d.ts +38 -0
  6. package/dist/cli/commands/DeployCommand.js +194 -0
  7. package/dist/cli/commands/DevCommand.d.ts +52 -0
  8. package/dist/cli/commands/DevCommand.js +385 -0
  9. package/dist/cli/commands/PlanCommand.d.ts +39 -0
  10. package/dist/cli/commands/PlanCommand.js +164 -0
  11. package/dist/cli/commands/index.d.ts +36 -0
  12. package/dist/cli/commands/index.js +43 -0
  13. package/dist/cli/core/ArgumentParser.d.ts +46 -0
  14. package/dist/cli/core/ArgumentParser.js +127 -0
  15. package/dist/cli/core/BaseCommand.d.ts +75 -0
  16. package/dist/cli/core/BaseCommand.js +95 -0
  17. package/dist/cli/core/CLIContext.d.ts +68 -0
  18. package/dist/cli/core/CLIContext.js +183 -0
  19. package/dist/cli/core/CommandRegistry.d.ts +64 -0
  20. package/dist/cli/core/CommandRegistry.js +89 -0
  21. package/dist/cli/core/index.d.ts +36 -0
  22. package/dist/cli/core/index.js +43 -0
  23. package/dist/cli/index.d.ts +35 -0
  24. package/dist/cli/index.js +100 -0
  25. package/dist/cli/output.d.ts +204 -0
  26. package/dist/cli/output.js +437 -0
  27. package/dist/cli/utils.d.ts +59 -0
  28. package/dist/cli/utils.js +76 -0
  29. package/dist/context/createContext.d.ts +90 -0
  30. package/dist/context/createContext.js +113 -0
  31. package/dist/context/index.d.ts +30 -0
  32. package/dist/context/index.js +35 -0
  33. package/dist/core/CReact.d.ts +409 -0
  34. package/dist/core/CReact.js +1127 -0
  35. package/dist/core/CloudDOMBuilder.d.ts +429 -0
  36. package/dist/core/CloudDOMBuilder.js +1198 -0
  37. package/dist/core/ContextDependencyTracker.d.ts +165 -0
  38. package/dist/core/ContextDependencyTracker.js +448 -0
  39. package/dist/core/ErrorRecoveryManager.d.ts +145 -0
  40. package/dist/core/ErrorRecoveryManager.js +443 -0
  41. package/dist/core/EventBus.d.ts +91 -0
  42. package/dist/core/EventBus.js +185 -0
  43. package/dist/core/ProviderOutputTracker.d.ts +211 -0
  44. package/dist/core/ProviderOutputTracker.js +476 -0
  45. package/dist/core/ReactiveUpdateQueue.d.ts +76 -0
  46. package/dist/core/ReactiveUpdateQueue.js +121 -0
  47. package/dist/core/Reconciler.d.ts +415 -0
  48. package/dist/core/Reconciler.js +1037 -0
  49. package/dist/core/RenderScheduler.d.ts +153 -0
  50. package/dist/core/RenderScheduler.js +519 -0
  51. package/dist/core/Renderer.d.ts +276 -0
  52. package/dist/core/Renderer.js +791 -0
  53. package/dist/core/Runtime.d.ts +246 -0
  54. package/dist/core/Runtime.js +640 -0
  55. package/dist/core/StateBindingManager.d.ts +121 -0
  56. package/dist/core/StateBindingManager.js +309 -0
  57. package/dist/core/StateMachine.d.ts +424 -0
  58. package/dist/core/StateMachine.js +787 -0
  59. package/dist/core/StructuralChangeDetector.d.ts +140 -0
  60. package/dist/core/StructuralChangeDetector.js +363 -0
  61. package/dist/core/Validator.d.ts +127 -0
  62. package/dist/core/Validator.js +279 -0
  63. package/dist/core/errors.d.ts +153 -0
  64. package/dist/core/errors.js +202 -0
  65. package/dist/core/index.d.ts +38 -0
  66. package/dist/core/index.js +64 -0
  67. package/dist/core/types.d.ts +263 -0
  68. package/dist/core/types.js +48 -0
  69. package/dist/hooks/context.d.ts +147 -0
  70. package/dist/hooks/context.js +334 -0
  71. package/dist/hooks/useContext.d.ts +113 -0
  72. package/dist/hooks/useContext.js +169 -0
  73. package/dist/hooks/useEffect.d.ts +105 -0
  74. package/dist/hooks/useEffect.js +540 -0
  75. package/dist/hooks/useInstance.d.ts +139 -0
  76. package/dist/hooks/useInstance.js +441 -0
  77. package/dist/hooks/useState.d.ts +120 -0
  78. package/dist/hooks/useState.js +298 -0
  79. package/dist/index.d.ts +46 -0
  80. package/dist/index.js +70 -0
  81. package/dist/jsx.d.ts +64 -0
  82. package/dist/jsx.js +76 -0
  83. package/dist/providers/DummyBackendProvider.d.ts +193 -0
  84. package/dist/providers/DummyBackendProvider.js +189 -0
  85. package/dist/providers/DummyCloudProvider.d.ts +128 -0
  86. package/dist/providers/DummyCloudProvider.js +157 -0
  87. package/dist/providers/IBackendProvider.d.ts +177 -0
  88. package/dist/providers/IBackendProvider.js +31 -0
  89. package/dist/providers/ICloudProvider.d.ts +146 -0
  90. package/dist/providers/ICloudProvider.js +31 -0
  91. package/dist/providers/index.d.ts +31 -0
  92. package/dist/providers/index.js +31 -0
  93. package/dist/test-event-callbacks.d.ts +0 -0
  94. package/dist/test-event-callbacks.js +1 -0
  95. package/dist/utils/Logger.d.ts +144 -0
  96. package/dist/utils/Logger.js +220 -0
  97. package/dist/utils/Output.d.ts +161 -0
  98. package/dist/utils/Output.js +401 -0
  99. package/dist/utils/deepEqual.d.ts +71 -0
  100. package/dist/utils/deepEqual.js +276 -0
  101. package/dist/utils/naming.d.ts +241 -0
  102. package/dist/utils/naming.js +376 -0
  103. package/package.json +87 -0
@@ -0,0 +1,640 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
32
+ return (mod && mod.__esModule) ? mod : { "default": mod };
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.Runtime = void 0;
36
+ // Runtime class for execution management
37
+ // REQ-1.1 through REQ-1.9: Runtime class with lifecycle management
38
+ const Renderer_1 = require("./Renderer");
39
+ const Validator_1 = require("./Validator");
40
+ const CloudDOMBuilder_1 = require("./CloudDOMBuilder");
41
+ const StateMachine_1 = require("./StateMachine");
42
+ const Reconciler_1 = require("./Reconciler");
43
+ const RenderScheduler_1 = require("./RenderScheduler");
44
+ const StateBindingManager_1 = require("./StateBindingManager");
45
+ const ProviderOutputTracker_1 = require("./ProviderOutputTracker");
46
+ const ContextDependencyTracker_1 = require("./ContextDependencyTracker");
47
+ const ErrorRecoveryManager_1 = require("./ErrorRecoveryManager");
48
+ const StructuralChangeDetector_1 = require("./StructuralChangeDetector");
49
+ const useInstance_1 = require("../hooks/useInstance");
50
+ const useState_1 = require("../hooks/useState");
51
+ const useContext_1 = require("../hooks/useContext");
52
+ const errors_1 = require("./errors");
53
+ const chalk_1 = __importDefault(require("chalk"));
54
+ // Color utilities for console output
55
+ const colors = {
56
+ success: chalk_1.default.green,
57
+ error: chalk_1.default.red,
58
+ warning: chalk_1.default.yellow,
59
+ info: chalk_1.default.blue,
60
+ dim: chalk_1.default.dim,
61
+ bold: chalk_1.default.bold,
62
+ checkmark: chalk_1.default.green('✓'),
63
+ cross: chalk_1.default.red('✗'),
64
+ bullet: chalk_1.default.gray('•'),
65
+ };
66
+ /**
67
+ * Runtime class for CReact execution management
68
+ *
69
+ * REQ-1.1: Dedicated Runtime class for managing execution lifecycle
70
+ * REQ-1.2: Initialize renderer, reconciler, state machine, and providers
71
+ * REQ-1.3: Execute full deployment pipeline
72
+ * REQ-1.4: Compute deployment diff without executing
73
+ * REQ-1.5: Tear down resources in reverse dependency order
74
+ * REQ-1.6: Accept provider instances, backend configuration, and deployment options
75
+ * REQ-1.7: Multiple Runtime instances are isolated from each other
76
+ * REQ-1.8: Provide lifecycle hooks (onStart, onComplete, onError)
77
+ * REQ-1.9: Clean up all resources and connections
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const runtime = new Runtime({
82
+ * cloudProvider: new MyCloudProvider(),
83
+ * backendProvider: new MyBackendProvider(),
84
+ * deployment: {
85
+ * parallel: true,
86
+ * maxConcurrency: 10
87
+ * },
88
+ * log: {
89
+ * scopes: ['runtime', 'reconciler'],
90
+ * level: 'info'
91
+ * },
92
+ * hooks: {
93
+ * onStart: () => console.log('Deployment starting...'),
94
+ * onComplete: (result) => console.log('Deployment complete!'),
95
+ * onError: (error) => console.error('Deployment failed:', error)
96
+ * }
97
+ * });
98
+ *
99
+ * // Deploy application
100
+ * const result = await runtime.deploy(<MyApp />);
101
+ *
102
+ * // Clean up
103
+ * await runtime.dispose();
104
+ * ```
105
+ */
106
+ class Runtime {
107
+ /**
108
+ * Constructor receives all dependencies via config (dependency injection)
109
+ * REQ-1.2: Initialize renderer, reconciler, state machine, and providers
110
+ * REQ-1.6: Accept configuration with providers and options
111
+ * REQ-1.7: Ensure multiple Runtime instances are isolated
112
+ *
113
+ * @param config - Runtime configuration with providers and options
114
+ */
115
+ constructor(config) {
116
+ this.config = config;
117
+ // Instance state
118
+ this.lastFiberTree = null;
119
+ this.isDisposed = false;
120
+ console.log(colors.dim('Initializing Runtime...'));
121
+ // REQ-1.2: Initialize core components
122
+ this.renderer = new Renderer_1.Renderer();
123
+ this.validator = new Validator_1.Validator();
124
+ this.cloudDOMBuilder = new CloudDOMBuilder_1.CloudDOMBuilder(config.cloudProvider);
125
+ this.reconciler = new Reconciler_1.Reconciler();
126
+ this.stateMachine = new StateMachine_1.StateMachine(config.backendProvider);
127
+ // Initialize reactive system components
128
+ this.renderScheduler = new RenderScheduler_1.RenderScheduler();
129
+ this.stateBindingManager = new StateBindingManager_1.StateBindingManager();
130
+ this.providerOutputTracker = new ProviderOutputTracker_1.ProviderOutputTracker();
131
+ this.contextDependencyTracker = new ContextDependencyTracker_1.ContextDependencyTracker();
132
+ this.errorRecoveryManager = new ErrorRecoveryManager_1.ErrorRecoveryManager();
133
+ this.structuralChangeDetector = new StructuralChangeDetector_1.StructuralChangeDetector();
134
+ // Wire up reactive components
135
+ this.renderer.setRenderScheduler(this.renderScheduler);
136
+ this.renderer.setContextDependencyTracker(this.contextDependencyTracker);
137
+ this.renderer.setStructuralChangeDetector(this.structuralChangeDetector);
138
+ this.cloudDOMBuilder.setReactiveComponents(this.stateBindingManager, this.providerOutputTracker);
139
+ this.contextDependencyTracker.setStateBindingManager(this.stateBindingManager);
140
+ // Set global hook contexts (for this Runtime instance)
141
+ (0, useContext_1.setContextDependencyTracker)(this.contextDependencyTracker);
142
+ (0, useState_1.setStateBindingManager)(this.stateBindingManager);
143
+ (0, useInstance_1.setProviderOutputTracker)(this.providerOutputTracker);
144
+ // Subscribe to provider output change events (event-driven reactivity)
145
+ if (config.cloudProvider.on) {
146
+ config.cloudProvider.on('outputsChanged', (change) => {
147
+ this.handleProviderOutputChange(change).catch((error) => {
148
+ console.error(colors.error('Error handling provider output change:'), error);
149
+ });
150
+ });
151
+ console.log(colors.dim('✓ Subscribed to provider output events'));
152
+ }
153
+ }
154
+ /**
155
+ * Deploy the application
156
+ * REQ-1.3: Execute full deployment pipeline and return DeploymentResult
157
+ * REQ-1.8: Lifecycle hooks support (onStart, onComplete, onError)
158
+ *
159
+ * Pipeline: render → validate → build → reconcile → deploy
160
+ *
161
+ * @param app - JSX element to deploy
162
+ * @param stackName - Stack name for state management (default: 'default')
163
+ * @param user - User initiating deployment (default: 'system')
164
+ * @returns Promise resolving to DeploymentResult
165
+ */
166
+ async deploy(app, stackName = 'default', user = 'system') {
167
+ this.ensureNotDisposed();
168
+ const startTime = Date.now();
169
+ try {
170
+ // REQ-1.8: Call onStart lifecycle hook
171
+ if (this.config.hooks?.onStart) {
172
+ await this.config.hooks.onStart();
173
+ }
174
+ // Step 1: Load previous state
175
+ const previousState = await this.stateMachine.getState(stackName);
176
+ // Step 2: Prepare hydration if we have previous state
177
+ if (previousState?.cloudDOM) {
178
+ this.prepareHydration(previousState.cloudDOM);
179
+ (0, useInstance_1.setPreviousOutputs)(this.buildOutputsMap(previousState.cloudDOM));
180
+ }
181
+ // Step 3: Render JSX → Fiber
182
+ const fiber = this.renderer.render(app);
183
+ this.lastFiberTree = fiber;
184
+ // Step 4: Clear hydration and previous outputs after render
185
+ this.clearHydration();
186
+ (0, useInstance_1.setPreviousOutputs)(null);
187
+ // Step 5: Validate Fiber
188
+ this.validator.validate(fiber);
189
+ // Step 6: Build CloudDOM from Fiber
190
+ const cloudDOM = await this.cloudDOMBuilder.build(fiber);
191
+ // Step 7: Compute diff
192
+ const previousCloudDOM = previousState?.cloudDOM || [];
193
+ const changeSet = this.reconciler.reconcile(previousCloudDOM, cloudDOM);
194
+ // Check if there are any changes
195
+ if (!(0, Reconciler_1.hasChanges)(changeSet)) {
196
+ console.log(colors.dim('No changes.'));
197
+ const duration = Date.now() - startTime;
198
+ const result = {
199
+ cloudDOM,
200
+ changeSet,
201
+ duration,
202
+ outputs: this.extractOutputs(cloudDOM),
203
+ stats: {
204
+ totalResources: cloudDOM.length,
205
+ created: 0,
206
+ updated: 0,
207
+ deleted: 0,
208
+ parallelBatches: 0,
209
+ parallelEfficiency: 0,
210
+ },
211
+ };
212
+ // REQ-1.8: Call onComplete lifecycle hook
213
+ if (this.config.hooks?.onComplete) {
214
+ await this.config.hooks.onComplete(result);
215
+ }
216
+ return result;
217
+ }
218
+ // Display changes summary with diff syntax
219
+ const total = changeSet.creates.length + changeSet.updates.length + changeSet.deletes.length;
220
+ console.log(colors.dim(`\n${total} change${total !== 1 ? 's' : ''}\n`));
221
+ // Show detailed diff
222
+ if (changeSet.creates.length > 0) {
223
+ changeSet.creates.forEach((node) => {
224
+ const resourceType = node.constructType || node.construct?.name || 'Resource';
225
+ console.log(colors.success(` + ${resourceType}`), colors.dim(node.id));
226
+ });
227
+ }
228
+ if (changeSet.updates.length > 0) {
229
+ changeSet.updates.forEach((node) => {
230
+ const resourceType = node.constructType || node.construct?.name || 'Resource';
231
+ console.log(colors.warning(` ~ ${resourceType}`), colors.dim(node.id));
232
+ });
233
+ }
234
+ if (changeSet.deletes.length > 0) {
235
+ changeSet.deletes.forEach((node) => {
236
+ const resourceType = node.constructType || node.construct?.name || 'Resource';
237
+ console.log(colors.error(` - ${resourceType}`), colors.dim(node.id));
238
+ });
239
+ }
240
+ console.log(); // Empty line before deployment starts
241
+ // Step 8: Start deployment via StateMachine
242
+ await this.stateMachine.startDeployment(stackName, changeSet, cloudDOM, user);
243
+ // Step 9: Call preDeploy lifecycle hook
244
+ if (this.config.cloudProvider.preDeploy) {
245
+ await this.config.cloudProvider.preDeploy(cloudDOM);
246
+ }
247
+ // Step 10: Deploy resources in order
248
+ for (let i = 0; i < changeSet.deploymentOrder.length; i++) {
249
+ const resourceId = changeSet.deploymentOrder[i];
250
+ const resourceNode = this.findNodeById(cloudDOM, resourceId);
251
+ if (!resourceNode) {
252
+ throw new errors_1.DeploymentError(`Resource not found: ${resourceId}`, {
253
+ message: `Resource not found: ${resourceId}`,
254
+ code: 'RESOURCE_NOT_FOUND',
255
+ details: { resourceId, stackName },
256
+ });
257
+ }
258
+ // Materialize single resource
259
+ await this.config.cloudProvider.materialize([resourceNode], null);
260
+ // Update checkpoint
261
+ await this.stateMachine.updateCheckpoint(stackName, i);
262
+ }
263
+ // Step 11: Collect outputs
264
+ const outputs = this.extractOutputs(cloudDOM);
265
+ // Step 12: Call postDeploy lifecycle hook
266
+ if (this.config.cloudProvider.postDeploy) {
267
+ await this.config.cloudProvider.postDeploy(cloudDOM, outputs);
268
+ }
269
+ // Step 13: Complete deployment
270
+ await this.stateMachine.completeDeployment(stackName);
271
+ const duration = Date.now() - startTime;
272
+ // Calculate statistics
273
+ const stats = {
274
+ totalResources: cloudDOM.length,
275
+ created: changeSet.creates.length,
276
+ updated: changeSet.updates.length,
277
+ deleted: changeSet.deletes.length,
278
+ parallelBatches: changeSet.parallelBatches.length,
279
+ parallelEfficiency: this.calculateParallelEfficiency(changeSet),
280
+ };
281
+ console.log(colors.success(`\n✓ Deployed in ${(duration / 1000).toFixed(2)}s`));
282
+ const result = {
283
+ cloudDOM,
284
+ changeSet,
285
+ duration,
286
+ outputs,
287
+ stats,
288
+ };
289
+ // REQ-1.8: Call onComplete lifecycle hook
290
+ if (this.config.hooks?.onComplete) {
291
+ await this.config.hooks.onComplete(result);
292
+ }
293
+ return result;
294
+ }
295
+ catch (error) {
296
+ console.error(colors.error(`\n✗ ${error.message}`));
297
+ // NEW (Gap 3): Attempt error recovery before failing
298
+ if (this.lastFiberTree) {
299
+ try {
300
+ console.log(colors.info('Attempting error recovery...'));
301
+ // Create snapshot for potential rollback
302
+ this.errorRecoveryManager.createComponentSnapshot(this.lastFiberTree);
303
+ // Attempt recovery using re-render error handler
304
+ const recoveryResult = await this.errorRecoveryManager.handleReRenderError(error, [this.lastFiberTree], 'manual');
305
+ if (recoveryResult.success) {
306
+ console.log(colors.success(`✓ Error recovery succeeded: ${recoveryResult.message}`));
307
+ // Recovery succeeded - return partial result
308
+ const duration = Date.now() - startTime;
309
+ return {
310
+ cloudDOM: recoveryResult.recoveredFibers?.[0]?.cloudDOMNodes || [],
311
+ changeSet: {
312
+ creates: [],
313
+ updates: [],
314
+ deletes: [],
315
+ replacements: [],
316
+ moves: [],
317
+ deploymentOrder: [],
318
+ parallelBatches: [],
319
+ },
320
+ duration,
321
+ outputs: {},
322
+ stats: {
323
+ totalResources: 0,
324
+ created: 0,
325
+ updated: 0,
326
+ deleted: 0,
327
+ parallelBatches: 0,
328
+ parallelEfficiency: 0,
329
+ },
330
+ };
331
+ }
332
+ console.log(colors.warning(`Error recovery failed: ${recoveryResult.message}`));
333
+ }
334
+ catch (recoveryError) {
335
+ console.error(colors.error('Error during recovery attempt:'), recoveryError);
336
+ // Continue with normal error handling
337
+ }
338
+ }
339
+ // REQ-1.8: Call onError lifecycle hook
340
+ if (this.config.hooks?.onError) {
341
+ await this.config.hooks.onError(error);
342
+ }
343
+ // Call provider onError hook
344
+ if (this.config.cloudProvider.onError) {
345
+ const cloudDOM = await this.getState(stackName);
346
+ await this.config.cloudProvider.onError(error, cloudDOM);
347
+ }
348
+ // Mark deployment as failed
349
+ const deploymentError = error instanceof errors_1.DeploymentError
350
+ ? error
351
+ : new errors_1.DeploymentError(error.message, {
352
+ message: error.message,
353
+ code: 'DEPLOYMENT_FAILED',
354
+ stack: error.stack,
355
+ });
356
+ await this.stateMachine.failDeployment(stackName, deploymentError);
357
+ throw error;
358
+ }
359
+ }
360
+ /**
361
+ * Compute deployment plan without executing
362
+ * REQ-1.4: Plan method computes ChangeSet without executing
363
+ *
364
+ * @param app - JSX element to plan
365
+ * @param stackName - Stack name for state comparison (default: 'default')
366
+ * @returns Promise resolving to ChangeSet
367
+ */
368
+ async plan(app, stackName = 'default') {
369
+ this.ensureNotDisposed();
370
+ try {
371
+ // Step 1: Load previous state
372
+ const previousState = await this.stateMachine.getState(stackName);
373
+ // Step 2: Prepare hydration if we have previous state
374
+ if (previousState?.cloudDOM) {
375
+ this.prepareHydration(previousState.cloudDOM);
376
+ (0, useInstance_1.setPreviousOutputs)(this.buildOutputsMap(previousState.cloudDOM));
377
+ }
378
+ // Step 3: Render JSX → Fiber
379
+ const fiber = this.renderer.render(app);
380
+ // Step 4: Clear hydration
381
+ this.clearHydration();
382
+ (0, useInstance_1.setPreviousOutputs)(null);
383
+ // Step 5: Validate Fiber
384
+ this.validator.validate(fiber);
385
+ // Step 6: Build CloudDOM
386
+ const cloudDOM = await this.cloudDOMBuilder.build(fiber);
387
+ // Step 7: Compute diff
388
+ const previousCloudDOM = previousState?.cloudDOM || [];
389
+ const changeSet = this.reconciler.reconcile(previousCloudDOM, cloudDOM);
390
+ const total = changeSet.creates.length + changeSet.updates.length + changeSet.deletes.length;
391
+ if (total === 0) {
392
+ console.log(colors.dim('No changes.'));
393
+ }
394
+ else {
395
+ console.log(colors.dim(`${total} change${total !== 1 ? 's' : ''}\n`));
396
+ // Show detailed diff
397
+ if (changeSet.creates.length > 0) {
398
+ changeSet.creates.forEach((node) => {
399
+ const resourceType = node.constructType || node.construct?.name || 'Resource';
400
+ console.log(colors.success(` + ${resourceType}`), colors.dim(node.id));
401
+ });
402
+ }
403
+ if (changeSet.updates.length > 0) {
404
+ changeSet.updates.forEach((node) => {
405
+ const resourceType = node.constructType || node.construct?.name || 'Resource';
406
+ console.log(colors.warning(` ~ ${resourceType}`), colors.dim(node.id));
407
+ });
408
+ }
409
+ if (changeSet.deletes.length > 0) {
410
+ changeSet.deletes.forEach((node) => {
411
+ const resourceType = node.constructType || node.construct?.name || 'Resource';
412
+ console.log(colors.error(` - ${resourceType}`), colors.dim(node.id));
413
+ });
414
+ }
415
+ }
416
+ return changeSet;
417
+ }
418
+ catch (error) {
419
+ console.error(colors.error(`✗ ${error.message}`));
420
+ throw error;
421
+ }
422
+ }
423
+ /**
424
+ * Destroy all resources
425
+ * REQ-1.5: Tear down resources in reverse dependency order
426
+ *
427
+ * @param stackName - Stack name to destroy (default: 'default')
428
+ */
429
+ async destroy(stackName = 'default') {
430
+ this.ensureNotDisposed();
431
+ try {
432
+ // Get current state
433
+ const state = await this.stateMachine.getState(stackName);
434
+ if (!state || !state.cloudDOM || state.cloudDOM.length === 0) {
435
+ console.log(colors.dim('Nothing to destroy.'));
436
+ return;
437
+ }
438
+ // Compute reverse deployment order
439
+ const changeSet = this.reconciler.reconcile(state.cloudDOM, []);
440
+ const reverseOrder = [...changeSet.deploymentOrder].reverse();
441
+ console.log(colors.dim(`${reverseOrder.length} resource${reverseOrder.length !== 1 ? 's' : ''} to destroy\n`));
442
+ // Show what will be destroyed
443
+ for (let i = 0; i < reverseOrder.length; i++) {
444
+ const resourceId = reverseOrder[i];
445
+ const resourceNode = this.findNodeById(state.cloudDOM, resourceId);
446
+ if (resourceNode) {
447
+ const resourceType = resourceNode.constructType || resourceNode.construct?.name || 'Resource';
448
+ console.log(colors.error(` - ${resourceType}`), colors.dim(resourceNode.id));
449
+ }
450
+ }
451
+ console.log(); // Empty line
452
+ // Delete resources in reverse order
453
+ for (let i = 0; i < reverseOrder.length; i++) {
454
+ const resourceId = reverseOrder[i];
455
+ const resourceNode = this.findNodeById(state.cloudDOM, resourceId);
456
+ if (resourceNode) {
457
+ // Note: Actual deletion would be handled by provider
458
+ // For now, we just log the action
459
+ }
460
+ }
461
+ // Clear state by saving empty CloudDOM
462
+ await this.config.backendProvider.saveState(stackName, {
463
+ status: 'DEPLOYED',
464
+ cloudDOM: [],
465
+ timestamp: Date.now(),
466
+ user: 'system',
467
+ stackName,
468
+ });
469
+ console.log(colors.success('✓ Destroyed'));
470
+ }
471
+ catch (error) {
472
+ console.error(colors.error(`✗ ${error.message}`));
473
+ throw error;
474
+ }
475
+ }
476
+ /**
477
+ * Get current CloudDOM state
478
+ * REQ-1.6: Get current CloudDOM state
479
+ *
480
+ * @param stackName - Stack name (default: 'default')
481
+ * @returns Promise resolving to CloudDOM tree
482
+ */
483
+ async getState(stackName = 'default') {
484
+ this.ensureNotDisposed();
485
+ const state = await this.stateMachine.getState(stackName);
486
+ return state?.cloudDOM || [];
487
+ }
488
+ /**
489
+ * Dispose runtime and clean up resources
490
+ * REQ-1.9: Clean up all resources and connections
491
+ */
492
+ async dispose() {
493
+ if (this.isDisposed) {
494
+ return;
495
+ }
496
+ // Clear hook contexts
497
+ (0, useContext_1.setContextDependencyTracker)(null);
498
+ (0, useState_1.setStateBindingManager)(null);
499
+ (0, useInstance_1.setProviderOutputTracker)(null);
500
+ (0, useInstance_1.setPreviousOutputs)(null);
501
+ // Mark as disposed
502
+ this.isDisposed = true;
503
+ }
504
+ /**
505
+ * Ensure runtime is not disposed
506
+ * @private
507
+ */
508
+ ensureNotDisposed() {
509
+ if (this.isDisposed) {
510
+ throw new Error('Runtime has been disposed and cannot be used');
511
+ }
512
+ }
513
+ /**
514
+ * Handle output change events from provider (event-driven reactivity)
515
+ * Called when provider emits 'outputsChanged' event
516
+ *
517
+ * This enables real-time reactivity without polling:
518
+ * 1. Update ProviderOutputTracker with new outputs
519
+ * 2. Execute useEffect callbacks bound to these outputs (TODO)
520
+ * 3. Update bound state and enqueue affected fibers for re-render
521
+ *
522
+ * @param change - Output change event from provider
523
+ * @private
524
+ */
525
+ async handleProviderOutputChange(change) {
526
+ if (!this.lastFiberTree) {
527
+ return; // No fiber tree to update
528
+ }
529
+ console.log(colors.dim(`Provider output changed: ${change.nodeId}`), change.outputs);
530
+ // Step 1: Update ProviderOutputTracker
531
+ const outputChanges = this.providerOutputTracker.updateInstanceOutputs(change.nodeId, change.outputs);
532
+ if (outputChanges.length === 0) {
533
+ return; // No actual changes detected
534
+ }
535
+ // Step 2: Execute useEffect callbacks bound to these outputs
536
+ // TODO: Implement executeEffectsOnOutputChange when useEffect is ready
537
+ // await executeEffectsOnOutputChange(this.lastFiberTree, outputChanges);
538
+ // Step 3: Update bound state and get affected fibers
539
+ const affectedFibers = this.stateBindingManager.processOutputChanges(outputChanges);
540
+ if (affectedFibers.length > 0) {
541
+ console.log(colors.info(`✓ Output change affected ${affectedFibers.length} fibers`));
542
+ // Schedule re-renders for affected components
543
+ affectedFibers.forEach((fiber) => {
544
+ this.renderScheduler.schedule(fiber, 'output-update');
545
+ });
546
+ // Note: Actual re-render execution happens in the next deployment cycle
547
+ // or can be triggered immediately via renderScheduler.flushBatch()
548
+ }
549
+ }
550
+ /**
551
+ * Prepare hydration data from previous CloudDOM
552
+ * @private
553
+ */
554
+ prepareHydration(previousCloudDOM) {
555
+ // This would integrate with the hydration system
556
+ // For now, we'll keep it simple
557
+ }
558
+ /**
559
+ * Clear hydration data
560
+ * @private
561
+ */
562
+ clearHydration() {
563
+ // Clear hydration
564
+ }
565
+ /**
566
+ * Build outputs map from CloudDOM
567
+ * @private
568
+ */
569
+ buildOutputsMap(cloudDOM) {
570
+ const outputsMap = new Map();
571
+ const walk = (nodes) => {
572
+ for (const node of nodes) {
573
+ if (node.outputs && Object.keys(node.outputs).length > 0) {
574
+ outputsMap.set(node.id, node.outputs);
575
+ }
576
+ if (node.children && node.children.length > 0) {
577
+ walk(node.children);
578
+ }
579
+ }
580
+ };
581
+ walk(cloudDOM);
582
+ return outputsMap;
583
+ }
584
+ /**
585
+ * Find node by ID in CloudDOM tree
586
+ * @private
587
+ */
588
+ findNodeById(cloudDOM, id) {
589
+ for (const node of cloudDOM) {
590
+ if (node.id === id) {
591
+ return node;
592
+ }
593
+ if (node.children && node.children.length > 0) {
594
+ const found = this.findNodeById(node.children, id);
595
+ if (found) {
596
+ return found;
597
+ }
598
+ }
599
+ }
600
+ return null;
601
+ }
602
+ /**
603
+ * Extract outputs from CloudDOM
604
+ * @private
605
+ */
606
+ extractOutputs(cloudDOM) {
607
+ const outputs = {};
608
+ const walk = (nodes) => {
609
+ for (const node of nodes) {
610
+ if (node.outputs) {
611
+ outputs[node.id] = node.outputs;
612
+ }
613
+ if (node.children && node.children.length > 0) {
614
+ walk(node.children);
615
+ }
616
+ }
617
+ };
618
+ walk(cloudDOM);
619
+ return outputs;
620
+ }
621
+ /**
622
+ * Calculate parallel efficiency from change set
623
+ * @private
624
+ */
625
+ calculateParallelEfficiency(changeSet) {
626
+ if (changeSet.parallelBatches.length === 0) {
627
+ return 0;
628
+ }
629
+ const totalResources = changeSet.deploymentOrder.length;
630
+ const batchCount = changeSet.parallelBatches.length;
631
+ // Perfect parallelism would be all resources in one batch
632
+ // No parallelism would be one resource per batch
633
+ // Efficiency = (totalResources - batchCount) / (totalResources - 1)
634
+ if (totalResources <= 1) {
635
+ return 1;
636
+ }
637
+ return (totalResources - batchCount) / (totalResources - 1);
638
+ }
639
+ }
640
+ exports.Runtime = Runtime;