@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,60 @@
1
+ /**
2
+ * Signal - core reactive primitive (internal)
3
+ * Inspired by SolidJS createSignal
4
+ */
5
+ import { getListener, scheduleComputation } from './tracking';
6
+ /**
7
+ * Create a reactive signal (internal - not exported from package)
8
+ */
9
+ export function createSignal(initial) {
10
+ const signal = {
11
+ value: initial,
12
+ observers: null,
13
+ observerSlots: null,
14
+ };
15
+ function read() {
16
+ const listener = getListener();
17
+ // If there's an active computation, register it
18
+ if (listener) {
19
+ const sSlot = signal.observers?.length ?? 0;
20
+ // Listener tracks this signal
21
+ if (!listener.sources) {
22
+ listener.sources = [signal];
23
+ listener.sourceSlots = [sSlot];
24
+ }
25
+ else {
26
+ listener.sources.push(signal);
27
+ listener.sourceSlots?.push(sSlot);
28
+ }
29
+ // Signal tracks this listener
30
+ if (!signal.observers) {
31
+ signal.observers = [listener];
32
+ signal.observerSlots = [listener.sources.length - 1];
33
+ }
34
+ else {
35
+ signal.observers.push(listener);
36
+ signal.observerSlots?.push(listener.sources.length - 1);
37
+ }
38
+ }
39
+ return signal.value;
40
+ }
41
+ function write(value) {
42
+ if (signal.value === value)
43
+ return; // No change
44
+ signal.value = value;
45
+ // Notify all observers
46
+ if (signal.observers?.length) {
47
+ for (const observer of signal.observers) {
48
+ observer.state = 1; // STALE
49
+ scheduleComputation(observer);
50
+ }
51
+ }
52
+ }
53
+ return [read, write];
54
+ }
55
+ /**
56
+ * Get raw signal value without tracking
57
+ */
58
+ export function peekSignal(signal) {
59
+ return signal.value;
60
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Global tracking context for reactive system
3
+ * Inspired by SolidJS signal.ts
4
+ */
5
+ import type { Computation } from './signal';
6
+ export declare let Listener: Computation<any> | null;
7
+ /**
8
+ * Set the current listener (computation being executed)
9
+ */
10
+ export declare function setListener(comp: Computation<any> | null): Computation<any> | null;
11
+ /**
12
+ * Get current listener
13
+ */
14
+ export declare function getListener(): Computation<any> | null;
15
+ /**
16
+ * Schedule a computation to run
17
+ */
18
+ export declare function scheduleComputation(comp: Computation<any>): void;
19
+ /**
20
+ * Run a computation with tracking
21
+ */
22
+ export declare function runComputation(comp: Computation<any>): void;
23
+ /**
24
+ * Clean up a computation's subscriptions (swap-and-pop for O(1))
25
+ */
26
+ export declare function cleanComputation(comp: Computation<any>): void;
27
+ /**
28
+ * Execute function without tracking dependencies
29
+ */
30
+ export declare function untrack<T>(fn: () => T): T;
31
+ /**
32
+ * Batch multiple updates into one flush
33
+ *
34
+ * Uses synchronous batching - all dependent re-renders happen
35
+ * immediately when the outermost batch completes.
36
+ */
37
+ export declare function batch<T>(fn: () => T): T;
38
+ /**
39
+ * Wait for all pending updates to complete
40
+ */
41
+ export declare function flushSync(): Promise<void>;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Global tracking context for reactive system
3
+ * Inspired by SolidJS signal.ts
4
+ */
5
+ // Currently executing computation (for dependency tracking)
6
+ export let Listener = null;
7
+ // Queue of computations to run
8
+ const queue = [];
9
+ let pending = false;
10
+ // Batch depth counter for synchronous batching
11
+ let batchDepth = 0;
12
+ /**
13
+ * Set the current listener (computation being executed)
14
+ */
15
+ export function setListener(comp) {
16
+ const prev = Listener;
17
+ Listener = comp;
18
+ return prev;
19
+ }
20
+ /**
21
+ * Get current listener
22
+ */
23
+ export function getListener() {
24
+ return Listener;
25
+ }
26
+ /**
27
+ * Schedule a computation to run
28
+ */
29
+ export function scheduleComputation(comp) {
30
+ queue.push(comp);
31
+ // Only trigger async flush if NOT in a batch
32
+ // When inside a batch, computations are flushed synchronously at batch end
33
+ if (batchDepth === 0 && !pending) {
34
+ pending = true;
35
+ queueMicrotask(flushQueue);
36
+ }
37
+ }
38
+ /**
39
+ * Flush all pending computations
40
+ */
41
+ function flushQueue() {
42
+ pending = false;
43
+ const toRun = [...queue];
44
+ queue.length = 0;
45
+ for (const comp of toRun) {
46
+ if (comp.state === 1) {
47
+ // Still STALE
48
+ runComputation(comp);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Run a computation with tracking
54
+ */
55
+ export function runComputation(comp) {
56
+ // 1. Clean up old subscriptions
57
+ cleanComputation(comp);
58
+ // 2. Set as current listener
59
+ const prevListener = Listener;
60
+ Listener = comp;
61
+ try {
62
+ // 3. Execute - any signal reads will register
63
+ comp.fn();
64
+ comp.state = 0; // CLEAN
65
+ }
66
+ finally {
67
+ Listener = prevListener;
68
+ }
69
+ }
70
+ /**
71
+ * Clean up a computation's subscriptions (swap-and-pop for O(1))
72
+ */
73
+ export function cleanComputation(comp) {
74
+ // Remove from all sources' observer lists
75
+ if (comp.sources && comp.sourceSlots) {
76
+ while (comp.sources.length) {
77
+ // biome-ignore lint/style/noNonNullAssertion: length check guarantees pop() returns value
78
+ const source = comp.sources.pop();
79
+ // biome-ignore lint/style/noNonNullAssertion: length check guarantees pop() returns value
80
+ const index = comp.sourceSlots.pop();
81
+ // Swap-and-pop removal from source.observers
82
+ if (source.observers?.length && source.observerSlots) {
83
+ // biome-ignore lint/style/noNonNullAssertion: length check guarantees pop() returns value
84
+ const last = source.observers.pop();
85
+ // biome-ignore lint/style/noNonNullAssertion: length check guarantees pop() returns value
86
+ const lastSlot = source.observerSlots.pop();
87
+ if (index < source.observers.length) {
88
+ // Move last item to fill the hole
89
+ source.observers[index] = last;
90
+ // biome-ignore lint/style/noNonNullAssertion: observerSlots exists when observers exists
91
+ source.observerSlots[index] = lastSlot;
92
+ // Update the moved item's reference
93
+ // biome-ignore lint/style/noNonNullAssertion: sourceSlots exists for active computations
94
+ last.sourceSlots[lastSlot] = index;
95
+ }
96
+ }
97
+ }
98
+ }
99
+ // Run cleanup functions
100
+ if (comp.cleanups) {
101
+ for (const cleanup of comp.cleanups)
102
+ cleanup();
103
+ comp.cleanups = null;
104
+ }
105
+ }
106
+ /**
107
+ * Execute function without tracking dependencies
108
+ */
109
+ export function untrack(fn) {
110
+ const prevListener = Listener;
111
+ Listener = null;
112
+ try {
113
+ return fn();
114
+ }
115
+ finally {
116
+ Listener = prevListener;
117
+ }
118
+ }
119
+ /**
120
+ * Batch multiple updates into one flush
121
+ *
122
+ * Uses synchronous batching - all dependent re-renders happen
123
+ * immediately when the outermost batch completes.
124
+ */
125
+ export function batch(fn) {
126
+ batchDepth++;
127
+ try {
128
+ return fn();
129
+ }
130
+ finally {
131
+ batchDepth--;
132
+ if (batchDepth === 0) {
133
+ // Flush synchronously - all updates happen NOW
134
+ while (queue.length) {
135
+ // biome-ignore lint/style/noNonNullAssertion: length check guarantees shift() returns value
136
+ const comp = queue.shift();
137
+ if (comp.state === 1) {
138
+ // Still STALE
139
+ runComputation(comp);
140
+ }
141
+ }
142
+ // Clear pending flag since we just flushed
143
+ pending = false;
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * Wait for all pending updates to complete
149
+ */
150
+ export function flushSync() {
151
+ return new Promise((resolve) => {
152
+ if (!pending && queue.length === 0) {
153
+ resolve();
154
+ }
155
+ else {
156
+ queueMicrotask(() => {
157
+ flushSync().then(resolve);
158
+ });
159
+ }
160
+ });
161
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Fiber - intermediate representation of component tree
3
+ */
4
+ import type { InstanceNode } from '../primitives/instance';
5
+ import type { Computation } from '../reactive/signal';
6
+ export interface Fiber {
7
+ type: any;
8
+ props: Record<string, any>;
9
+ children: Fiber[];
10
+ path: string[];
11
+ key?: string | number;
12
+ instanceNodes: InstanceNode[];
13
+ store?: any;
14
+ computation?: Computation<void>;
15
+ incomingResourcePath?: string[];
16
+ hasPlaceholderInstance?: boolean;
17
+ }
18
+ /**
19
+ * Create a new fiber
20
+ */
21
+ export declare function createFiber(type: any, props: Record<string, any>, path: string[], key?: string | number): Fiber;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Fiber - intermediate representation of component tree
3
+ */
4
+ /**
5
+ * Create a new fiber
6
+ */
7
+ export function createFiber(type, props, path, key) {
8
+ return {
9
+ type,
10
+ props,
11
+ children: [],
12
+ path,
13
+ key,
14
+ instanceNodes: [],
15
+ };
16
+ }
@@ -0,0 +1,4 @@
1
+ export { createFiber, type Fiber } from './fiber';
2
+ export { type ChangeSet, deepEqual, hasNewNodes, reconcile } from './reconcile';
3
+ export { cleanupFiber, collectInstanceNodes, getCurrentFiber, getCurrentPath, renderFiber, } from './render';
4
+ export { CReact, type CReactOptions, resetRuntime, run } from './run';
@@ -0,0 +1,4 @@
1
+ export { createFiber } from './fiber';
2
+ export { deepEqual, hasNewNodes, reconcile } from './reconcile';
3
+ export { cleanupFiber, collectInstanceNodes, getCurrentFiber, getCurrentPath, renderFiber, } from './render';
4
+ export { CReact, resetRuntime, run } from './run';
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Reconciler - diff two sets of instance nodes with dependency ordering
3
+ *
4
+ * Key features:
5
+ * - Path-based id for matching (unique per position in tree)
6
+ * - Dependency graph from parent-child relationships
7
+ * - Topological sort for correct deployment order
8
+ * - Parallel batches for concurrent deployment
9
+ */
10
+ import type { InstanceNode } from '../primitives/instance';
11
+ /**
12
+ * Dependency graph
13
+ */
14
+ export interface DependencyGraph {
15
+ /** node → nodes it depends on (must be deployed first) */
16
+ dependencies: Map<string, string[]>;
17
+ /** node → nodes that depend on it (must wait for this) */
18
+ dependents: Map<string, string[]>;
19
+ }
20
+ /**
21
+ * Change set with deployment ordering
22
+ */
23
+ export interface ChangeSet {
24
+ creates: InstanceNode[];
25
+ updates: InstanceNode[];
26
+ deletes: InstanceNode[];
27
+ /** Topologically sorted node IDs for deployment */
28
+ deploymentOrder: string[];
29
+ /** Groups of nodes that can be deployed in parallel */
30
+ parallelBatches: string[][];
31
+ }
32
+ /**
33
+ * Build dependency graph from nodes
34
+ *
35
+ * Dependencies are derived from the path hierarchy:
36
+ * - Parent must be deployed before children
37
+ * - Node with path ['App', 'DB', 'Cache'] depends on ['App', 'DB']
38
+ */
39
+ export declare function buildDependencyGraph(nodes: InstanceNode[]): DependencyGraph;
40
+ /**
41
+ * Topological sort using Kahn's algorithm
42
+ *
43
+ * Returns node IDs in deployment order (dependencies first)
44
+ */
45
+ export declare function topologicalSort(nodeIds: string[], graph: DependencyGraph): string[];
46
+ /**
47
+ * Compute parallel batches from deployment order
48
+ *
49
+ * Nodes that don't depend on each other can be deployed in parallel.
50
+ * Each batch contains nodes that can be deployed concurrently.
51
+ */
52
+ export declare function computeParallelBatches(deploymentOrder: string[], graph: DependencyGraph): string[][];
53
+ /**
54
+ * Reconcile previous and current instance nodes
55
+ * Uses path-based id for matching - each node has unique position in tree
56
+ */
57
+ export declare function reconcile(previous: InstanceNode[], current: InstanceNode[]): ChangeSet;
58
+ /**
59
+ * Deep equality check
60
+ */
61
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
62
+ /**
63
+ * Check if there are new nodes that weren't in previous
64
+ * Uses id for matching consistency with reconcile()
65
+ */
66
+ export declare function hasNewNodes(previous: InstanceNode[], current: InstanceNode[]): boolean;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Reconciler - diff two sets of instance nodes with dependency ordering
3
+ *
4
+ * Key features:
5
+ * - Path-based id for matching (unique per position in tree)
6
+ * - Dependency graph from parent-child relationships
7
+ * - Topological sort for correct deployment order
8
+ * - Parallel batches for concurrent deployment
9
+ */
10
+ /**
11
+ * Build dependency graph from nodes
12
+ *
13
+ * Dependencies are derived from the path hierarchy:
14
+ * - Parent must be deployed before children
15
+ * - Node with path ['App', 'DB', 'Cache'] depends on ['App', 'DB']
16
+ */
17
+ export function buildDependencyGraph(nodes) {
18
+ const dependencies = new Map();
19
+ const dependents = new Map();
20
+ const nodeIds = new Set(nodes.map((n) => n.id));
21
+ for (const node of nodes) {
22
+ dependencies.set(node.id, []);
23
+ dependents.set(node.id, []);
24
+ }
25
+ for (const node of nodes) {
26
+ // Find parent in the path
27
+ if (node.path.length > 1) {
28
+ // Try to find a parent node in the graph
29
+ // Walk up the path to find the nearest ancestor that's a node
30
+ for (let i = node.path.length - 2; i >= 0; i--) {
31
+ const parentPath = node.path.slice(0, i + 1);
32
+ const parentId = parentPath.join('.');
33
+ if (nodeIds.has(parentId)) {
34
+ // Add dependency: node depends on parent
35
+ dependencies.get(node.id)?.push(parentId);
36
+ // Add dependent: parent has node as dependent
37
+ dependents.get(parentId)?.push(node.id);
38
+ break; // Only direct parent dependency
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return { dependencies, dependents };
44
+ }
45
+ /**
46
+ * Topological sort using Kahn's algorithm
47
+ *
48
+ * Returns node IDs in deployment order (dependencies first)
49
+ */
50
+ export function topologicalSort(nodeIds, graph) {
51
+ const result = [];
52
+ const inDegree = new Map();
53
+ const queue = [];
54
+ // Initialize in-degree counts
55
+ for (const id of nodeIds) {
56
+ const deps = graph.dependencies.get(id) ?? [];
57
+ // Only count dependencies that are in our nodeIds set
58
+ const relevantDeps = deps.filter((d) => nodeIds.includes(d));
59
+ inDegree.set(id, relevantDeps.length);
60
+ if (relevantDeps.length === 0) {
61
+ queue.push(id);
62
+ }
63
+ }
64
+ // Process nodes with no remaining dependencies
65
+ while (queue.length > 0) {
66
+ const current = queue.shift();
67
+ result.push(current);
68
+ // Reduce in-degree for dependents
69
+ const deps = graph.dependents.get(current) ?? [];
70
+ for (const dep of deps) {
71
+ if (!inDegree.has(dep))
72
+ continue; // Not in our set
73
+ const newDegree = inDegree.get(dep) - 1;
74
+ inDegree.set(dep, newDegree);
75
+ if (newDegree === 0) {
76
+ queue.push(dep);
77
+ }
78
+ }
79
+ }
80
+ // Check for cycles (but not for duplicate IDs, which are handled normally)
81
+ if (result.length !== nodeIds.length) {
82
+ const remaining = nodeIds.filter((id) => !result.includes(id));
83
+ // Only warn if there are actual stuck nodes (not just duplicates)
84
+ if (remaining.length > 0) {
85
+ console.warn('Circular dependency detected. Remaining nodes:', remaining);
86
+ // Still include remaining nodes at the end
87
+ result.push(...remaining);
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ /**
93
+ * Compute parallel batches from deployment order
94
+ *
95
+ * Nodes that don't depend on each other can be deployed in parallel.
96
+ * Each batch contains nodes that can be deployed concurrently.
97
+ */
98
+ export function computeParallelBatches(deploymentOrder, graph) {
99
+ const batches = [];
100
+ const deployed = new Set();
101
+ for (const nodeId of deploymentOrder) {
102
+ // Find which batch this node can go in
103
+ const deps = graph.dependencies.get(nodeId) ?? [];
104
+ const relevantDeps = deps.filter((d) => deploymentOrder.includes(d));
105
+ // Find the latest batch that contains a dependency
106
+ let batchIndex = 0;
107
+ for (let i = 0; i < batches.length; i++) {
108
+ const batch = batches[i];
109
+ if (batch?.some((id) => relevantDeps.includes(id))) {
110
+ batchIndex = i + 1;
111
+ }
112
+ }
113
+ // Create new batch if needed
114
+ while (batches.length <= batchIndex) {
115
+ batches.push([]);
116
+ }
117
+ batches[batchIndex]?.push(nodeId);
118
+ deployed.add(nodeId);
119
+ }
120
+ return batches;
121
+ }
122
+ /**
123
+ * Reconcile previous and current instance nodes
124
+ * Uses path-based id for matching - each node has unique position in tree
125
+ */
126
+ export function reconcile(previous, current) {
127
+ // Use path-based id for matching (unique per position in tree)
128
+ const prevMap = new Map(previous.map((n) => [n.id, n]));
129
+ const currMap = new Map(current.map((n) => [n.id, n]));
130
+ const creates = [];
131
+ const updates = [];
132
+ const deletes = [];
133
+ // Find creates and updates
134
+ for (const node of current) {
135
+ const prev = prevMap.get(node.id);
136
+ if (!prev) {
137
+ creates.push(node);
138
+ }
139
+ else if (!deepEqual(prev.props, node.props)) {
140
+ updates.push(node);
141
+ }
142
+ }
143
+ // Find deletes
144
+ for (const node of previous) {
145
+ if (!currMap.has(node.id)) {
146
+ deletes.push(node);
147
+ }
148
+ }
149
+ // Build dependency graph for current nodes
150
+ const graph = buildDependencyGraph(current);
151
+ // Get IDs of nodes that need deployment (creates + updates)
152
+ const toDeployIds = [...creates, ...updates].map((n) => n.id);
153
+ // Compute deployment order and parallel batches
154
+ const deploymentOrder = topologicalSort(toDeployIds, graph);
155
+ const parallelBatches = computeParallelBatches(deploymentOrder, graph);
156
+ return {
157
+ creates,
158
+ updates,
159
+ deletes,
160
+ deploymentOrder,
161
+ parallelBatches,
162
+ };
163
+ }
164
+ /**
165
+ * Deep equality check
166
+ */
167
+ export function deepEqual(a, b) {
168
+ if (a === b)
169
+ return true;
170
+ if (a == null || b == null)
171
+ return false;
172
+ if (typeof a !== typeof b)
173
+ return false;
174
+ if (typeof a === 'object') {
175
+ // Type guard: both are objects at this point
176
+ const objA = a;
177
+ const objB = b;
178
+ if (Array.isArray(objA) !== Array.isArray(objB))
179
+ return false;
180
+ if (Array.isArray(objA) && Array.isArray(objB)) {
181
+ if (objA.length !== objB.length)
182
+ return false;
183
+ for (let i = 0; i < objA.length; i++) {
184
+ if (!deepEqual(objA[i], objB[i]))
185
+ return false;
186
+ }
187
+ return true;
188
+ }
189
+ const keysA = Object.keys(objA);
190
+ const keysB = Object.keys(objB);
191
+ if (keysA.length !== keysB.length)
192
+ return false;
193
+ for (const key of keysA) {
194
+ if (!Object.hasOwn(objB, key))
195
+ return false;
196
+ if (!deepEqual(objA[key], objB[key]))
197
+ return false;
198
+ }
199
+ return true;
200
+ }
201
+ return false;
202
+ }
203
+ /**
204
+ * Check if there are new nodes that weren't in previous
205
+ * Uses id for matching consistency with reconcile()
206
+ */
207
+ export function hasNewNodes(previous, current) {
208
+ const prevIds = new Set(previous.map((n) => n.id));
209
+ return current.some((n) => !prevIds.has(n.id));
210
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Render - transform JSX to Fiber tree
3
+ */
4
+ import type { Fiber } from './fiber';
5
+ /**
6
+ * Get current fiber (for hooks)
7
+ */
8
+ export declare function getCurrentFiber(): Fiber | null;
9
+ /**
10
+ * Get current path (for hooks)
11
+ */
12
+ export declare function getCurrentPath(): string[];
13
+ /**
14
+ * Get current resource path (for useInstance)
15
+ * Only components with useInstance contribute to this path
16
+ */
17
+ export declare function getCurrentResourcePath(): string[];
18
+ /**
19
+ * Push a segment to resource path (called by useInstance)
20
+ */
21
+ export declare function pushResourcePath(segment: string): void;
22
+ /**
23
+ * Pop a segment from resource path (called after component with instance renders)
24
+ */
25
+ export declare function popResourcePath(): void;
26
+ /**
27
+ * Reset resource path (called at start of render)
28
+ */
29
+ export declare function resetResourcePath(): void;
30
+ /**
31
+ * Render a JSX element to a Fiber
32
+ */
33
+ export declare function renderFiber(element: any, path: string[]): Fiber;
34
+ /**
35
+ * Collect all instance nodes from fiber tree
36
+ * Returns cloned nodes to ensure returned arrays are independent snapshots
37
+ */
38
+ export declare function collectInstanceNodes(fiber: Fiber): any[];
39
+ /**
40
+ * Clean up a fiber tree
41
+ */
42
+ export declare function cleanupFiber(fiber: Fiber): void;