@zenithbuild/core 0.6.2 → 0.6.4
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/CORE_CONTRACT.md +145 -0
- package/README.md +14 -29
- package/bin/zenith.js +89 -0
- package/package.json +39 -56
- package/src/config.js +136 -0
- package/src/core-template.js +30 -0
- package/src/errors.js +54 -0
- package/src/guards.js +61 -0
- package/src/hash.js +52 -0
- package/src/index.js +26 -0
- package/src/ir/index.js +1 -0
- package/src/order.js +69 -0
- package/src/path.js +131 -0
- package/src/schema.js +28 -0
- package/src/version.js +67 -0
- package/bin/zen-build.ts +0 -2
- package/bin/zen-dev.ts +0 -2
- package/bin/zen-preview.ts +0 -2
- package/bin/zenith.ts +0 -2
- package/cli/commands/add.ts +0 -37
- package/cli/commands/build.ts +0 -37
- package/cli/commands/create.ts +0 -702
- package/cli/commands/dev.ts +0 -388
- package/cli/commands/index.ts +0 -112
- package/cli/commands/preview.ts +0 -62
- package/cli/commands/remove.ts +0 -33
- package/cli/index.ts +0 -10
- package/cli/main.ts +0 -101
- package/cli/utils/branding.ts +0 -178
- package/cli/utils/content.ts +0 -112
- package/cli/utils/logger.ts +0 -46
- package/cli/utils/plugin-manager.ts +0 -114
- package/cli/utils/project.ts +0 -77
- package/compiler/README.md +0 -380
- package/compiler/build-analyzer.ts +0 -122
- package/compiler/css/index.ts +0 -317
- package/compiler/discovery/componentDiscovery.ts +0 -178
- package/compiler/discovery/layouts.ts +0 -70
- package/compiler/errors/compilerError.ts +0 -56
- package/compiler/finalize/finalizeOutput.ts +0 -192
- package/compiler/finalize/generateFinalBundle.ts +0 -82
- package/compiler/index.ts +0 -83
- package/compiler/ir/types.ts +0 -174
- package/compiler/output/types.ts +0 -34
- package/compiler/parse/detectMapExpressions.ts +0 -102
- package/compiler/parse/importTypes.ts +0 -78
- package/compiler/parse/parseImports.ts +0 -309
- package/compiler/parse/parseScript.ts +0 -46
- package/compiler/parse/parseTemplate.ts +0 -599
- package/compiler/parse/parseZenFile.ts +0 -66
- package/compiler/parse/scriptAnalysis.ts +0 -91
- package/compiler/parse/trackLoopContext.ts +0 -82
- package/compiler/runtime/dataExposure.ts +0 -317
- package/compiler/runtime/generateDOM.ts +0 -246
- package/compiler/runtime/generateHydrationBundle.ts +0 -407
- package/compiler/runtime/hydration.ts +0 -309
- package/compiler/runtime/navigation.ts +0 -432
- package/compiler/runtime/thinRuntime.ts +0 -160
- package/compiler/runtime/transformIR.ts +0 -370
- package/compiler/runtime/wrapExpression.ts +0 -95
- package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
- package/compiler/spa-build.ts +0 -917
- package/compiler/ssg-build.ts +0 -422
- package/compiler/test/validate-test.ts +0 -104
- package/compiler/transform/classifyExpression.ts +0 -444
- package/compiler/transform/componentResolver.ts +0 -312
- package/compiler/transform/componentScriptTransformer.ts +0 -303
- package/compiler/transform/expressionTransformer.ts +0 -385
- package/compiler/transform/fragmentLowering.ts +0 -634
- package/compiler/transform/generateBindings.ts +0 -47
- package/compiler/transform/generateHTML.ts +0 -28
- package/compiler/transform/layoutProcessor.ts +0 -132
- package/compiler/transform/slotResolver.ts +0 -292
- package/compiler/transform/transformNode.ts +0 -126
- package/compiler/transform/transformTemplate.ts +0 -38
- package/compiler/validate/invariants.ts +0 -292
- package/compiler/validate/validateExpressions.ts +0 -168
- package/core/config/index.ts +0 -16
- package/core/config/loader.ts +0 -69
- package/core/config/types.ts +0 -89
- package/core/index.ts +0 -135
- package/core/lifecycle/index.ts +0 -49
- package/core/lifecycle/zen-mount.ts +0 -182
- package/core/lifecycle/zen-unmount.ts +0 -88
- package/core/plugins/index.ts +0 -7
- package/core/plugins/registry.ts +0 -81
- package/core/reactivity/index.ts +0 -54
- package/core/reactivity/tracking.ts +0 -167
- package/core/reactivity/zen-batch.ts +0 -57
- package/core/reactivity/zen-effect.ts +0 -139
- package/core/reactivity/zen-memo.ts +0 -146
- package/core/reactivity/zen-ref.ts +0 -52
- package/core/reactivity/zen-signal.ts +0 -121
- package/core/reactivity/zen-state.ts +0 -180
- package/core/reactivity/zen-untrack.ts +0 -44
- package/dist/cli.js +0 -11665
- package/dist/zen-build.js +0 -21172
- package/dist/zen-dev.js +0 -21172
- package/dist/zen-preview.js +0 -21172
- package/dist/zenith.js +0 -21172
- package/router/index.ts +0 -28
- package/router/manifest.ts +0 -314
- package/router/navigation/ZenLink.zen +0 -231
- package/router/navigation/index.ts +0 -78
- package/router/navigation/zen-link.ts +0 -584
- package/router/runtime.ts +0 -458
- package/router/types.ts +0 -168
- package/runtime/build.ts +0 -17
- package/runtime/bundle-generator.ts +0 -1247
- package/runtime/client-runtime.ts +0 -549
- package/runtime/serve.ts +0 -93
- package/tsconfig.json +0 -28
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Reactivity Tracking System
|
|
3
|
-
*
|
|
4
|
-
* This module provides the core dependency tracking mechanism used by
|
|
5
|
-
* signals, effects, and memos. It uses a stack-based approach to track
|
|
6
|
-
* which reactive values are accessed during effect/memo execution.
|
|
7
|
-
*
|
|
8
|
-
* Key concepts:
|
|
9
|
-
* - Subscriber: A function that should be called when a dependency changes
|
|
10
|
-
* - Tracking context: The currently executing effect/memo that should collect dependencies
|
|
11
|
-
* - Dependency: A reactive value that an effect/memo depends on
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* A subscriber is a function that gets called when a reactive value changes
|
|
16
|
-
*/
|
|
17
|
-
export type Subscriber = () => void
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Tracking context - represents an effect or memo that is collecting dependencies
|
|
21
|
-
*/
|
|
22
|
-
export interface TrackingContext {
|
|
23
|
-
/** The function to call when dependencies change */
|
|
24
|
-
execute: Subscriber
|
|
25
|
-
/** Set of dependency subscriber sets this context is registered with */
|
|
26
|
-
dependencies: Set<Set<Subscriber>>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Stack of currently executing tracking contexts
|
|
31
|
-
* When an effect runs, it pushes itself onto this stack.
|
|
32
|
-
* When a signal is read, it registers the top of the stack as a subscriber.
|
|
33
|
-
*/
|
|
34
|
-
const trackingStack: TrackingContext[] = []
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Flag to disable tracking (used by zenUntrack)
|
|
38
|
-
*/
|
|
39
|
-
let trackingDisabled = false
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Batch depth counter - when > 0, effect execution is deferred
|
|
43
|
-
*/
|
|
44
|
-
let batchDepth = 0
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Queue of effects to run after batch completes
|
|
48
|
-
*/
|
|
49
|
-
const pendingEffects: Set<Subscriber> = new Set()
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get the current tracking context (if any)
|
|
53
|
-
*/
|
|
54
|
-
export function getCurrentContext(): TrackingContext | undefined {
|
|
55
|
-
if (trackingDisabled) return undefined
|
|
56
|
-
return trackingStack[trackingStack.length - 1]
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Push a new tracking context onto the stack
|
|
61
|
-
*/
|
|
62
|
-
export function pushContext(context: TrackingContext): void {
|
|
63
|
-
trackingStack.push(context)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Pop the current tracking context from the stack
|
|
68
|
-
*/
|
|
69
|
-
export function popContext(): TrackingContext | undefined {
|
|
70
|
-
return trackingStack.pop()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Track a dependency - called when a reactive value is read
|
|
75
|
-
*
|
|
76
|
-
* @param subscribers - The subscriber set of the reactive value being read
|
|
77
|
-
*/
|
|
78
|
-
export function trackDependency(subscribers: Set<Subscriber>): void {
|
|
79
|
-
const context = getCurrentContext()
|
|
80
|
-
|
|
81
|
-
if (context) {
|
|
82
|
-
// Register this effect as a subscriber to the signal
|
|
83
|
-
subscribers.add(context.execute)
|
|
84
|
-
// Track that this effect depends on this signal
|
|
85
|
-
context.dependencies.add(subscribers)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Notify subscribers that a reactive value has changed
|
|
91
|
-
*
|
|
92
|
-
* @param subscribers - The subscriber set to notify
|
|
93
|
-
*/
|
|
94
|
-
export function notifySubscribers(subscribers: Set<Subscriber>): void {
|
|
95
|
-
// Copy subscribers to avoid issues if the set is modified during iteration
|
|
96
|
-
const toNotify = [...subscribers]
|
|
97
|
-
|
|
98
|
-
for (const subscriber of toNotify) {
|
|
99
|
-
if (batchDepth > 0) {
|
|
100
|
-
// Batching - defer effect execution
|
|
101
|
-
pendingEffects.add(subscriber)
|
|
102
|
-
} else {
|
|
103
|
-
// Execute immediately
|
|
104
|
-
subscriber()
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Clean up a tracking context - remove it from all dependency sets
|
|
111
|
-
*
|
|
112
|
-
* @param context - The context to clean up
|
|
113
|
-
*/
|
|
114
|
-
export function cleanupContext(context: TrackingContext): void {
|
|
115
|
-
for (const deps of context.dependencies) {
|
|
116
|
-
deps.delete(context.execute)
|
|
117
|
-
}
|
|
118
|
-
context.dependencies.clear()
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Run a function without tracking dependencies
|
|
123
|
-
*
|
|
124
|
-
* @param fn - The function to run
|
|
125
|
-
* @returns The return value of the function
|
|
126
|
-
*/
|
|
127
|
-
export function runUntracked<T>(fn: () => T): T {
|
|
128
|
-
const wasDisabled = trackingDisabled
|
|
129
|
-
trackingDisabled = true
|
|
130
|
-
try {
|
|
131
|
-
return fn()
|
|
132
|
-
} finally {
|
|
133
|
-
trackingDisabled = wasDisabled
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Start a batch - defer effect execution until batch ends
|
|
139
|
-
*/
|
|
140
|
-
export function startBatch(): void {
|
|
141
|
-
batchDepth++
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* End a batch - run all pending effects
|
|
146
|
-
*/
|
|
147
|
-
export function endBatch(): void {
|
|
148
|
-
batchDepth--
|
|
149
|
-
|
|
150
|
-
if (batchDepth === 0 && pendingEffects.size > 0) {
|
|
151
|
-
// Run all pending effects
|
|
152
|
-
const effects = [...pendingEffects]
|
|
153
|
-
pendingEffects.clear()
|
|
154
|
-
|
|
155
|
-
for (const effect of effects) {
|
|
156
|
-
effect()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Check if currently inside a batch
|
|
163
|
-
*/
|
|
164
|
-
export function isBatching(): boolean {
|
|
165
|
-
return batchDepth > 0
|
|
166
|
-
}
|
|
167
|
-
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Batch - Deferred Effect Execution
|
|
3
|
-
*
|
|
4
|
-
* Batching allows you to make multiple reactive updates without
|
|
5
|
-
* triggering effects until all updates are complete. This improves
|
|
6
|
-
* performance by preventing redundant effect executions.
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Groups multiple mutations
|
|
10
|
-
* - Defers effect execution until batch completes
|
|
11
|
-
* - Supports nested batches
|
|
12
|
-
* - Automatically flushes on completion
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* const firstName = zenSignal('John')
|
|
17
|
-
* const lastName = zenSignal('Doe')
|
|
18
|
-
*
|
|
19
|
-
* zenEffect(() => {
|
|
20
|
-
* console.log(`${firstName()} ${lastName()}`)
|
|
21
|
-
* })
|
|
22
|
-
* // Logs: "John Doe"
|
|
23
|
-
*
|
|
24
|
-
* // Without batch - effect runs twice
|
|
25
|
-
* firstName('Jane') // Logs: "Jane Doe"
|
|
26
|
-
* lastName('Smith') // Logs: "Jane Smith"
|
|
27
|
-
*
|
|
28
|
-
* // With batch - effect runs once
|
|
29
|
-
* zenBatch(() => {
|
|
30
|
-
* firstName('Bob')
|
|
31
|
-
* lastName('Brown')
|
|
32
|
-
* })
|
|
33
|
-
* // Logs: "Bob Brown" (only once)
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
import { startBatch, endBatch } from './tracking'
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Execute a function with batched updates
|
|
41
|
-
*
|
|
42
|
-
* All reactive updates inside the batch will be collected,
|
|
43
|
-
* and effects will only run once after the batch completes.
|
|
44
|
-
*
|
|
45
|
-
* @param fn - The function to execute
|
|
46
|
-
* @returns The return value of the function
|
|
47
|
-
*/
|
|
48
|
-
export function zenBatch<T>(fn: () => T): T {
|
|
49
|
-
startBatch()
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
return fn()
|
|
53
|
-
} finally {
|
|
54
|
-
endBatch()
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Effect - Auto-Tracked Side Effect
|
|
3
|
-
*
|
|
4
|
-
* Effects are functions that automatically track their reactive dependencies
|
|
5
|
-
* and re-run when those dependencies change. They are the bridge between
|
|
6
|
-
* reactive state and side effects (DOM updates, logging, API calls, etc.)
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Automatic dependency tracking (no dependency arrays)
|
|
10
|
-
* - Runs immediately on creation
|
|
11
|
-
* - Re-runs when dependencies change
|
|
12
|
-
* - Supports cleanup functions
|
|
13
|
-
* - Can be manually disposed
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```ts
|
|
17
|
-
* const count = zenSignal(0)
|
|
18
|
-
*
|
|
19
|
-
* // Effect runs immediately, then re-runs when count changes
|
|
20
|
-
* const dispose = zenEffect(() => {
|
|
21
|
-
* console.log('Count:', count())
|
|
22
|
-
*
|
|
23
|
-
* // Optional cleanup - runs before next execution or on dispose
|
|
24
|
-
* return () => {
|
|
25
|
-
* console.log('Cleanup')
|
|
26
|
-
* }
|
|
27
|
-
* })
|
|
28
|
-
*
|
|
29
|
-
* count(1) // Logs: "Cleanup", then "Count: 1"
|
|
30
|
-
*
|
|
31
|
-
* dispose() // Cleanup and stop watching
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import {
|
|
36
|
-
pushContext,
|
|
37
|
-
popContext,
|
|
38
|
-
cleanupContext,
|
|
39
|
-
type TrackingContext
|
|
40
|
-
} from './tracking'
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Effect function type - can optionally return a cleanup function
|
|
44
|
-
*/
|
|
45
|
-
export type EffectFn = () => void | (() => void)
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Dispose function - call to stop the effect
|
|
49
|
-
*/
|
|
50
|
-
export type DisposeFn = () => void
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Effect state
|
|
54
|
-
*/
|
|
55
|
-
interface EffectState {
|
|
56
|
-
/** The effect function */
|
|
57
|
-
fn: EffectFn
|
|
58
|
-
/** Current cleanup function (if any) */
|
|
59
|
-
cleanup: (() => void) | null
|
|
60
|
-
/** Tracking context for dependency collection */
|
|
61
|
-
context: TrackingContext
|
|
62
|
-
/** Whether the effect has been disposed */
|
|
63
|
-
disposed: boolean
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Create an auto-tracked side effect
|
|
68
|
-
*
|
|
69
|
-
* @param fn - The effect function to run
|
|
70
|
-
* @returns A dispose function to stop the effect
|
|
71
|
-
*/
|
|
72
|
-
export function zenEffect(fn: EffectFn): DisposeFn {
|
|
73
|
-
const state: EffectState = {
|
|
74
|
-
fn,
|
|
75
|
-
cleanup: null,
|
|
76
|
-
context: {
|
|
77
|
-
execute: () => runEffect(state),
|
|
78
|
-
dependencies: new Set()
|
|
79
|
-
},
|
|
80
|
-
disposed: false
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Run the effect immediately
|
|
84
|
-
runEffect(state)
|
|
85
|
-
|
|
86
|
-
// Return dispose function
|
|
87
|
-
return () => disposeEffect(state)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Run an effect, tracking dependencies
|
|
92
|
-
*/
|
|
93
|
-
function runEffect(state: EffectState): void {
|
|
94
|
-
if (state.disposed) return
|
|
95
|
-
|
|
96
|
-
// Run cleanup from previous execution
|
|
97
|
-
if (state.cleanup) {
|
|
98
|
-
state.cleanup()
|
|
99
|
-
state.cleanup = null
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Clean up old dependencies
|
|
103
|
-
cleanupContext(state.context)
|
|
104
|
-
|
|
105
|
-
// Push this effect onto the tracking stack
|
|
106
|
-
pushContext(state.context)
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
// Run the effect function
|
|
110
|
-
const result = state.fn()
|
|
111
|
-
|
|
112
|
-
// Store cleanup if returned
|
|
113
|
-
if (typeof result === 'function') {
|
|
114
|
-
state.cleanup = result
|
|
115
|
-
}
|
|
116
|
-
} finally {
|
|
117
|
-
// Pop from tracking stack
|
|
118
|
-
popContext()
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Dispose an effect - run cleanup and stop watching
|
|
124
|
-
*/
|
|
125
|
-
function disposeEffect(state: EffectState): void {
|
|
126
|
-
if (state.disposed) return
|
|
127
|
-
|
|
128
|
-
state.disposed = true
|
|
129
|
-
|
|
130
|
-
// Run cleanup
|
|
131
|
-
if (state.cleanup) {
|
|
132
|
-
state.cleanup()
|
|
133
|
-
state.cleanup = null
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Remove from all dependency sets
|
|
137
|
-
cleanupContext(state.context)
|
|
138
|
-
}
|
|
139
|
-
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Memo - Computed/Derived Value
|
|
3
|
-
*
|
|
4
|
-
* A memo is a lazily-evaluated, cached computation that automatically
|
|
5
|
-
* tracks its dependencies and only recomputes when those dependencies change.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Lazy evaluation (only computes when read)
|
|
9
|
-
* - Automatic dependency tracking
|
|
10
|
-
* - Cached value until dependencies change
|
|
11
|
-
* - Read-only (no setter)
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* const firstName = zenSignal('John')
|
|
16
|
-
* const lastName = zenSignal('Doe')
|
|
17
|
-
*
|
|
18
|
-
* // Memo computes full name, tracks firstName and lastName
|
|
19
|
-
* const fullName = zenMemo(() => `${firstName()} ${lastName()}`)
|
|
20
|
-
*
|
|
21
|
-
* console.log(fullName()) // "John Doe"
|
|
22
|
-
*
|
|
23
|
-
* firstName('Jane')
|
|
24
|
-
* console.log(fullName()) // "Jane Doe" (recomputed)
|
|
25
|
-
* console.log(fullName()) // "Jane Doe" (cached, no recomputation)
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import {
|
|
30
|
-
pushContext,
|
|
31
|
-
popContext,
|
|
32
|
-
cleanupContext,
|
|
33
|
-
trackDependency,
|
|
34
|
-
type TrackingContext,
|
|
35
|
-
type Subscriber
|
|
36
|
-
} from './tracking'
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Memo interface - callable getter
|
|
40
|
-
*/
|
|
41
|
-
export interface Memo<T> {
|
|
42
|
-
/** Get the current computed value */
|
|
43
|
-
(): T
|
|
44
|
-
/** Peek at cached value without tracking (may be stale) */
|
|
45
|
-
peek(): T
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Memo state
|
|
50
|
-
*/
|
|
51
|
-
interface MemoState<T> {
|
|
52
|
-
/** The computation function */
|
|
53
|
-
fn: () => T
|
|
54
|
-
/** Cached value */
|
|
55
|
-
value: T | undefined
|
|
56
|
-
/** Whether the cached value is valid */
|
|
57
|
-
dirty: boolean
|
|
58
|
-
/** Tracking context for dependency collection */
|
|
59
|
-
context: TrackingContext
|
|
60
|
-
/** Subscribers to this memo */
|
|
61
|
-
subscribers: Set<Subscriber>
|
|
62
|
-
/** Whether this is the first computation */
|
|
63
|
-
initialized: boolean
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Create a memoized computed value
|
|
68
|
-
*
|
|
69
|
-
* @param fn - The computation function
|
|
70
|
-
* @returns A memo that can be read to get the computed value
|
|
71
|
-
*/
|
|
72
|
-
export function zenMemo<T>(fn: () => T): Memo<T> {
|
|
73
|
-
const state: MemoState<T> = {
|
|
74
|
-
fn,
|
|
75
|
-
value: undefined,
|
|
76
|
-
dirty: true,
|
|
77
|
-
context: {
|
|
78
|
-
execute: () => markDirty(state),
|
|
79
|
-
dependencies: new Set()
|
|
80
|
-
},
|
|
81
|
-
subscribers: new Set(),
|
|
82
|
-
initialized: false
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function memo(): T {
|
|
86
|
-
// Track that something is reading this memo
|
|
87
|
-
trackDependency(state.subscribers)
|
|
88
|
-
|
|
89
|
-
// Recompute if dirty
|
|
90
|
-
if (state.dirty) {
|
|
91
|
-
computeMemo(state)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return state.value as T
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Add peek method
|
|
98
|
-
;(memo as Memo<T>).peek = function(): T {
|
|
99
|
-
// Return cached value without tracking or recomputing
|
|
100
|
-
if (state.dirty && !state.initialized) {
|
|
101
|
-
computeMemo(state)
|
|
102
|
-
}
|
|
103
|
-
return state.value as T
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return memo as Memo<T>
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Compute the memo value, tracking dependencies
|
|
111
|
-
*/
|
|
112
|
-
function computeMemo<T>(state: MemoState<T>): void {
|
|
113
|
-
// Clean up old dependencies
|
|
114
|
-
cleanupContext(state.context)
|
|
115
|
-
|
|
116
|
-
// Push this memo onto the tracking stack
|
|
117
|
-
pushContext(state.context)
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
// Compute new value
|
|
121
|
-
state.value = state.fn()
|
|
122
|
-
state.dirty = false
|
|
123
|
-
state.initialized = true
|
|
124
|
-
} finally {
|
|
125
|
-
// Pop from tracking stack
|
|
126
|
-
popContext()
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Mark the memo as dirty (needs recomputation)
|
|
132
|
-
* Called when a dependency changes
|
|
133
|
-
*/
|
|
134
|
-
function markDirty<T>(state: MemoState<T>): void {
|
|
135
|
-
if (!state.dirty) {
|
|
136
|
-
state.dirty = true
|
|
137
|
-
|
|
138
|
-
// Notify any effects/memos that depend on this memo
|
|
139
|
-
// Copy to avoid issues during iteration
|
|
140
|
-
const subscribers = [...state.subscribers]
|
|
141
|
-
for (const subscriber of subscribers) {
|
|
142
|
-
subscriber()
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Ref - Mutable Reference Container
|
|
3
|
-
*
|
|
4
|
-
* A ref is a mutable container that does NOT trigger reactivity.
|
|
5
|
-
* It's useful for:
|
|
6
|
-
* - Storing DOM element references
|
|
7
|
-
* - Imperative escape hatches
|
|
8
|
-
* - Values that change but shouldn't trigger re-renders
|
|
9
|
-
*
|
|
10
|
-
* Features:
|
|
11
|
-
* - Mutable `.current` property
|
|
12
|
-
* - Does NOT track dependencies
|
|
13
|
-
* - Does NOT trigger effects
|
|
14
|
-
* - Persists across effect re-runs
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* // DOM reference
|
|
19
|
-
* const inputRef = zenRef<HTMLInputElement>()
|
|
20
|
-
*
|
|
21
|
-
* // Later, after mount
|
|
22
|
-
* inputRef.current = document.querySelector('input')
|
|
23
|
-
* inputRef.current?.focus()
|
|
24
|
-
*
|
|
25
|
-
* // Mutable value that doesn't trigger reactivity
|
|
26
|
-
* const previousValue = zenRef(0)
|
|
27
|
-
* previousValue.current = count() // No effect triggered
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Ref interface - mutable container with .current
|
|
33
|
-
*/
|
|
34
|
-
export interface Ref<T> {
|
|
35
|
-
/** The current value */
|
|
36
|
-
current: T
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create a mutable reference container
|
|
41
|
-
*
|
|
42
|
-
* @param initialValue - The initial value (optional, defaults to undefined)
|
|
43
|
-
* @returns A ref object with a mutable .current property
|
|
44
|
-
*/
|
|
45
|
-
export function zenRef<T>(): Ref<T | undefined>
|
|
46
|
-
export function zenRef<T>(initialValue: T): Ref<T>
|
|
47
|
-
export function zenRef<T>(initialValue?: T): Ref<T | undefined> {
|
|
48
|
-
return {
|
|
49
|
-
current: initialValue
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Signal - Atomic Reactive Value
|
|
3
|
-
*
|
|
4
|
-
* A signal is the most basic reactive primitive. It holds a single value
|
|
5
|
-
* and notifies subscribers when the value changes.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Getter/setter model
|
|
9
|
-
* - Automatic dependency tracking
|
|
10
|
-
* - Fine-grained reactivity (no component re-rendering)
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* const count = zenSignal(0)
|
|
15
|
-
*
|
|
16
|
-
* // Read value
|
|
17
|
-
* console.log(count()) // 0
|
|
18
|
-
*
|
|
19
|
-
* // Write value
|
|
20
|
-
* count(1)
|
|
21
|
-
*
|
|
22
|
-
* // Or use .value
|
|
23
|
-
* count.value = 2
|
|
24
|
-
* console.log(count.value) // 2
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { trackDependency, notifySubscribers, type Subscriber } from './tracking'
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Signal interface - callable getter/setter with .value accessor
|
|
32
|
-
*/
|
|
33
|
-
export interface Signal<T> {
|
|
34
|
-
/** Get the current value (also tracks dependency) */
|
|
35
|
-
(): T
|
|
36
|
-
/** Set a new value */
|
|
37
|
-
(value: T): void
|
|
38
|
-
/** Get/set value via property */
|
|
39
|
-
value: T
|
|
40
|
-
/** Peek at value without tracking */
|
|
41
|
-
peek(): T
|
|
42
|
-
/** Subscribe to changes */
|
|
43
|
-
subscribe(fn: (value: T) => void): () => void
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Internal signal state
|
|
48
|
-
*/
|
|
49
|
-
interface SignalState<T> {
|
|
50
|
-
value: T
|
|
51
|
-
subscribers: Set<Subscriber>
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Create a reactive signal
|
|
56
|
-
*
|
|
57
|
-
* @param initialValue - The initial value of the signal
|
|
58
|
-
* @returns A signal that can be read and written
|
|
59
|
-
*/
|
|
60
|
-
export function zenSignal<T>(initialValue: T): Signal<T> {
|
|
61
|
-
const state: SignalState<T> = {
|
|
62
|
-
value: initialValue,
|
|
63
|
-
subscribers: new Set()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// The signal function - acts as both getter and setter
|
|
67
|
-
function signal(newValue?: T): T {
|
|
68
|
-
if (arguments.length === 0) {
|
|
69
|
-
// Getter - track dependency and return value
|
|
70
|
-
trackDependency(state.subscribers)
|
|
71
|
-
return state.value
|
|
72
|
-
} else {
|
|
73
|
-
// Setter - update value and notify
|
|
74
|
-
const oldValue = state.value
|
|
75
|
-
state.value = newValue as T
|
|
76
|
-
|
|
77
|
-
if (!Object.is(oldValue, newValue)) {
|
|
78
|
-
notifySubscribers(state.subscribers)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return state.value
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Add .value accessor
|
|
86
|
-
Object.defineProperty(signal, 'value', {
|
|
87
|
-
get() {
|
|
88
|
-
trackDependency(state.subscribers)
|
|
89
|
-
return state.value
|
|
90
|
-
},
|
|
91
|
-
set(newValue: T) {
|
|
92
|
-
const oldValue = state.value
|
|
93
|
-
state.value = newValue
|
|
94
|
-
|
|
95
|
-
if (!Object.is(oldValue, newValue)) {
|
|
96
|
-
notifySubscribers(state.subscribers)
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
enumerable: true,
|
|
100
|
-
configurable: false
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
// Add .peek() - read without tracking
|
|
104
|
-
;(signal as Signal<T>).peek = function(): T {
|
|
105
|
-
return state.value
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Add .subscribe() - manual subscription
|
|
109
|
-
;(signal as Signal<T>).subscribe = function(fn: (value: T) => void): () => void {
|
|
110
|
-
const subscriber: Subscriber = () => fn(state.value)
|
|
111
|
-
state.subscribers.add(subscriber)
|
|
112
|
-
|
|
113
|
-
// Return unsubscribe function
|
|
114
|
-
return () => {
|
|
115
|
-
state.subscribers.delete(subscriber)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return signal as Signal<T>
|
|
120
|
-
}
|
|
121
|
-
|