@creact-labs/creact 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -349
- package/dist/cli/commands/DevCommand.js +11 -2
- package/dist/cli/index.js +0 -0
- package/dist/core/Reconciler.js +7 -0
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -5,11 +5,6 @@ NOTE: !!! THIS IS A EXPERIMENT OF THOUGHT NOT A PRODUCTION READY PRODUCT !!!
|
|
|
5
5
|
|
|
6
6
|

|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
Think of it as React's rendering model — but instead of a DOM, you render to the cloud.
|
|
10
|
-
|
|
11
|
-
Write your cloud architecture in JSX. CReact figures out the dependencies, orchestrates the deployment, and keeps everything in sync.
|
|
12
|
-
|
|
13
8
|
```tsx
|
|
14
9
|
function App() {
|
|
15
10
|
return (
|
|
@@ -24,351 +19,9 @@ function App() {
|
|
|
24
19
|
}
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## The Problem
|
|
30
|
-
|
|
31
|
-
Terraform and Pulumi are great, but they're static. You write a plan, run it, and hope nothing breaks. If you need dynamic orchestration - like deploying a database, waiting for its endpoint, then using that endpoint in your API config - you're writing bash scripts or custom tooling.
|
|
32
|
-
|
|
33
|
-
CReact treats infrastructure like React treats UI. Components re-render when their dependencies change. Outputs flow naturally through context. The Reconciler figures out what actually needs to deploy.
|
|
34
|
-
|
|
35
|
-
## What Makes It Different
|
|
36
|
-
|
|
37
|
-
**It's reactive** - when a database finishes deploying and outputs its endpoint, components that depend on it automatically re-render and deploy. No manual dependency chains.
|
|
38
|
-
|
|
39
|
-
**It's a compiler** - your JSX compiles to CloudDOM (basically an AST for infrastructure). You can diff it, version it, test it without cloud credentials. Only when you're ready does it materialize to real resources.
|
|
40
|
-
|
|
41
|
-
**It's resilient** - every deployment is checkpointed and resumable. Crash halfway through? Resume from where you left off. The Reconciler only deploys what changed, like React's virtual DOM but for cloud resources.
|
|
42
|
-
|
|
43
|
-
**It works with existing tools** - CReact doesn't replace Terraform or CDK. It orchestrates them. Wrap your Terraform modules as CReact components and compose them together.
|
|
44
|
-
|
|
45
|
-
## Watch It Work
|
|
46
|
-
|
|
47
|
-
Here's a real deployment - 22 resources across 3 regions, 6 reactive cycles, fully automatic:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
$ creact dev --entry reactive-app.tsx --auto-approve
|
|
51
|
-
|
|
52
|
-
Starting CReact development mode...
|
|
53
|
-
Building initial state...
|
|
54
|
-
Changes detected: 2 changes
|
|
55
|
-
|
|
56
|
-
Planning changes for stack: optimized-reactive-app-stack
|
|
57
|
-
──────────────────────────────────────────────────
|
|
58
|
-
+ 2 to create
|
|
59
|
-
+ Analytics (Kinesis stream for API analytics)
|
|
60
|
-
+ VPC (Network foundation)
|
|
61
|
-
|
|
62
|
-
Plan: 2 to add, 0 to change, 0 to destroy
|
|
63
|
-
Auto-approving changes...
|
|
64
|
-
|
|
65
|
-
Applying changes...
|
|
66
|
-
[MockCloud] ✅ All resources materialized
|
|
67
|
-
Apply complete! Resources: 2 added, 0 changed, 0 destroyed
|
|
68
|
-
Duration: 0.3s
|
|
69
|
-
|
|
70
|
-
Reactive changes detected
|
|
71
|
-
+ 6 to create
|
|
72
|
-
+ S3Bucket (Asset storage)
|
|
73
|
-
+ SecurityGroup (Network security)
|
|
74
|
-
+ Subnet × 4 (Multi-AZ subnets)
|
|
75
|
-
|
|
76
|
-
Plan: 6 to add, 0 to change, 0 to destroy
|
|
77
|
-
Reactive deployment cycle #2
|
|
78
|
-
Apply complete! Resources: 6 added, 0 changed, 0 destroyed
|
|
79
|
-
Duration: 0.1s
|
|
80
|
-
|
|
81
|
-
Reactive changes detected
|
|
82
|
-
+ 4 to create
|
|
83
|
-
+ ElastiCache (Redis cluster)
|
|
84
|
-
+ CloudFront (CDN distribution)
|
|
85
|
-
+ LoadBalancer (Application load balancer)
|
|
86
|
-
+ RDSInstance (PostgreSQL database)
|
|
87
|
-
|
|
88
|
-
Reactive deployment cycle #3
|
|
89
|
-
Apply complete! Resources: 4 added, 0 changed, 0 destroyed
|
|
90
|
-
Duration: 0.5s
|
|
91
|
-
|
|
92
|
-
Reactive changes detected
|
|
93
|
-
+ 4 to create
|
|
94
|
-
+ Lambda × 3 (Regional API handlers: us-east-1, eu-west-1, ap-southeast-1)
|
|
95
|
-
+ Backup (Database backup vault)
|
|
96
|
-
|
|
97
|
-
Reactive deployment cycle #4
|
|
98
|
-
Apply complete! Resources: 4 added, 0 changed, 0 destroyed
|
|
99
|
-
Duration: 0.5s
|
|
100
|
-
|
|
101
|
-
Reactive changes detected
|
|
102
|
-
+ 3 to create
|
|
103
|
-
+ ApiGateway × 3 (Regional API endpoints)
|
|
104
|
-
|
|
105
|
-
Reactive deployment cycle #5
|
|
106
|
-
Apply complete! Resources: 3 added, 0 changed, 0 destroyed
|
|
107
|
-
Duration: 0.4s
|
|
108
|
-
|
|
109
|
-
Reactive changes detected
|
|
110
|
-
+ 3 to create
|
|
111
|
-
+ CloudWatch × 3 (Regional monitoring dashboards)
|
|
112
|
-
|
|
113
|
-
Reactive deployment cycle #6
|
|
114
|
-
Apply complete! Resources: 3 added, 0 changed, 0 destroyed
|
|
115
|
-
Duration: 0.4s
|
|
116
|
-
|
|
117
|
-
✅ Deployment complete: 22 resources across 6 reactive cycles
|
|
118
|
-
Watching for changes... (Press Ctrl+C to stop)
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**What happened:**
|
|
122
|
-
|
|
123
|
-
VPC deployed first. When its outputs became available, subnets and security groups deployed in parallel. When those finished, database and cache deployed. When the database endpoint was ready, Lambdas deployed. When Lambdas got function ARNs, API Gateways deployed. When APIs were live, monitoring deployed.
|
|
124
|
-
|
|
125
|
-
You didn't orchestrate anything manually. You just wrote JSX components that reference each other's outputs. CReact figured out the rest.
|
|
126
|
-
|
|
127
|
-
## How It Works
|
|
128
|
-
|
|
129
|
-
### JSX Compiles to CloudDOM
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
<VPC name="app-vpc" cidr="10.0.0.0/16" />
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
becomes:
|
|
136
|
-
|
|
137
|
-
```json
|
|
138
|
-
{
|
|
139
|
-
"id": "app-vpc",
|
|
140
|
-
"construct": "VPC",
|
|
141
|
-
"props": { "cidr": "10.0.0.0/16" },
|
|
142
|
-
"outputs": {}
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
CloudDOM is just a JSON tree. You can diff it like Git, version it, test it without touching AWS. It's the intermediate representation between your code and actual cloud resources.
|
|
147
|
-
|
|
148
|
-
### The Reconciler Figures Out What Changed
|
|
149
|
-
|
|
150
|
-
Before deploying anything, CReact diffs the previous CloudDOM against the new one. Creates, updates, deletes, replacements - all computed ahead of time. You get a Terraform-style plan but it's just comparing two data structures.
|
|
151
|
-
|
|
152
|
-
### Deployment Is Checkpointed
|
|
153
|
-
|
|
154
|
-
After each resource deploys, CReact saves a checkpoint. Crash halfway through? Run it again and it resumes from where it left off. No manual cleanup, no orphaned resources.
|
|
155
|
-
|
|
156
|
-
### Outputs Trigger Re-renders
|
|
157
|
-
|
|
158
|
-
When a resource finishes deploying, its outputs (endpoint, ARN, ID) become available. Any component that depends on those outputs automatically re-renders and creates new resources. It's like useEffect but for infrastructure.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
## Hooks
|
|
163
|
-
|
|
164
|
-
### useInstance - Create Resources
|
|
165
|
-
|
|
166
|
-
Creates cloud resources and provides access to their outputs:
|
|
167
|
-
|
|
168
|
-
```tsx
|
|
169
|
-
function DatabaseComponent() {
|
|
170
|
-
// Create database resource
|
|
171
|
-
const database = useInstance(Database, {
|
|
172
|
-
name: 'my-db',
|
|
173
|
-
engine: 'postgres'
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Access outputs after deployment
|
|
177
|
-
console.log(database.outputs?.endpoint); // "db.example.com:5432"
|
|
178
|
-
|
|
179
|
-
return <></>;
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**Key Behavior**: If any prop value is `undefined`, creates a placeholder node that doesn't deploy. When dependencies become available, component re-renders and creates the real resource.
|
|
184
|
-
|
|
185
|
-
### useState - Persistent State
|
|
186
|
-
|
|
187
|
-
Manages state that persists across deployments:
|
|
188
|
-
|
|
189
|
-
```tsx
|
|
190
|
-
function App() {
|
|
191
|
-
// State survives deployments (stored in backend)
|
|
192
|
-
const [deployCount, setDeployCount] = useState(0);
|
|
193
|
-
const [appVersion] = useState('1.0.0');
|
|
194
|
-
|
|
195
|
-
// Update state (takes effect next deployment)
|
|
196
|
-
setDeployCount(prev => (prev || 0) + 1);
|
|
22
|
+
## Demos
|
|
197
23
|
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Advanced**: Can bind to provider outputs for reactivity:
|
|
203
|
-
|
|
204
|
-
```tsx
|
|
205
|
-
function DatabaseComponent() {
|
|
206
|
-
const db = useInstance(Database, { name: 'my-db' });
|
|
207
|
-
|
|
208
|
-
// This triggers re-renders when db.outputs.endpoint changes
|
|
209
|
-
const [endpoint, setEndpoint] = useState(db.outputs?.endpoint);
|
|
210
|
-
|
|
211
|
-
return <></>;
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### useContext - Share Dependencies
|
|
216
|
-
|
|
217
|
-
Shares data between components with reactivity when containing outputs:
|
|
218
|
-
|
|
219
|
-
```tsx
|
|
220
|
-
const DatabaseContext = createContext<{ endpoint?: string }>({});
|
|
221
|
-
|
|
222
|
-
function DatabaseProvider({ children }) {
|
|
223
|
-
const db = useInstance(Database, { name: 'my-db' });
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<DatabaseContext.Provider value={{
|
|
227
|
-
endpoint: db.outputs?.endpoint // Makes context reactive
|
|
228
|
-
}}>
|
|
229
|
-
{children}
|
|
230
|
-
</DatabaseContext.Provider>
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function ApiConsumer() {
|
|
235
|
-
const { endpoint } = useContext(DatabaseContext); // Re-renders when endpoint available
|
|
236
|
-
|
|
237
|
-
const api = useInstance(ApiGateway, {
|
|
238
|
-
dbUrl: endpoint // Triggers re-render when endpoint changes
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
return <></>;
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Reactivity Mechanisms
|
|
246
|
-
|
|
247
|
-
CReact implements multiple interconnected reactivity systems:
|
|
248
|
-
|
|
249
|
-
1. **useState Binding**: `useState` can bind to provider outputs, triggering re-renders when outputs change
|
|
250
|
-
2. **Context Dependency Tracking**: Contexts containing outputs trigger re-renders in consuming components
|
|
251
|
-
3. **Output Change Detection**: Post-deployment effect system detects when provider outputs change
|
|
252
|
-
4. **Selective Re-rendering**: Only components affected by changes re-render
|
|
253
|
-
|
|
254
|
-
## Deployment Orchestration
|
|
255
|
-
|
|
256
|
-
### Reactive Deployment Cycles
|
|
257
|
-
|
|
258
|
-
```tsx
|
|
259
|
-
function MultiLayerApp() {
|
|
260
|
-
return (
|
|
261
|
-
<NetworkStack> {/* Layer 1: Deploy VPC first */}
|
|
262
|
-
<DatabaseStack> {/* Layer 2: Deploy DB/cache when VPC ready */}
|
|
263
|
-
<StorageStack> {/* Layer 3: Deploy S3/CDN when network ready */}
|
|
264
|
-
<ApiStack> {/* Layer 4: Deploy APIs when all deps ready */}
|
|
265
|
-
<MonitoringStack /> {/* Layer 5: Deploy monitoring last */}
|
|
266
|
-
</ApiStack>
|
|
267
|
-
</StorageStack>
|
|
268
|
-
</DatabaseStack>
|
|
269
|
-
</NetworkStack>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Deployment Flow**:
|
|
275
|
-
1. **Initial Render**: All components create resource references
|
|
276
|
-
2. **Wave 1**: Network resources deploy (no dependencies)
|
|
277
|
-
3. **Wave 2**: Data resources deploy (depend on network outputs)
|
|
278
|
-
4. **Wave 3**: Storage resources deploy (depend on network outputs)
|
|
279
|
-
5. **Wave 4**: API resources deploy (depend on data + storage outputs)
|
|
280
|
-
6. **Wave 5**: Monitoring resources deploy (depend on API outputs)
|
|
281
|
-
|
|
282
|
-
## Provider Implementation
|
|
283
|
-
|
|
284
|
-
### Cloud Provider Example
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
class AWSProvider implements ICloudProvider {
|
|
288
|
-
async materialize(resources: CloudDOMNode[]): Promise<void> {
|
|
289
|
-
for (const resource of resources) {
|
|
290
|
-
if (resource.construct?.name === 'Database') {
|
|
291
|
-
const db = await this.createRDSInstance(resource.props);
|
|
292
|
-
resource.outputs = {
|
|
293
|
-
endpoint: db.endpoint,
|
|
294
|
-
connectionUrl: db.connectionString
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
// Handle other resource types...
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### Backend Provider Example
|
|
304
|
-
|
|
305
|
-
```tsx
|
|
306
|
-
class S3BackendProvider implements IBackendProvider {
|
|
307
|
-
async getState(stackName: string): Promise<any> {
|
|
308
|
-
const state = await this.s3.getObject({
|
|
309
|
-
Bucket: 'my-state-bucket',
|
|
310
|
-
Key: `${stackName}.json`
|
|
311
|
-
}).promise();
|
|
312
|
-
|
|
313
|
-
return JSON.parse(state.Body.toString());
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async saveState(stackName: string, state: any): Promise<void> {
|
|
317
|
-
await this.s3.putObject({
|
|
318
|
-
Bucket: 'my-state-bucket',
|
|
319
|
-
Key: `${stackName}.json`,
|
|
320
|
-
Body: JSON.stringify(state)
|
|
321
|
-
}).promise();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## Real-World Example
|
|
327
|
-
|
|
328
|
-
Here's how the 5-layer enterprise application from the examples works:
|
|
329
|
-
|
|
330
|
-
```tsx
|
|
331
|
-
function EnterpriseApp() {
|
|
332
|
-
return (
|
|
333
|
-
<NetworkProvider> {/* VPC, subnets, security groups */}
|
|
334
|
-
<DatabaseProvider> {/* Database + cache */}
|
|
335
|
-
<StorageProvider> {/* S3 + CDN */}
|
|
336
|
-
<ApiProvider> {/* APIs + load balancer */}
|
|
337
|
-
<MonitoringProvider /> {/* Analytics + backups */}
|
|
338
|
-
</ApiProvider>
|
|
339
|
-
</StorageProvider>
|
|
340
|
-
</DatabaseProvider>
|
|
341
|
-
</NetworkProvider>
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Each provider component:
|
|
346
|
-
function NetworkProvider({ children }) {
|
|
347
|
-
const vpc = useInstance(VPC, { cidr: '10.0.0.0/16' });
|
|
348
|
-
|
|
349
|
-
return (
|
|
350
|
-
<NetworkContext.Provider value={{ vpcId: vpc.outputs?.vpcId }}>
|
|
351
|
-
{children}
|
|
352
|
-
</NetworkContext.Provider>
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function DatabaseProvider({ children }) {
|
|
357
|
-
const { vpcId } = useContext(NetworkContext);
|
|
358
|
-
|
|
359
|
-
const db = useInstance(Database, { vpcId });
|
|
360
|
-
const cache = useInstance(Redis, { vpcId });
|
|
361
|
-
|
|
362
|
-
return (
|
|
363
|
-
<DatabaseContext.Provider value={{
|
|
364
|
-
dbUrl: db.outputs?.endpoint,
|
|
365
|
-
cacheUrl: cache.outputs?.endpoint
|
|
366
|
-
}}>
|
|
367
|
-
{children}
|
|
368
|
-
</DatabaseContext.Provider>
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
```
|
|
24
|
+
Multi env Landing page -> https://github.com/creact-labs/creact-app-demo-multi-env-web-server
|
|
372
25
|
|
|
373
26
|
## Installation & Usage
|
|
374
27
|
|
|
@@ -129,7 +129,6 @@ class DevCommand extends BaseCommand_1.BaseCommand {
|
|
|
129
129
|
try {
|
|
130
130
|
const backendState = await result.instance.getBackendProvider().getState(result.stackName);
|
|
131
131
|
previousCloudDOM = backendState?.cloudDOM || [];
|
|
132
|
-
logger.debug(`DevCommand: Previous CloudDOM loaded with ${previousCloudDOM.length} resources`);
|
|
133
132
|
}
|
|
134
133
|
catch (error) {
|
|
135
134
|
logger.debug(`DevCommand: Could not load previous state: ${error.message}`);
|
|
@@ -184,9 +183,19 @@ class DevCommand extends BaseCommand_1.BaseCommand {
|
|
|
184
183
|
// Create new instance
|
|
185
184
|
const result = await CLIContext_1.CLIContextManager.createCLIInstance(entryPath, this.verbose);
|
|
186
185
|
logger.debug(`DevCommand: Hot reload CloudDOM built with ${result.cloudDOM.length} resources`);
|
|
186
|
+
// Load drift-corrected state for comparison
|
|
187
|
+
let previousCloudDOM = this.state.lastCloudDOM;
|
|
188
|
+
try {
|
|
189
|
+
const backendState = await result.instance.getBackendProvider().getState(result.stackName);
|
|
190
|
+
previousCloudDOM = backendState?.cloudDOM || this.state.lastCloudDOM;
|
|
191
|
+
logger.debug(`DevCommand: Comparing against drift-corrected state with ${previousCloudDOM?.length || 0} resources`);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
logger.debug(`DevCommand: Using cached state for comparison: ${error.message}`);
|
|
195
|
+
}
|
|
187
196
|
// Compute diff using Reconciler
|
|
188
197
|
const reconciler = new Reconciler_1.Reconciler();
|
|
189
|
-
const changeSet = reconciler.reconcile(
|
|
198
|
+
const changeSet = reconciler.reconcile(previousCloudDOM, result.cloudDOM);
|
|
190
199
|
const totalChanges = (0, Reconciler_1.getTotalChanges)(changeSet);
|
|
191
200
|
logger.debug(`DevCommand: Hot reload total changes: ${totalChanges}`);
|
|
192
201
|
// Always update state to preserve reactive changes
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
package/dist/core/Reconciler.js
CHANGED
|
@@ -580,6 +580,13 @@ class Reconciler {
|
|
|
580
580
|
});
|
|
581
581
|
return 'replacement';
|
|
582
582
|
}
|
|
583
|
+
// DRIFT DETECTION: If previous node has no outputs, it needs redeployment
|
|
584
|
+
// This happens when drift detection clears outputs for dead resources
|
|
585
|
+
// Treat as update to trigger redeployment
|
|
586
|
+
if (!previous.outputs || Object.keys(previous.outputs).length === 0) {
|
|
587
|
+
logger.debug(`Node ${current.id} has no outputs (drift detected) - needs redeployment`);
|
|
588
|
+
return 'update';
|
|
589
|
+
}
|
|
583
590
|
// Hash-based diff acceleration: compute or reuse cached hashes
|
|
584
591
|
const prevHash = ((_a = previous)._propHash ?? (_a._propHash = this.computeShallowHash(previous.props)));
|
|
585
592
|
const currHash = ((_b = current)._propHash ?? (_b._propHash = this.computeShallowHash(current.props)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@creact-labs/creact",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "CReact - React for Infrastructure. Render JSX to CloudDOM.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -82,9 +82,6 @@
|
|
|
82
82
|
"typescript-eslint": "^8.45.0",
|
|
83
83
|
"vitest": "^3.2.4"
|
|
84
84
|
},
|
|
85
|
-
"peerDependencies": {
|
|
86
|
-
"react": "^18.0.0"
|
|
87
|
-
},
|
|
88
85
|
"dependencies": {
|
|
89
86
|
"chalk": "^4.1.2",
|
|
90
87
|
"cli-progress": "^3.12.0",
|