@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,139 @@
|
|
|
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 '../core/types';
|
|
31
|
+
import { ProviderOutputTracker } from '../core/ProviderOutputTracker';
|
|
32
|
+
/**
|
|
33
|
+
* Set the ProviderOutputTracker instance (for testing/injection)
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare function setProviderOutputTracker(tracker: ProviderOutputTracker): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get the ProviderOutputTracker instance (for external access)
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export declare function getProviderOutputTrackerInstance(): ProviderOutputTracker;
|
|
42
|
+
/**
|
|
43
|
+
* Set the current rendering context
|
|
44
|
+
* Called by Renderer before executing a component
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare function setInstanceRenderContext(fiber: any, path: string[]): void;
|
|
49
|
+
/**
|
|
50
|
+
* Clear the current rendering context
|
|
51
|
+
* Called by Renderer after component execution
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export declare function clearInstanceRenderContext(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Set previous outputs map for restoration during render
|
|
58
|
+
* Called by CReact before rendering
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export declare function setPreviousOutputs(outputsMap: Map<string, Record<string, any>> | null): void;
|
|
63
|
+
/**
|
|
64
|
+
* useInstance hook - Register an infrastructure resource (React-like API)
|
|
65
|
+
*
|
|
66
|
+
* This hook creates a CloudDOM node and attaches it to the current Fiber.
|
|
67
|
+
* It must be called during component rendering (inside a component function).
|
|
68
|
+
*
|
|
69
|
+
* Automatically extracts event callbacks (onDeploy, onError, onDestroy) from
|
|
70
|
+
* component props and attaches them to the CloudDOM node for lifecycle events.
|
|
71
|
+
*
|
|
72
|
+
* REQ-01: JSX → CloudDOM rendering
|
|
73
|
+
* REQ-04: Resource creation via hooks (React-like API)
|
|
74
|
+
* REQ-2.3: CloudDOM event callbacks for deployment lifecycle
|
|
75
|
+
*
|
|
76
|
+
* @param construct - Constructor/class for the infrastructure resource
|
|
77
|
+
* @param props - Properties/configuration for the resource (may include `key` and event callbacks)
|
|
78
|
+
* @returns Reference to the created CloudDOM node
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* // With event callbacks (extracted from component props)
|
|
83
|
+
* function MyDatabase({ onDeploy, onError }) {
|
|
84
|
+
* const db = useInstance(Database, {
|
|
85
|
+
* name: 'my-db',
|
|
86
|
+
* size: '100GB'
|
|
87
|
+
* });
|
|
88
|
+
* // onDeploy and onError are automatically extracted from component props
|
|
89
|
+
* return <></>;
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* // Usage with event callbacks
|
|
93
|
+
* <MyDatabase
|
|
94
|
+
* onDeploy={(ctx) => logger.info('Database deployed:', ctx.resourceId)}
|
|
95
|
+
* onError={(ctx, err) => logger.error('Database failed:', err)}
|
|
96
|
+
* />
|
|
97
|
+
*
|
|
98
|
+
* // Without event callbacks (normal usage)
|
|
99
|
+
* function MyBucket() {
|
|
100
|
+
* const bucket = useInstance(S3Bucket, {
|
|
101
|
+
* bucketName: 'my-assets'
|
|
102
|
+
* });
|
|
103
|
+
* return <></>;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function useInstance<T = any>(construct: new (...args: any[]) => T, props: Record<string, any>): CloudDOMNode;
|
|
108
|
+
/**
|
|
109
|
+
* Reset construct call counts for a fiber
|
|
110
|
+
* Called by Renderer before executing a component
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export declare function resetConstructCounts(fiber: any): void;
|
|
115
|
+
/**
|
|
116
|
+
* Get the current rendering path
|
|
117
|
+
* Useful for debugging and testing
|
|
118
|
+
*
|
|
119
|
+
* @internal
|
|
120
|
+
*/
|
|
121
|
+
export declare function getCurrentPath(): string[];
|
|
122
|
+
/**
|
|
123
|
+
* Check if currently rendering
|
|
124
|
+
* Useful for validation and testing
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
export declare function isRendering(): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Update outputs for a CloudDOM node and notify bound components
|
|
131
|
+
* This is called after deployment when provider outputs are available
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
export declare function updateNodeOutputs(nodeId: string, newOutputs: Record<string, any>): void;
|
|
135
|
+
/**
|
|
136
|
+
* Get current outputs for a CloudDOM node
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
export declare function getNodeOutputs(nodeId: string): Record<string, any>;
|
|
@@ -0,0 +1,441 @@
|
|
|
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.setProviderOutputTracker = setProviderOutputTracker;
|
|
33
|
+
exports.getProviderOutputTrackerInstance = getProviderOutputTrackerInstance;
|
|
34
|
+
exports.setInstanceRenderContext = setInstanceRenderContext;
|
|
35
|
+
exports.clearInstanceRenderContext = clearInstanceRenderContext;
|
|
36
|
+
exports.setPreviousOutputs = setPreviousOutputs;
|
|
37
|
+
exports.useInstance = useInstance;
|
|
38
|
+
exports.resetConstructCounts = resetConstructCounts;
|
|
39
|
+
exports.getCurrentPath = getCurrentPath;
|
|
40
|
+
exports.isRendering = isRendering;
|
|
41
|
+
exports.updateNodeOutputs = updateNodeOutputs;
|
|
42
|
+
exports.getNodeOutputs = getNodeOutputs;
|
|
43
|
+
const naming_1 = require("../utils/naming");
|
|
44
|
+
const ProviderOutputTracker_1 = require("../core/ProviderOutputTracker");
|
|
45
|
+
const context_1 = require("./context");
|
|
46
|
+
const Logger_1 = require("../utils/Logger");
|
|
47
|
+
const logger = Logger_1.LoggerFactory.getLogger('hooks');
|
|
48
|
+
// Global ProviderOutputTracker instance
|
|
49
|
+
let providerOutputTracker = null;
|
|
50
|
+
/**
|
|
51
|
+
* Get or create the global ProviderOutputTracker instance
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
function getProviderOutputTracker() {
|
|
55
|
+
if (!providerOutputTracker) {
|
|
56
|
+
providerOutputTracker = new ProviderOutputTracker_1.ProviderOutputTracker();
|
|
57
|
+
}
|
|
58
|
+
return providerOutputTracker;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Set the ProviderOutputTracker instance (for testing/injection)
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
function setProviderOutputTracker(tracker) {
|
|
65
|
+
providerOutputTracker = tracker;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get the ProviderOutputTracker instance (for external access)
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
function getProviderOutputTrackerInstance() {
|
|
72
|
+
return getProviderOutputTracker();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a placeholder node when dependencies are undefined
|
|
76
|
+
* This node won't be included in CloudDOM but allows the component to continue rendering
|
|
77
|
+
* All output accesses return undefined
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
function createPlaceholderNode() {
|
|
82
|
+
const placeholderNode = {
|
|
83
|
+
id: '__placeholder__',
|
|
84
|
+
path: ['__placeholder__'],
|
|
85
|
+
construct: class Placeholder {
|
|
86
|
+
},
|
|
87
|
+
constructType: 'Placeholder',
|
|
88
|
+
props: {},
|
|
89
|
+
children: [],
|
|
90
|
+
outputs: {},
|
|
91
|
+
};
|
|
92
|
+
// Return a proxy that always returns undefined for outputs
|
|
93
|
+
return new Proxy(placeholderNode, {
|
|
94
|
+
get(target, prop) {
|
|
95
|
+
if (prop === 'outputs') {
|
|
96
|
+
return new Proxy({}, {
|
|
97
|
+
get() {
|
|
98
|
+
return undefined;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return Reflect.get(target, prop);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create an enhanced CloudDOM node with output reference capabilities
|
|
108
|
+
* This allows automatic binding when outputs are used in useState
|
|
109
|
+
* The proxy dynamically reads from the node's current outputs for reactivity
|
|
110
|
+
*
|
|
111
|
+
* REQ-2.1, 2.4, 2.5: Enhanced proxy that always reads live values and tracks access
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
function createEnhancedNode(node, fiber) {
|
|
116
|
+
// Create a proxy that intercepts property access to provide reactive output access
|
|
117
|
+
return new Proxy(node, {
|
|
118
|
+
get(target, prop, receiver) {
|
|
119
|
+
// Special handling for 'outputs' property to ensure reactivity
|
|
120
|
+
if (prop === 'outputs') {
|
|
121
|
+
// Return a proxy for the outputs object that tracks reads
|
|
122
|
+
return new Proxy(target.outputs || {}, {
|
|
123
|
+
get(outputsTarget, outputKey) {
|
|
124
|
+
if (typeof outputKey === 'string') {
|
|
125
|
+
// REQ-2.1: Always read from current target.outputs (live values)
|
|
126
|
+
const currentValue = target.outputs?.[outputKey];
|
|
127
|
+
// REQ-2.4, 2.5: Track this output read for binding creation
|
|
128
|
+
if (currentValue !== undefined) {
|
|
129
|
+
const tracker = getProviderOutputTracker();
|
|
130
|
+
tracker.trackOutputRead(target.id, outputKey, fiber);
|
|
131
|
+
logger.debug(`Tracked output read: ${target.id}.${outputKey} = ${currentValue}`);
|
|
132
|
+
}
|
|
133
|
+
// REQ-2.2: Return undefined gracefully if not populated
|
|
134
|
+
return currentValue;
|
|
135
|
+
}
|
|
136
|
+
return Reflect.get(outputsTarget, outputKey);
|
|
137
|
+
},
|
|
138
|
+
has(outputsTarget, outputKey) {
|
|
139
|
+
// Check if output exists in current target.outputs
|
|
140
|
+
return (typeof outputKey === 'string' &&
|
|
141
|
+
target.outputs !== undefined &&
|
|
142
|
+
outputKey in target.outputs);
|
|
143
|
+
},
|
|
144
|
+
ownKeys(outputsTarget) {
|
|
145
|
+
// Return keys from current target.outputs
|
|
146
|
+
return target.outputs ? Object.keys(target.outputs) : [];
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// For all other properties, return the original value
|
|
151
|
+
return Reflect.get(target, prop, receiver);
|
|
152
|
+
},
|
|
153
|
+
has(target, prop) {
|
|
154
|
+
// Standard property checks
|
|
155
|
+
return Reflect.has(target, prop);
|
|
156
|
+
},
|
|
157
|
+
ownKeys(target) {
|
|
158
|
+
// Return original keys
|
|
159
|
+
return Reflect.ownKeys(target);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Set the current rendering context
|
|
165
|
+
* Called by Renderer before executing a component
|
|
166
|
+
*
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
function setInstanceRenderContext(fiber, path) {
|
|
170
|
+
(0, context_1.setRenderContext)(fiber, path);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Clear the current rendering context
|
|
174
|
+
* Called by Renderer after component execution
|
|
175
|
+
*
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
function clearInstanceRenderContext() {
|
|
179
|
+
(0, context_1.clearRenderContext)();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Set previous outputs map for restoration during render
|
|
183
|
+
* Called by CReact before rendering
|
|
184
|
+
*
|
|
185
|
+
* @internal
|
|
186
|
+
*/
|
|
187
|
+
function setPreviousOutputs(outputsMap) {
|
|
188
|
+
(0, context_1.setPreviousOutputs)(outputsMap);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Track construct call counts per component for auto-ID generation
|
|
192
|
+
* Maps component fiber to construct type to call count
|
|
193
|
+
*/
|
|
194
|
+
const constructCallCounts = new WeakMap();
|
|
195
|
+
/**
|
|
196
|
+
* useInstance hook - Register an infrastructure resource (React-like API)
|
|
197
|
+
*
|
|
198
|
+
* This hook creates a CloudDOM node and attaches it to the current Fiber.
|
|
199
|
+
* It must be called during component rendering (inside a component function).
|
|
200
|
+
*
|
|
201
|
+
* Automatically extracts event callbacks (onDeploy, onError, onDestroy) from
|
|
202
|
+
* component props and attaches them to the CloudDOM node for lifecycle events.
|
|
203
|
+
*
|
|
204
|
+
* REQ-01: JSX → CloudDOM rendering
|
|
205
|
+
* REQ-04: Resource creation via hooks (React-like API)
|
|
206
|
+
* REQ-2.3: CloudDOM event callbacks for deployment lifecycle
|
|
207
|
+
*
|
|
208
|
+
* @param construct - Constructor/class for the infrastructure resource
|
|
209
|
+
* @param props - Properties/configuration for the resource (may include `key` and event callbacks)
|
|
210
|
+
* @returns Reference to the created CloudDOM node
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```tsx
|
|
214
|
+
* // With event callbacks (extracted from component props)
|
|
215
|
+
* function MyDatabase({ onDeploy, onError }) {
|
|
216
|
+
* const db = useInstance(Database, {
|
|
217
|
+
* name: 'my-db',
|
|
218
|
+
* size: '100GB'
|
|
219
|
+
* });
|
|
220
|
+
* // onDeploy and onError are automatically extracted from component props
|
|
221
|
+
* return <></>;
|
|
222
|
+
* }
|
|
223
|
+
*
|
|
224
|
+
* // Usage with event callbacks
|
|
225
|
+
* <MyDatabase
|
|
226
|
+
* onDeploy={(ctx) => logger.info('Database deployed:', ctx.resourceId)}
|
|
227
|
+
* onError={(ctx, err) => logger.error('Database failed:', err)}
|
|
228
|
+
* />
|
|
229
|
+
*
|
|
230
|
+
* // Without event callbacks (normal usage)
|
|
231
|
+
* function MyBucket() {
|
|
232
|
+
* const bucket = useInstance(S3Bucket, {
|
|
233
|
+
* bucketName: 'my-assets'
|
|
234
|
+
* });
|
|
235
|
+
* return <></>;
|
|
236
|
+
* }
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
function useInstance(construct, props) {
|
|
240
|
+
// Use consolidated hook context
|
|
241
|
+
const context = (0, context_1.requireHookContext)();
|
|
242
|
+
const currentFiber = context.currentFiber; // Non-null assertion safe due to requireHookContext validation
|
|
243
|
+
const { currentPath, previousOutputsMap } = context;
|
|
244
|
+
// Get hook index for this useInstance call (instance-specific)
|
|
245
|
+
const hookIndex = (0, context_1.incrementHookIndex)('instance');
|
|
246
|
+
// Extract key from props (React-like)
|
|
247
|
+
const { key, ...restProps } = props;
|
|
248
|
+
// Check for undefined dependencies - enforce deployment order
|
|
249
|
+
// If any prop value is undefined, don't create the node yet
|
|
250
|
+
// This ensures resources are only created when their dependencies are available
|
|
251
|
+
const hasUndefinedDeps = Object.values(restProps).some((v) => v === undefined);
|
|
252
|
+
if (hasUndefinedDeps) {
|
|
253
|
+
logger.info(`[Deployment Order] Skipping resource creation - undefined dependencies detected`);
|
|
254
|
+
logger.info(` Construct: ${construct.name}`);
|
|
255
|
+
logger.info(` Props with undefined values:`, Object.entries(restProps)
|
|
256
|
+
.filter(([_, v]) => v === undefined)
|
|
257
|
+
.map(([k]) => k));
|
|
258
|
+
// Don't attach anything to fiber - this resource will be skipped entirely
|
|
259
|
+
// Return a placeholder node that won't be included in CloudDOM
|
|
260
|
+
// The proxy will return undefined for all output accesses
|
|
261
|
+
return createPlaceholderNode();
|
|
262
|
+
}
|
|
263
|
+
// Remove undefined values from props to match JSON serialization behavior
|
|
264
|
+
// When CloudDOM is saved to backend, JSON.stringify strips undefined values
|
|
265
|
+
// This ensures consistent comparison after deserialization
|
|
266
|
+
const cleanProps = {};
|
|
267
|
+
for (const [k, v] of Object.entries(restProps)) {
|
|
268
|
+
if (v !== undefined) {
|
|
269
|
+
cleanProps[k] = v;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Extract event callbacks from current component's props (not useInstance props)
|
|
273
|
+
const componentProps = currentFiber.props || {};
|
|
274
|
+
const eventCallbacks = {};
|
|
275
|
+
// Extract onDeploy callback
|
|
276
|
+
if (typeof componentProps.onDeploy === 'function') {
|
|
277
|
+
eventCallbacks.onDeploy = componentProps.onDeploy;
|
|
278
|
+
}
|
|
279
|
+
// Extract onError callback
|
|
280
|
+
if (typeof componentProps.onError === 'function') {
|
|
281
|
+
eventCallbacks.onError = componentProps.onError;
|
|
282
|
+
}
|
|
283
|
+
// Extract onDestroy callback
|
|
284
|
+
if (typeof componentProps.onDestroy === 'function') {
|
|
285
|
+
eventCallbacks.onDestroy = componentProps.onDestroy;
|
|
286
|
+
}
|
|
287
|
+
// Generate ID from key or construct type
|
|
288
|
+
// Priority: explicit key from useInstance props > component fiber key > auto-generated
|
|
289
|
+
let id;
|
|
290
|
+
if (key !== undefined) {
|
|
291
|
+
// Use explicit key if provided in useInstance props
|
|
292
|
+
id = String(key);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// Auto-generate ID from construct type name (kebab-case)
|
|
296
|
+
const constructName = (0, naming_1.toKebabCase)(construct.name);
|
|
297
|
+
// Track call count for this construct type in this component
|
|
298
|
+
if (!constructCallCounts.has(currentFiber)) {
|
|
299
|
+
constructCallCounts.set(currentFiber, new Map());
|
|
300
|
+
}
|
|
301
|
+
const counts = constructCallCounts.get(currentFiber);
|
|
302
|
+
const count = counts.get(construct) || 0;
|
|
303
|
+
counts.set(construct, count + 1);
|
|
304
|
+
// Append index if multiple calls with same type
|
|
305
|
+
if (count > 0) {
|
|
306
|
+
id = `${constructName}-${count}`;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
id = constructName;
|
|
310
|
+
}
|
|
311
|
+
// If component has a key (from JSX), prepend it to make resources unique per component instance
|
|
312
|
+
if (currentFiber.key) {
|
|
313
|
+
id = `${currentFiber.key}-${id}`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Generate full resource ID from current path
|
|
317
|
+
// Example: ['registry', 'service'] + 'bucket' → 'registry.service.bucket'
|
|
318
|
+
const fullPath = [...currentPath, id];
|
|
319
|
+
const resourceId = (0, naming_1.generateResourceId)(fullPath);
|
|
320
|
+
// Generate stable constructType identifier
|
|
321
|
+
// This is a serializable string that uniquely identifies the construct type
|
|
322
|
+
const constructType = construct.name || 'UnknownConstruct';
|
|
323
|
+
// Create CloudDOM node
|
|
324
|
+
const node = {
|
|
325
|
+
id: resourceId,
|
|
326
|
+
path: fullPath,
|
|
327
|
+
construct,
|
|
328
|
+
constructType, // Serializable construct type identifier
|
|
329
|
+
props: cleanProps, // Use cleaned props (no undefined values, no key)
|
|
330
|
+
children: [],
|
|
331
|
+
outputs: {}, // Will be populated during materialization
|
|
332
|
+
eventCallbacks: Object.keys(eventCallbacks).length > 0 ? eventCallbacks : undefined,
|
|
333
|
+
};
|
|
334
|
+
// Restore outputs from previous state if available
|
|
335
|
+
logger.debug(`Checking for outputs: ${resourceId}, map has ${previousOutputsMap?.size || 0} entries`);
|
|
336
|
+
if (previousOutputsMap && previousOutputsMap.has(resourceId)) {
|
|
337
|
+
node.outputs = { ...previousOutputsMap.get(resourceId) };
|
|
338
|
+
logger.debug(`✓ Restored outputs for: ${resourceId}`, node.outputs);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
logger.debug(`✗ No outputs found for: ${resourceId}`);
|
|
342
|
+
if (previousOutputsMap) {
|
|
343
|
+
logger.debug(` Available keys:`, Array.from(previousOutputsMap.keys()));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Attach node to current Fiber
|
|
347
|
+
// The Fiber stores all CloudDOM nodes created during its execution
|
|
348
|
+
if (!currentFiber.cloudDOMNodes) {
|
|
349
|
+
currentFiber.cloudDOMNodes = [];
|
|
350
|
+
}
|
|
351
|
+
// Check if a node with this ID already exists (from previous render)
|
|
352
|
+
// If so, update it instead of creating a duplicate
|
|
353
|
+
const existingNodeIndex = currentFiber.cloudDOMNodes.findIndex((n) => n.id === resourceId);
|
|
354
|
+
if (existingNodeIndex >= 0) {
|
|
355
|
+
logger.debug(`Updating existing node: ${resourceId}`);
|
|
356
|
+
currentFiber.cloudDOMNodes[existingNodeIndex] = node;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
logger.debug(`Creating new node: ${resourceId}`);
|
|
360
|
+
currentFiber.cloudDOMNodes.push(node);
|
|
361
|
+
}
|
|
362
|
+
// Track this instance with ProviderOutputTracker for output change detection
|
|
363
|
+
const outputTracker = getProviderOutputTracker();
|
|
364
|
+
outputTracker.trackInstance(node, currentFiber);
|
|
365
|
+
// REQ-2.3, 6.1: Enhance the node with proxy that reads live values and tracks access
|
|
366
|
+
// No longer creating stale outputReferences - proxy reads directly from target.outputs
|
|
367
|
+
const enhancedNode = createEnhancedNode(node, currentFiber);
|
|
368
|
+
// Debug logging
|
|
369
|
+
logger.debug(`Created and tracked instance: ${resourceId}`);
|
|
370
|
+
logger.debug(`Node outputs at creation: ${JSON.stringify(node.outputs || {})}`);
|
|
371
|
+
// Return reference to the enhanced node
|
|
372
|
+
// This allows components to reference the resource and its outputs
|
|
373
|
+
// REQ-2.5: Multiple accesses within same render cycle are consistent
|
|
374
|
+
return enhancedNode;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Reset construct call counts for a fiber
|
|
378
|
+
* Called by Renderer before executing a component
|
|
379
|
+
*
|
|
380
|
+
* @internal
|
|
381
|
+
*/
|
|
382
|
+
function resetConstructCounts(fiber) {
|
|
383
|
+
constructCallCounts.delete(fiber);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get the current rendering path
|
|
387
|
+
* Useful for debugging and testing
|
|
388
|
+
*
|
|
389
|
+
* @internal
|
|
390
|
+
*/
|
|
391
|
+
function getCurrentPath() {
|
|
392
|
+
try {
|
|
393
|
+
const context = (0, context_1.requireHookContext)();
|
|
394
|
+
return [...context.currentPath];
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Check if currently rendering
|
|
402
|
+
* Useful for validation and testing
|
|
403
|
+
*
|
|
404
|
+
* @internal
|
|
405
|
+
*/
|
|
406
|
+
function isRendering() {
|
|
407
|
+
try {
|
|
408
|
+
const context = (0, context_1.requireHookContext)();
|
|
409
|
+
return context.currentFiber !== null;
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Update outputs for a CloudDOM node and notify bound components
|
|
417
|
+
* This is called after deployment when provider outputs are available
|
|
418
|
+
* @internal
|
|
419
|
+
*/
|
|
420
|
+
function updateNodeOutputs(nodeId, newOutputs) {
|
|
421
|
+
const outputTracker = getProviderOutputTracker();
|
|
422
|
+
const changes = outputTracker.updateNodeOutputs(nodeId, newOutputs);
|
|
423
|
+
if (changes.length > 0) {
|
|
424
|
+
// Process the changes and get affected fibers
|
|
425
|
+
const affectedFibers = outputTracker.processOutputChanges(changes);
|
|
426
|
+
logger.debug(`Output changes detected for ${nodeId}:`, {
|
|
427
|
+
changes: changes.length,
|
|
428
|
+
affectedFibers: affectedFibers.size,
|
|
429
|
+
});
|
|
430
|
+
// Note: The actual re-rendering will be handled by the RenderScheduler
|
|
431
|
+
// when it's integrated with the CReact main orchestrator
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Get current outputs for a CloudDOM node
|
|
436
|
+
* @internal
|
|
437
|
+
*/
|
|
438
|
+
function getNodeOutputs(nodeId) {
|
|
439
|
+
const outputTracker = getProviderOutputTracker();
|
|
440
|
+
return outputTracker.getNodeOutputs(nodeId);
|
|
441
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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 { StateBindingManager } from '../core/StateBindingManager';
|
|
31
|
+
import { FiberNode } from '../core/types';
|
|
32
|
+
/**
|
|
33
|
+
* Set the StateBindingManager instance (for testing/injection)
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare function setStateBindingManager(manager: StateBindingManager): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get the StateBindingManager instance (for external access)
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export declare function getStateBindingManagerInstance(): StateBindingManager;
|
|
42
|
+
/**
|
|
43
|
+
* Set the current rendering context for useState
|
|
44
|
+
* Called by Renderer before executing a component
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare function setStateRenderContext(fiber: FiberNode, path?: string[]): void;
|
|
49
|
+
/**
|
|
50
|
+
* Clear the current rendering context for useState
|
|
51
|
+
* Called by Renderer after component execution
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export declare function clearStateRenderContext(): void;
|
|
56
|
+
/**
|
|
57
|
+
* useState hook - Declare component outputs (NOT reactive state)
|
|
58
|
+
*
|
|
59
|
+
* This hook declares outputs that will be persisted across build/deploy cycles.
|
|
60
|
+
* Unlike React's useState, this does NOT trigger re-renders.
|
|
61
|
+
*
|
|
62
|
+
* Mimics React's useState API with hooks array pattern for multiple calls.
|
|
63
|
+
*
|
|
64
|
+
* REQ-02: Stack Context (declarative outputs)
|
|
65
|
+
*
|
|
66
|
+
* Semantics:
|
|
67
|
+
* - `useState(initialValue)` - Declares a single output value
|
|
68
|
+
* - `setState(value)` during build - Updates the output value for build-time enrichment
|
|
69
|
+
* - `setState(value)` during deploy - Updates persisted output after provider materialization
|
|
70
|
+
* - NOT a render trigger - it's a persistent output update mechanism
|
|
71
|
+
* - Supports multiple useState calls per component (like React)
|
|
72
|
+
*
|
|
73
|
+
* Key difference from React:
|
|
74
|
+
* - React: `setState()` causes re-render in memory
|
|
75
|
+
* - CReact: `setState()` updates persisted outputs for next cycle
|
|
76
|
+
*
|
|
77
|
+
* @param initialValue - Initial output value to declare
|
|
78
|
+
* @returns Tuple of [state, setState] where setState updates the output value
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* function CDNStack({ children }) {
|
|
83
|
+
* const distribution = useInstance('cdn', CloudFrontDistribution, { ... });
|
|
84
|
+
*
|
|
85
|
+
* // Multiple useState calls (like React)
|
|
86
|
+
* const [distributionId, setDistributionId] = useState<string>();
|
|
87
|
+
* const [distributionDomain, setDistributionDomain] = useState<string>();
|
|
88
|
+
* const [distributionArn, setDistributionArn] = useState<string>();
|
|
89
|
+
*
|
|
90
|
+
* // Optional: Enrich outputs after async materialization
|
|
91
|
+
* useEffect(async () => {
|
|
92
|
+
* const actualDomain = await distribution.getDomain();
|
|
93
|
+
* setDistributionDomain(actualDomain);
|
|
94
|
+
* }, [distribution]);
|
|
95
|
+
*
|
|
96
|
+
* // Aggregate outputs for StackContext
|
|
97
|
+
* const outputs = {
|
|
98
|
+
* distributionId,
|
|
99
|
+
* distributionDomain,
|
|
100
|
+
* distributionArn,
|
|
101
|
+
* };
|
|
102
|
+
*
|
|
103
|
+
* return <StackContext.Provider value={outputs}>{children}</StackContext.Provider>;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function useState<T = undefined>(initialValue?: T): [T, (value: T | ((prev: T) => T)) => void];
|
|
108
|
+
/**
|
|
109
|
+
* Check if useState context is available
|
|
110
|
+
* Useful for validation and testing
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export declare function hasStateContext(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Get the current state from Fiber (for testing)
|
|
117
|
+
*
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
export declare function getCurrentState(): Record<string, any> | undefined;
|