@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.
- package/README.md +72 -21
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +88 -0
- package/dist/index.d.ts +19 -44
- package/dist/index.js +20 -68
- package/dist/jsx/index.d.ts +2 -0
- package/dist/jsx/index.js +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +4 -0
- package/dist/jsx/jsx-dev-runtime.js +4 -0
- package/dist/jsx/jsx-runtime.d.ts +38 -0
- package/dist/jsx/jsx-runtime.js +38 -0
- package/dist/jsx/types.d.ts +12 -0
- package/dist/jsx/types.js +4 -0
- package/dist/primitives/context.d.ts +34 -0
- package/dist/primitives/context.js +63 -0
- package/dist/primitives/index.d.ts +3 -0
- package/dist/primitives/index.js +3 -0
- package/dist/primitives/instance.d.ts +72 -0
- package/dist/primitives/instance.js +235 -0
- package/dist/primitives/store.d.ts +22 -0
- package/dist/primitives/store.js +97 -0
- package/dist/provider/backend.d.ts +110 -0
- package/dist/provider/backend.js +37 -0
- package/dist/provider/interface.d.ts +48 -0
- package/dist/provider/interface.js +39 -0
- package/dist/reactive/effect.d.ts +11 -0
- package/dist/reactive/effect.js +42 -0
- package/dist/reactive/index.d.ts +3 -0
- package/dist/reactive/index.js +3 -0
- package/dist/reactive/signal.d.ts +32 -0
- package/dist/reactive/signal.js +60 -0
- package/dist/reactive/tracking.d.ts +41 -0
- package/dist/reactive/tracking.js +161 -0
- package/dist/runtime/fiber.d.ts +21 -0
- package/dist/runtime/fiber.js +16 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/reconcile.d.ts +66 -0
- package/dist/runtime/reconcile.js +210 -0
- package/dist/runtime/render.d.ts +42 -0
- package/dist/runtime/render.js +231 -0
- package/dist/runtime/run.d.ts +119 -0
- package/dist/runtime/run.js +334 -0
- package/dist/runtime/state-machine.d.ts +95 -0
- package/dist/runtime/state-machine.js +209 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +4 -0
- package/package.json +11 -27
- package/dist/cli/commands/BuildCommand.d.ts +0 -40
- package/dist/cli/commands/BuildCommand.js +0 -151
- package/dist/cli/commands/DeployCommand.d.ts +0 -38
- package/dist/cli/commands/DeployCommand.js +0 -194
- package/dist/cli/commands/DevCommand.d.ts +0 -52
- package/dist/cli/commands/DevCommand.js +0 -394
- package/dist/cli/commands/PlanCommand.d.ts +0 -39
- package/dist/cli/commands/PlanCommand.js +0 -164
- package/dist/cli/commands/index.d.ts +0 -36
- package/dist/cli/commands/index.js +0 -43
- package/dist/cli/core/ArgumentParser.d.ts +0 -46
- package/dist/cli/core/ArgumentParser.js +0 -127
- package/dist/cli/core/BaseCommand.d.ts +0 -75
- package/dist/cli/core/BaseCommand.js +0 -95
- package/dist/cli/core/CLIContext.d.ts +0 -68
- package/dist/cli/core/CLIContext.js +0 -183
- package/dist/cli/core/CommandRegistry.d.ts +0 -64
- package/dist/cli/core/CommandRegistry.js +0 -89
- package/dist/cli/core/index.d.ts +0 -36
- package/dist/cli/core/index.js +0 -43
- package/dist/cli/index.d.ts +0 -35
- package/dist/cli/index.js +0 -100
- package/dist/cli/output.d.ts +0 -204
- package/dist/cli/output.js +0 -437
- package/dist/cli/utils.d.ts +0 -59
- package/dist/cli/utils.js +0 -76
- package/dist/context/createContext.d.ts +0 -90
- package/dist/context/createContext.js +0 -113
- package/dist/context/index.d.ts +0 -30
- package/dist/context/index.js +0 -35
- package/dist/core/CReact.d.ts +0 -409
- package/dist/core/CReact.js +0 -1151
- package/dist/core/CloudDOMBuilder.d.ts +0 -447
- package/dist/core/CloudDOMBuilder.js +0 -1234
- package/dist/core/ContextDependencyTracker.d.ts +0 -165
- package/dist/core/ContextDependencyTracker.js +0 -448
- package/dist/core/ErrorRecoveryManager.d.ts +0 -145
- package/dist/core/ErrorRecoveryManager.js +0 -443
- package/dist/core/EventBus.d.ts +0 -91
- package/dist/core/EventBus.js +0 -185
- package/dist/core/ProviderOutputTracker.d.ts +0 -211
- package/dist/core/ProviderOutputTracker.js +0 -476
- package/dist/core/ReactiveUpdateQueue.d.ts +0 -76
- package/dist/core/ReactiveUpdateQueue.js +0 -121
- package/dist/core/Reconciler.d.ts +0 -415
- package/dist/core/Reconciler.js +0 -1044
- package/dist/core/RenderScheduler.d.ts +0 -153
- package/dist/core/RenderScheduler.js +0 -519
- package/dist/core/Renderer.d.ts +0 -336
- package/dist/core/Renderer.js +0 -944
- package/dist/core/Runtime.d.ts +0 -246
- package/dist/core/Runtime.js +0 -640
- package/dist/core/StateBindingManager.d.ts +0 -121
- package/dist/core/StateBindingManager.js +0 -309
- package/dist/core/StateMachine.d.ts +0 -441
- package/dist/core/StateMachine.js +0 -883
- package/dist/core/StructuralChangeDetector.d.ts +0 -140
- package/dist/core/StructuralChangeDetector.js +0 -363
- package/dist/core/Validator.d.ts +0 -127
- package/dist/core/Validator.js +0 -279
- package/dist/core/errors.d.ts +0 -153
- package/dist/core/errors.js +0 -202
- package/dist/core/index.d.ts +0 -38
- package/dist/core/index.js +0 -64
- package/dist/core/types.d.ts +0 -265
- package/dist/core/types.js +0 -48
- package/dist/hooks/context.d.ts +0 -147
- package/dist/hooks/context.js +0 -334
- package/dist/hooks/useContext.d.ts +0 -113
- package/dist/hooks/useContext.js +0 -169
- package/dist/hooks/useEffect.d.ts +0 -105
- package/dist/hooks/useEffect.js +0 -540
- package/dist/hooks/useInstance.d.ts +0 -139
- package/dist/hooks/useInstance.js +0 -455
- package/dist/hooks/useState.d.ts +0 -120
- package/dist/hooks/useState.js +0 -298
- package/dist/jsx.d.ts +0 -143
- package/dist/jsx.js +0 -76
- package/dist/providers/DummyBackendProvider.d.ts +0 -193
- package/dist/providers/DummyBackendProvider.js +0 -189
- package/dist/providers/DummyCloudProvider.d.ts +0 -128
- package/dist/providers/DummyCloudProvider.js +0 -157
- package/dist/providers/IBackendProvider.d.ts +0 -177
- package/dist/providers/IBackendProvider.js +0 -31
- package/dist/providers/ICloudProvider.d.ts +0 -230
- package/dist/providers/ICloudProvider.js +0 -31
- package/dist/providers/index.d.ts +0 -31
- package/dist/providers/index.js +0 -31
- package/dist/test-event-callbacks.d.ts +0 -0
- package/dist/test-event-callbacks.js +0 -1
- package/dist/utils/Logger.d.ts +0 -144
- package/dist/utils/Logger.js +0 -220
- package/dist/utils/Output.d.ts +0 -161
- package/dist/utils/Output.js +0 -401
- package/dist/utils/deepEqual.d.ts +0 -71
- package/dist/utils/deepEqual.js +0 -276
- package/dist/utils/naming.d.ts +0 -241
- 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,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;
|