@creact-labs/creact 0.1.7 → 0.2.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 (146) hide show
  1. package/README.md +72 -21
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.js +88 -0
  4. package/dist/index.d.ts +19 -44
  5. package/dist/index.js +20 -68
  6. package/dist/jsx/index.d.ts +2 -0
  7. package/dist/jsx/index.js +1 -0
  8. package/dist/jsx/jsx-dev-runtime.d.ts +4 -0
  9. package/dist/jsx/jsx-dev-runtime.js +4 -0
  10. package/dist/jsx/jsx-runtime.d.ts +38 -0
  11. package/dist/jsx/jsx-runtime.js +38 -0
  12. package/dist/jsx/types.d.ts +12 -0
  13. package/dist/jsx/types.js +4 -0
  14. package/dist/primitives/context.d.ts +34 -0
  15. package/dist/primitives/context.js +63 -0
  16. package/dist/primitives/index.d.ts +3 -0
  17. package/dist/primitives/index.js +3 -0
  18. package/dist/primitives/instance.d.ts +72 -0
  19. package/dist/primitives/instance.js +235 -0
  20. package/dist/primitives/store.d.ts +22 -0
  21. package/dist/primitives/store.js +97 -0
  22. package/dist/provider/backend.d.ts +110 -0
  23. package/dist/provider/backend.js +37 -0
  24. package/dist/provider/interface.d.ts +48 -0
  25. package/dist/provider/interface.js +39 -0
  26. package/dist/reactive/effect.d.ts +11 -0
  27. package/dist/reactive/effect.js +42 -0
  28. package/dist/reactive/index.d.ts +3 -0
  29. package/dist/reactive/index.js +3 -0
  30. package/dist/reactive/signal.d.ts +32 -0
  31. package/dist/reactive/signal.js +60 -0
  32. package/dist/reactive/tracking.d.ts +41 -0
  33. package/dist/reactive/tracking.js +161 -0
  34. package/dist/runtime/fiber.d.ts +21 -0
  35. package/dist/runtime/fiber.js +16 -0
  36. package/dist/runtime/index.d.ts +4 -0
  37. package/dist/runtime/index.js +4 -0
  38. package/dist/runtime/reconcile.d.ts +66 -0
  39. package/dist/runtime/reconcile.js +210 -0
  40. package/dist/runtime/render.d.ts +42 -0
  41. package/dist/runtime/render.js +231 -0
  42. package/dist/runtime/run.d.ts +119 -0
  43. package/dist/runtime/run.js +334 -0
  44. package/dist/runtime/state-machine.d.ts +95 -0
  45. package/dist/runtime/state-machine.js +209 -0
  46. package/dist/types.d.ts +13 -0
  47. package/dist/types.js +4 -0
  48. package/package.json +11 -27
  49. package/dist/cli/commands/BuildCommand.d.ts +0 -40
  50. package/dist/cli/commands/BuildCommand.js +0 -151
  51. package/dist/cli/commands/DeployCommand.d.ts +0 -38
  52. package/dist/cli/commands/DeployCommand.js +0 -194
  53. package/dist/cli/commands/DevCommand.d.ts +0 -52
  54. package/dist/cli/commands/DevCommand.js +0 -394
  55. package/dist/cli/commands/PlanCommand.d.ts +0 -39
  56. package/dist/cli/commands/PlanCommand.js +0 -164
  57. package/dist/cli/commands/index.d.ts +0 -36
  58. package/dist/cli/commands/index.js +0 -43
  59. package/dist/cli/core/ArgumentParser.d.ts +0 -46
  60. package/dist/cli/core/ArgumentParser.js +0 -127
  61. package/dist/cli/core/BaseCommand.d.ts +0 -75
  62. package/dist/cli/core/BaseCommand.js +0 -95
  63. package/dist/cli/core/CLIContext.d.ts +0 -68
  64. package/dist/cli/core/CLIContext.js +0 -183
  65. package/dist/cli/core/CommandRegistry.d.ts +0 -64
  66. package/dist/cli/core/CommandRegistry.js +0 -89
  67. package/dist/cli/core/index.d.ts +0 -36
  68. package/dist/cli/core/index.js +0 -43
  69. package/dist/cli/index.d.ts +0 -35
  70. package/dist/cli/index.js +0 -100
  71. package/dist/cli/output.d.ts +0 -204
  72. package/dist/cli/output.js +0 -437
  73. package/dist/cli/utils.d.ts +0 -59
  74. package/dist/cli/utils.js +0 -76
  75. package/dist/context/createContext.d.ts +0 -90
  76. package/dist/context/createContext.js +0 -113
  77. package/dist/context/index.d.ts +0 -30
  78. package/dist/context/index.js +0 -35
  79. package/dist/core/CReact.d.ts +0 -409
  80. package/dist/core/CReact.js +0 -1151
  81. package/dist/core/CloudDOMBuilder.d.ts +0 -447
  82. package/dist/core/CloudDOMBuilder.js +0 -1234
  83. package/dist/core/ContextDependencyTracker.d.ts +0 -165
  84. package/dist/core/ContextDependencyTracker.js +0 -448
  85. package/dist/core/ErrorRecoveryManager.d.ts +0 -145
  86. package/dist/core/ErrorRecoveryManager.js +0 -443
  87. package/dist/core/EventBus.d.ts +0 -91
  88. package/dist/core/EventBus.js +0 -185
  89. package/dist/core/ProviderOutputTracker.d.ts +0 -211
  90. package/dist/core/ProviderOutputTracker.js +0 -476
  91. package/dist/core/ReactiveUpdateQueue.d.ts +0 -76
  92. package/dist/core/ReactiveUpdateQueue.js +0 -121
  93. package/dist/core/Reconciler.d.ts +0 -415
  94. package/dist/core/Reconciler.js +0 -1044
  95. package/dist/core/RenderScheduler.d.ts +0 -153
  96. package/dist/core/RenderScheduler.js +0 -519
  97. package/dist/core/Renderer.d.ts +0 -336
  98. package/dist/core/Renderer.js +0 -944
  99. package/dist/core/Runtime.d.ts +0 -246
  100. package/dist/core/Runtime.js +0 -640
  101. package/dist/core/StateBindingManager.d.ts +0 -121
  102. package/dist/core/StateBindingManager.js +0 -309
  103. package/dist/core/StateMachine.d.ts +0 -441
  104. package/dist/core/StateMachine.js +0 -883
  105. package/dist/core/StructuralChangeDetector.d.ts +0 -140
  106. package/dist/core/StructuralChangeDetector.js +0 -363
  107. package/dist/core/Validator.d.ts +0 -127
  108. package/dist/core/Validator.js +0 -279
  109. package/dist/core/errors.d.ts +0 -153
  110. package/dist/core/errors.js +0 -202
  111. package/dist/core/index.d.ts +0 -38
  112. package/dist/core/index.js +0 -64
  113. package/dist/core/types.d.ts +0 -265
  114. package/dist/core/types.js +0 -48
  115. package/dist/hooks/context.d.ts +0 -147
  116. package/dist/hooks/context.js +0 -334
  117. package/dist/hooks/useContext.d.ts +0 -113
  118. package/dist/hooks/useContext.js +0 -169
  119. package/dist/hooks/useEffect.d.ts +0 -105
  120. package/dist/hooks/useEffect.js +0 -540
  121. package/dist/hooks/useInstance.d.ts +0 -139
  122. package/dist/hooks/useInstance.js +0 -455
  123. package/dist/hooks/useState.d.ts +0 -120
  124. package/dist/hooks/useState.js +0 -298
  125. package/dist/jsx.d.ts +0 -143
  126. package/dist/jsx.js +0 -76
  127. package/dist/providers/DummyBackendProvider.d.ts +0 -193
  128. package/dist/providers/DummyBackendProvider.js +0 -189
  129. package/dist/providers/DummyCloudProvider.d.ts +0 -128
  130. package/dist/providers/DummyCloudProvider.js +0 -157
  131. package/dist/providers/IBackendProvider.d.ts +0 -177
  132. package/dist/providers/IBackendProvider.js +0 -31
  133. package/dist/providers/ICloudProvider.d.ts +0 -230
  134. package/dist/providers/ICloudProvider.js +0 -31
  135. package/dist/providers/index.d.ts +0 -31
  136. package/dist/providers/index.js +0 -31
  137. package/dist/test-event-callbacks.d.ts +0 -0
  138. package/dist/test-event-callbacks.js +0 -1
  139. package/dist/utils/Logger.d.ts +0 -144
  140. package/dist/utils/Logger.js +0 -220
  141. package/dist/utils/Output.d.ts +0 -161
  142. package/dist/utils/Output.js +0 -401
  143. package/dist/utils/deepEqual.d.ts +0 -71
  144. package/dist/utils/deepEqual.js +0 -276
  145. package/dist/utils/naming.d.ts +0 -241
  146. package/dist/utils/naming.js +0 -376
@@ -0,0 +1,235 @@
1
+ /**
2
+ * useInstance - bind to a provider and get reactive outputs
3
+ */
4
+ import { createSignal } from '../reactive/signal';
5
+ import { batch } from '../reactive/tracking';
6
+ import { getCurrentFiber, getCurrentResourcePath, pushResourcePath } from '../runtime/render';
7
+ // Registry of all instance nodes by ID
8
+ const nodeRegistry = new Map();
9
+ // Track which fiber owns each nodeId (to detect duplicate siblings)
10
+ const nodeOwnership = new Map();
11
+ // Output hydration map - populated before render to restore outputs from previous run
12
+ const outputHydrationMap = new Map();
13
+ /**
14
+ * Prepare output hydration from serialized nodes
15
+ * Called by runtime BEFORE rendering to restore outputs
16
+ */
17
+ export function prepareOutputHydration(serializedNodes) {
18
+ outputHydrationMap.clear();
19
+ for (const node of serializedNodes) {
20
+ if (node.outputs && Object.keys(node.outputs).length > 0) {
21
+ outputHydrationMap.set(node.id, node.outputs);
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Clear output hydration map
27
+ */
28
+ export function clearOutputHydration() {
29
+ outputHydrationMap.clear();
30
+ }
31
+ /**
32
+ * Create a placeholder proxy that returns undefined for all outputs
33
+ * Used when props have undefined dependencies - node won't be created yet
34
+ */
35
+ function createPlaceholderProxy() {
36
+ return new Proxy({}, {
37
+ get(_target, _key) {
38
+ return () => undefined; // All outputs return undefined
39
+ },
40
+ });
41
+ }
42
+ /**
43
+ * Create an instance bound to a provider
44
+ */
45
+ export function useInstance(construct, props) {
46
+ const fiber = getCurrentFiber();
47
+ if (!fiber) {
48
+ throw new Error('useInstance must be called during render');
49
+ }
50
+ // Enforce one instance per component - forces proper JSX composition
51
+ // Each component = one resource, compose via children
52
+ if (fiber.instanceNodes.length > 0 || fiber.hasPlaceholderInstance) {
53
+ throw new Error('useInstance can only be called once per component. ' +
54
+ 'Use child components for additional resources:\n\n' +
55
+ ' function MyStack() {\n' +
56
+ ' const db = useInstance(Database, { name: "main" });\n' +
57
+ ' return <Cache dbUrl={db.url()} />;\n' +
58
+ ' }\n\n' +
59
+ ' function Cache({ dbUrl }) {\n' +
60
+ ' useInstance(CacheService, { db: dbUrl });\n' +
61
+ ' return null;\n' +
62
+ ' }');
63
+ }
64
+ // Generate deterministic ID using resource path (not fiber path)
65
+ // This makes wrapper components transparent - they don't affect resource IDs
66
+ const currentResourcePath = getCurrentResourcePath();
67
+ const name = getNodeName(construct, fiber.key);
68
+ const fullPath = [...currentResourcePath, name];
69
+ const nodeId = fullPath.join('.');
70
+ // Check for undefined dependencies BEFORE creating/updating node
71
+ // If any prop is undefined, return placeholder - don't create node in registry
72
+ const hasUndefinedDeps = Object.values(props).some((v) => v === undefined);
73
+ if (hasUndefinedDeps) {
74
+ // STILL push to resource path so children have correct paths
75
+ pushResourcePath(name);
76
+ // Mark fiber as having a placeholder instance (for proper path pop in render.ts)
77
+ fiber.hasPlaceholderInstance = true;
78
+ return createPlaceholderProxy();
79
+ }
80
+ const constructType = construct.name || 'Unknown';
81
+ // Check for duplicate siblings (requires keys)
82
+ // Allow same fiber to re-register (reactive re-render), but not different fibers
83
+ const existingOwner = nodeOwnership.get(nodeId);
84
+ if (existingOwner && existingOwner !== fiber) {
85
+ throw new Error(`Multiple instances of ${constructType} at the same level require unique keys.\n` +
86
+ `Add a key prop to differentiate them:\n\n` +
87
+ ` {items.map((item) => (\n` +
88
+ ` <MyResource key={item.id} ... />\n` +
89
+ ` ))}`);
90
+ }
91
+ nodeOwnership.set(nodeId, fiber);
92
+ // Create or get existing node
93
+ let node = nodeRegistry.get(nodeId);
94
+ if (!node) {
95
+ node = {
96
+ id: nodeId,
97
+ path: fullPath,
98
+ construct,
99
+ constructType,
100
+ props,
101
+ outputSignals: new Map(),
102
+ children: [],
103
+ setOutputs(outputs) {
104
+ // Clear ownership before batch triggers re-renders
105
+ // When signals update, components re-execute and create new fibers
106
+ // that need to claim the same nodeIds
107
+ nodeOwnership.clear();
108
+ batch(() => {
109
+ for (const [key, value] of Object.entries(outputs)) {
110
+ if (!this.outputSignals.has(key)) {
111
+ this.outputSignals.set(key, createSignal(value));
112
+ }
113
+ else {
114
+ const [, write] = this.outputSignals.get(key);
115
+ write(value);
116
+ }
117
+ }
118
+ });
119
+ },
120
+ };
121
+ nodeRegistry.set(nodeId, node);
122
+ }
123
+ else {
124
+ // Update props
125
+ node.props = props;
126
+ }
127
+ // Push to resource path so children see this component in their path
128
+ pushResourcePath(name);
129
+ // Hydrate outputs from previous run (if available)
130
+ const hydratedOutputs = outputHydrationMap.get(nodeId);
131
+ if (hydratedOutputs) {
132
+ for (const [key, value] of Object.entries(hydratedOutputs)) {
133
+ if (!node.outputSignals.has(key)) {
134
+ node.outputSignals.set(key, createSignal(value));
135
+ }
136
+ else {
137
+ const [, write] = node.outputSignals.get(key);
138
+ write(value);
139
+ }
140
+ }
141
+ }
142
+ // Attach to fiber
143
+ fiber.instanceNodes.push(node);
144
+ // Return proxy where each property access returns a wrapper function
145
+ // that auto-unwraps accessors (SolidJS-style)
146
+ return new Proxy({}, {
147
+ get(_, key) {
148
+ // Lazily create signal for this output
149
+ if (!node.outputSignals.has(key)) {
150
+ node.outputSignals.set(key, createSignal());
151
+ }
152
+ // biome-ignore lint/style/noNonNullAssertion: we just ensured the key exists above
153
+ const [read] = node.outputSignals.get(key);
154
+ // Return wrapper that auto-unwraps accessors
155
+ return () => {
156
+ const value = read(); // Read from signal (tracks it)
157
+ // If provider returned an accessor, call it (tracks provider's signal)
158
+ if (typeof value === 'function') {
159
+ return value();
160
+ }
161
+ return value;
162
+ };
163
+ },
164
+ });
165
+ }
166
+ /**
167
+ * Generate a node name from construct and fiber key
168
+ */
169
+ function getNodeName(construct, key) {
170
+ const baseName = construct.name || 'Instance';
171
+ const kebab = toKebabCase(baseName);
172
+ if (key !== undefined) {
173
+ return `${kebab}-${key}`;
174
+ }
175
+ return kebab;
176
+ }
177
+ /**
178
+ * Convert PascalCase to kebab-case
179
+ */
180
+ function toKebabCase(str) {
181
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
182
+ }
183
+ /**
184
+ * Fill instance outputs (called by runtime after provider returns)
185
+ * @internal
186
+ */
187
+ export function fillInstanceOutputs(nodeId, outputs) {
188
+ const node = nodeRegistry.get(nodeId);
189
+ if (!node)
190
+ return;
191
+ // Clear ownership before batch triggers re-renders
192
+ // When signals update, components re-execute and create new fibers
193
+ // that need to claim the same nodeIds
194
+ nodeOwnership.clear();
195
+ batch(() => {
196
+ for (const [key, value] of Object.entries(outputs)) {
197
+ if (node.outputSignals.has(key)) {
198
+ const [, write] = node.outputSignals.get(key);
199
+ write(value); // Write value (plain or accessor) to signal
200
+ }
201
+ else {
202
+ node.outputSignals.set(key, createSignal(value));
203
+ }
204
+ }
205
+ });
206
+ }
207
+ /**
208
+ * Get a node by ID
209
+ * @internal
210
+ */
211
+ export function getNodeById(nodeId) {
212
+ return nodeRegistry.get(nodeId);
213
+ }
214
+ /**
215
+ * Get all registered nodes
216
+ * @internal
217
+ */
218
+ export function getAllNodes() {
219
+ return Array.from(nodeRegistry.values());
220
+ }
221
+ /**
222
+ * Clear node registry (for testing)
223
+ * @internal
224
+ */
225
+ export function clearNodeRegistry() {
226
+ nodeRegistry.clear();
227
+ nodeOwnership.clear();
228
+ }
229
+ /**
230
+ * Clear node ownership (call at start of each render pass)
231
+ * @internal
232
+ */
233
+ export function clearNodeOwnership() {
234
+ nodeOwnership.clear();
235
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Store - persistent state (non-reactive)
3
+ */
4
+ export type SetStoreFunction<T> = {
5
+ <K extends keyof T>(key: K, value: T[K] | ((prev: T[K]) => T[K])): void;
6
+ <K1 extends keyof T, K2 extends keyof T[K1]>(k1: K1, k2: K2, value: T[K1][K2] | ((prev: T[K1][K2]) => T[K1][K2])): void;
7
+ (...args: any[]): void;
8
+ };
9
+ /**
10
+ * Create a persistent store (non-reactive)
11
+ */
12
+ export declare function createStore<T extends object>(initial: T): [T, SetStoreFunction<T>];
13
+ /**
14
+ * Prepare hydration from previous nodes
15
+ * @internal
16
+ */
17
+ export declare function prepareHydration(previousNodes: any[]): void;
18
+ /**
19
+ * Clear hydration map (for testing)
20
+ * @internal
21
+ */
22
+ export declare function clearHydration(): void;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Store - persistent state (non-reactive)
3
+ */
4
+ import { getCurrentFiber } from '../runtime/render';
5
+ // Hydration map for restoring state across cycles
6
+ // biome-ignore lint/suspicious/noExplicitAny: stores hold user-defined state of any type
7
+ const hydrationMap = new Map();
8
+ /**
9
+ * Create a persistent store (non-reactive)
10
+ */
11
+ export function createStore(initial) {
12
+ const fiber = getCurrentFiber();
13
+ // Try to hydrate from previous cycle
14
+ const hydrated = fiber ? hydrateStore(fiber.path) : undefined;
15
+ const state = hydrated ?? { ...initial };
16
+ // Mark for persistence
17
+ if (fiber) {
18
+ fiber.store = state;
19
+ }
20
+ // biome-ignore lint/suspicious/noExplicitAny: rest args for deep path updates
21
+ function setStore(...args) {
22
+ updatePath(state, args);
23
+ }
24
+ return [state, setStore];
25
+ }
26
+ /**
27
+ * Update a nested path in an object
28
+ */
29
+ // biome-ignore lint/suspicious/noExplicitAny: operates on arbitrary nested objects
30
+ function updatePath(obj, args) {
31
+ if (args.length === 2) {
32
+ const [key, value] = args;
33
+ obj[key] = typeof value === 'function' ? value(obj[key]) : value;
34
+ }
35
+ else if (args.length > 2) {
36
+ const key = args[0];
37
+ if (obj[key] === undefined) {
38
+ obj[key] = {};
39
+ }
40
+ updatePath(obj[key], args.slice(1));
41
+ }
42
+ }
43
+ /**
44
+ * Prepare hydration from previous nodes
45
+ * @internal
46
+ */
47
+ // biome-ignore lint/suspicious/noExplicitAny: accepts serialized nodes with arbitrary structure
48
+ export function prepareHydration(previousNodes) {
49
+ hydrationMap.clear();
50
+ for (const node of flattenNodes(previousNodes)) {
51
+ if (node.store) {
52
+ // Key by component path (parent of node)
53
+ const componentPath = node.path.slice(0, -1).join('.');
54
+ hydrationMap.set(componentPath, node.store);
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Hydrate store from previous cycle
60
+ * Returns a deep clone to ensure previous and current stores are independent
61
+ */
62
+ function hydrateStore(fiberPath) {
63
+ if (!fiberPath)
64
+ return undefined;
65
+ const key = fiberPath.join('.');
66
+ const stored = hydrationMap.get(key);
67
+ // Deep clone to ensure independence between runs
68
+ return stored ? JSON.parse(JSON.stringify(stored)) : undefined;
69
+ }
70
+ /**
71
+ * Flatten nested nodes
72
+ */
73
+ // biome-ignore lint/suspicious/noExplicitAny: operates on serialized nodes with arbitrary structure
74
+ function flattenNodes(nodes) {
75
+ // biome-ignore lint/suspicious/noExplicitAny: accumulates serialized nodes
76
+ const result = [];
77
+ // biome-ignore lint/suspicious/noExplicitAny: serialized node structure
78
+ function walk(node) {
79
+ result.push(node);
80
+ if (node.children) {
81
+ for (const child of node.children) {
82
+ walk(child);
83
+ }
84
+ }
85
+ }
86
+ for (const node of nodes) {
87
+ walk(node);
88
+ }
89
+ return result;
90
+ }
91
+ /**
92
+ * Clear hydration map (for testing)
93
+ * @internal
94
+ */
95
+ export function clearHydration() {
96
+ hydrationMap.clear();
97
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Backend interface - abstracts state persistence
3
+ *
4
+ * The Backend enables:
5
+ * - Resume from crash (nodes with outputs are treated as deployed)
6
+ * - Incremental deploys (only changed resources)
7
+ * - State hydration (createStore values restored)
8
+ * - Drift detection (compare expected vs actual)
9
+ */
10
+ import type { InstanceNode } from '../primitives/instance';
11
+ /**
12
+ * Resource deployment state
13
+ */
14
+ export type ResourceState = 'pending' | 'applying' | 'deployed' | 'failed';
15
+ /**
16
+ * Deployment lifecycle status
17
+ */
18
+ export type DeploymentStatus = 'pending' | 'applying' | 'deployed' | 'failed';
19
+ /**
20
+ * Change set with deployment ordering
21
+ */
22
+ export interface ChangeSet {
23
+ creates: InstanceNode[];
24
+ updates: InstanceNode[];
25
+ deletes: InstanceNode[];
26
+ deploymentOrder: string[];
27
+ parallelBatches: string[][];
28
+ }
29
+ /**
30
+ * Serializable node state for persistence
31
+ */
32
+ export interface SerializedNode {
33
+ id: string;
34
+ path: string[];
35
+ constructType: string;
36
+ props: Record<string, any>;
37
+ outputs?: Record<string, any>;
38
+ state?: ResourceState;
39
+ store?: any;
40
+ }
41
+ /**
42
+ * Deployment state - persisted by backend
43
+ */
44
+ export interface DeploymentState {
45
+ nodes: SerializedNode[];
46
+ status: DeploymentStatus;
47
+ applyingNodeId?: string;
48
+ stackName: string;
49
+ lastDeployedAt: number;
50
+ user?: string;
51
+ storeValues?: Record<string, any>;
52
+ }
53
+ /**
54
+ * Audit log entry for tracking deployment history
55
+ */
56
+ export interface AuditLogEntry {
57
+ timestamp: number;
58
+ action: 'deploy_start' | 'deploy_complete' | 'deploy_failed' | 'resource_applied' | 'resource_destroyed';
59
+ nodeId?: string;
60
+ details?: Record<string, any>;
61
+ user?: string;
62
+ }
63
+ /**
64
+ * Backend interface - implement this to persist deployment state
65
+ *
66
+ * Example implementations:
67
+ * - InMemoryBackend: For testing
68
+ * - FileBackend: Local JSON file
69
+ * - DynamoDBBackend: AWS DynamoDB
70
+ * - S3Backend: AWS S3
71
+ * - PostgresBackend: Database
72
+ */
73
+ export interface Backend {
74
+ /**
75
+ * Get current deployment state for a stack
76
+ */
77
+ getState(stackName: string): Promise<DeploymentState | null>;
78
+ /**
79
+ * Save deployment state
80
+ */
81
+ saveState(stackName: string, state: DeploymentState): Promise<void>;
82
+ /**
83
+ * Optional: Acquire lock for concurrent deploy protection
84
+ * @param stackName - Stack to lock
85
+ * @param holder - Identity of lock holder (e.g., deployment ID)
86
+ * @param ttlSeconds - Lock TTL in seconds
87
+ * @returns true if lock acquired, false if already locked
88
+ */
89
+ acquireLock?(stackName: string, holder: string, ttlSeconds: number): Promise<boolean>;
90
+ /**
91
+ * Optional: Release deployment lock
92
+ */
93
+ releaseLock?(stackName: string): Promise<void>;
94
+ /**
95
+ * Optional: Append to audit trail
96
+ */
97
+ appendAuditLog?(stackName: string, entry: AuditLogEntry): Promise<void>;
98
+ /**
99
+ * Optional: Get audit log entries
100
+ */
101
+ getAuditLog?(stackName: string, limit?: number): Promise<AuditLogEntry[]>;
102
+ }
103
+ /**
104
+ * Serialize an InstanceNode for persistence
105
+ */
106
+ export declare function serializeNode(node: InstanceNode): SerializedNode;
107
+ /**
108
+ * Serialize multiple nodes
109
+ */
110
+ export declare function serializeNodes(nodes: InstanceNode[]): SerializedNode[];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Backend interface - abstracts state persistence
3
+ *
4
+ * The Backend enables:
5
+ * - Resume from crash (nodes with outputs are treated as deployed)
6
+ * - Incremental deploys (only changed resources)
7
+ * - State hydration (createStore values restored)
8
+ * - Drift detection (compare expected vs actual)
9
+ */
10
+ /**
11
+ * Serialize an InstanceNode for persistence
12
+ */
13
+ export function serializeNode(node) {
14
+ // Extract current output values from signals
15
+ // biome-ignore lint/suspicious/noExplicitAny: outputs are provider-returned with arbitrary types
16
+ const outputs = {};
17
+ for (const [key, [read]] of node.outputSignals) {
18
+ const value = read();
19
+ if (value !== undefined) {
20
+ outputs[key] = value;
21
+ }
22
+ }
23
+ return {
24
+ id: node.id,
25
+ path: node.path,
26
+ constructType: node.constructType,
27
+ props: node.props,
28
+ outputs: Object.keys(outputs).length > 0 ? outputs : undefined,
29
+ store: node.store,
30
+ };
31
+ }
32
+ /**
33
+ * Serialize multiple nodes
34
+ */
35
+ export function serializeNodes(nodes) {
36
+ return nodes.map(serializeNode);
37
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Provider interface - abstracts the execution environment
3
+ */
4
+ import type { InstanceNode } from '../primitives/instance';
5
+ /**
6
+ * Event emitted when provider detects output changes
7
+ */
8
+ export interface OutputChangeEvent {
9
+ resourceName: string;
10
+ outputs: Record<string, any>;
11
+ timestamp: number;
12
+ }
13
+ /**
14
+ * Provider interface - implement this to connect to any backend
15
+ */
16
+ export interface Provider {
17
+ /** Initialize provider (async setup) */
18
+ initialize?(): Promise<void>;
19
+ /**
20
+ * Materialize nodes into cloud resources.
21
+ *
22
+ * Providers should call node.setOutputs() when outputs are available.
23
+ * This triggers reactive updates - dependent components re-render automatically.
24
+ *
25
+ * For sync providers: call node.setOutputs() immediately
26
+ * For async providers: call node.setOutputs() in the async callback
27
+ *
28
+ * Returns a Promise that resolves when all resources are materialized.
29
+ */
30
+ materialize(nodes: InstanceNode[]): Promise<void>;
31
+ /** Destroy a node */
32
+ destroy(node: InstanceNode): Promise<void>;
33
+ /** Lifecycle hooks */
34
+ preDeploy?(nodes: InstanceNode[]): Promise<void>;
35
+ postDeploy?(nodes: InstanceNode[], outputs: Record<string, any>): Promise<void>;
36
+ onError?(error: Error, nodes: InstanceNode[]): Promise<void>;
37
+ /** Event system (required for continuous runtime) */
38
+ on(event: 'outputsChanged', handler: (change: OutputChangeEvent) => void): void;
39
+ off(event: 'outputsChanged', handler: (change: OutputChangeEvent) => void): void;
40
+ stop(): void;
41
+ }
42
+ /**
43
+ * Create a mock provider for testing
44
+ */
45
+ export declare function createMockProvider(handlers?: Partial<{
46
+ materialize: (nodes: InstanceNode[]) => Promise<void> | void;
47
+ destroy: (node: InstanceNode) => Promise<void> | void;
48
+ }>): Provider;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Provider interface - abstracts the execution environment
3
+ */
4
+ /**
5
+ * Create a mock provider for testing
6
+ */
7
+ export function createMockProvider(handlers = {}) {
8
+ const eventHandlers = new Map();
9
+ return {
10
+ async materialize(nodes) {
11
+ if (handlers.materialize) {
12
+ await handlers.materialize(nodes);
13
+ }
14
+ else {
15
+ // Default: set empty outputs via reactive API
16
+ for (const node of nodes) {
17
+ node.setOutputs({});
18
+ }
19
+ }
20
+ },
21
+ async destroy(node) {
22
+ if (handlers.destroy) {
23
+ await handlers.destroy(node);
24
+ }
25
+ },
26
+ on(event, handler) {
27
+ if (!eventHandlers.has(event)) {
28
+ eventHandlers.set(event, new Set());
29
+ }
30
+ eventHandlers.get(event)?.add(handler);
31
+ },
32
+ off(event, handler) {
33
+ eventHandlers.get(event)?.delete(handler);
34
+ },
35
+ stop() {
36
+ eventHandlers.clear();
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * createEffect - run side effects when dependencies change
3
+ */
4
+ /**
5
+ * Create a reactive effect that runs when dependencies change
6
+ */
7
+ export declare function createEffect(fn: () => undefined | (() => void)): void;
8
+ /**
9
+ * Register a cleanup function for the current computation
10
+ */
11
+ export declare function onCleanup(fn: () => void): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * createEffect - run side effects when dependencies change
3
+ */
4
+ import { getListener, runComputation } from './tracking';
5
+ /**
6
+ * Create a reactive effect that runs when dependencies change
7
+ */
8
+ export function createEffect(fn) {
9
+ const computation = {
10
+ fn: () => {
11
+ const cleanup = fn();
12
+ if (typeof cleanup === 'function') {
13
+ if (!computation.cleanups) {
14
+ computation.cleanups = [cleanup];
15
+ }
16
+ else {
17
+ computation.cleanups.push(cleanup);
18
+ }
19
+ }
20
+ },
21
+ sources: null,
22
+ sourceSlots: null,
23
+ state: 1, // STALE - needs initial run
24
+ cleanups: null,
25
+ };
26
+ // Run immediately
27
+ runComputation(computation);
28
+ }
29
+ /**
30
+ * Register a cleanup function for the current computation
31
+ */
32
+ export function onCleanup(fn) {
33
+ const listener = getListener();
34
+ if (listener) {
35
+ if (!listener.cleanups) {
36
+ listener.cleanups = [fn];
37
+ }
38
+ else {
39
+ listener.cleanups.push(fn);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,3 @@
1
+ export { createEffect, onCleanup } from './effect';
2
+ export { type Accessor, type Computation, createSignal, type Setter, type Signal } from './signal';
3
+ export { batch, cleanComputation, flushSync, getListener, runComputation, scheduleComputation, setListener, untrack, } from './tracking';
@@ -0,0 +1,3 @@
1
+ export { createEffect, onCleanup } from './effect';
2
+ export { createSignal } from './signal';
3
+ export { batch, cleanComputation, flushSync, getListener, runComputation, scheduleComputation, setListener, untrack, } from './tracking';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Signal - core reactive primitive (internal)
3
+ * Inspired by SolidJS createSignal
4
+ */
5
+ /**
6
+ * Signal state - holds value and tracks observers
7
+ */
8
+ export interface Signal<T> {
9
+ value: T | undefined;
10
+ observers: Computation<any>[] | null;
11
+ observerSlots: number[] | null;
12
+ }
13
+ /**
14
+ * Computation - tracks dependencies and re-runs when they change
15
+ */
16
+ export interface Computation<T> {
17
+ fn: () => T;
18
+ sources: Signal<any>[] | null;
19
+ sourceSlots: number[] | null;
20
+ state: 0 | 1 | 2;
21
+ cleanups: (() => void)[] | null;
22
+ }
23
+ export type Accessor<T> = () => T | undefined;
24
+ export type Setter<T> = (value: T) => void;
25
+ /**
26
+ * Create a reactive signal (internal - not exported from package)
27
+ */
28
+ export declare function createSignal<T>(initial?: T): [Accessor<T>, Setter<T>];
29
+ /**
30
+ * Get raw signal value without tracking
31
+ */
32
+ export declare function peekSignal<T>(signal: Signal<T>): T | undefined;