@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.
Files changed (112) hide show
  1. package/CORE_CONTRACT.md +145 -0
  2. package/README.md +14 -29
  3. package/bin/zenith.js +89 -0
  4. package/package.json +39 -56
  5. package/src/config.js +136 -0
  6. package/src/core-template.js +30 -0
  7. package/src/errors.js +54 -0
  8. package/src/guards.js +61 -0
  9. package/src/hash.js +52 -0
  10. package/src/index.js +26 -0
  11. package/src/ir/index.js +1 -0
  12. package/src/order.js +69 -0
  13. package/src/path.js +131 -0
  14. package/src/schema.js +28 -0
  15. package/src/version.js +67 -0
  16. package/bin/zen-build.ts +0 -2
  17. package/bin/zen-dev.ts +0 -2
  18. package/bin/zen-preview.ts +0 -2
  19. package/bin/zenith.ts +0 -2
  20. package/cli/commands/add.ts +0 -37
  21. package/cli/commands/build.ts +0 -37
  22. package/cli/commands/create.ts +0 -702
  23. package/cli/commands/dev.ts +0 -388
  24. package/cli/commands/index.ts +0 -112
  25. package/cli/commands/preview.ts +0 -62
  26. package/cli/commands/remove.ts +0 -33
  27. package/cli/index.ts +0 -10
  28. package/cli/main.ts +0 -101
  29. package/cli/utils/branding.ts +0 -178
  30. package/cli/utils/content.ts +0 -112
  31. package/cli/utils/logger.ts +0 -46
  32. package/cli/utils/plugin-manager.ts +0 -114
  33. package/cli/utils/project.ts +0 -77
  34. package/compiler/README.md +0 -380
  35. package/compiler/build-analyzer.ts +0 -122
  36. package/compiler/css/index.ts +0 -317
  37. package/compiler/discovery/componentDiscovery.ts +0 -178
  38. package/compiler/discovery/layouts.ts +0 -70
  39. package/compiler/errors/compilerError.ts +0 -56
  40. package/compiler/finalize/finalizeOutput.ts +0 -192
  41. package/compiler/finalize/generateFinalBundle.ts +0 -82
  42. package/compiler/index.ts +0 -83
  43. package/compiler/ir/types.ts +0 -174
  44. package/compiler/output/types.ts +0 -34
  45. package/compiler/parse/detectMapExpressions.ts +0 -102
  46. package/compiler/parse/importTypes.ts +0 -78
  47. package/compiler/parse/parseImports.ts +0 -309
  48. package/compiler/parse/parseScript.ts +0 -46
  49. package/compiler/parse/parseTemplate.ts +0 -599
  50. package/compiler/parse/parseZenFile.ts +0 -66
  51. package/compiler/parse/scriptAnalysis.ts +0 -91
  52. package/compiler/parse/trackLoopContext.ts +0 -82
  53. package/compiler/runtime/dataExposure.ts +0 -317
  54. package/compiler/runtime/generateDOM.ts +0 -246
  55. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  56. package/compiler/runtime/hydration.ts +0 -309
  57. package/compiler/runtime/navigation.ts +0 -432
  58. package/compiler/runtime/thinRuntime.ts +0 -160
  59. package/compiler/runtime/transformIR.ts +0 -370
  60. package/compiler/runtime/wrapExpression.ts +0 -95
  61. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  62. package/compiler/spa-build.ts +0 -917
  63. package/compiler/ssg-build.ts +0 -422
  64. package/compiler/test/validate-test.ts +0 -104
  65. package/compiler/transform/classifyExpression.ts +0 -444
  66. package/compiler/transform/componentResolver.ts +0 -312
  67. package/compiler/transform/componentScriptTransformer.ts +0 -303
  68. package/compiler/transform/expressionTransformer.ts +0 -385
  69. package/compiler/transform/fragmentLowering.ts +0 -634
  70. package/compiler/transform/generateBindings.ts +0 -47
  71. package/compiler/transform/generateHTML.ts +0 -28
  72. package/compiler/transform/layoutProcessor.ts +0 -132
  73. package/compiler/transform/slotResolver.ts +0 -292
  74. package/compiler/transform/transformNode.ts +0 -126
  75. package/compiler/transform/transformTemplate.ts +0 -38
  76. package/compiler/validate/invariants.ts +0 -292
  77. package/compiler/validate/validateExpressions.ts +0 -168
  78. package/core/config/index.ts +0 -16
  79. package/core/config/loader.ts +0 -69
  80. package/core/config/types.ts +0 -89
  81. package/core/index.ts +0 -135
  82. package/core/lifecycle/index.ts +0 -49
  83. package/core/lifecycle/zen-mount.ts +0 -182
  84. package/core/lifecycle/zen-unmount.ts +0 -88
  85. package/core/plugins/index.ts +0 -7
  86. package/core/plugins/registry.ts +0 -81
  87. package/core/reactivity/index.ts +0 -54
  88. package/core/reactivity/tracking.ts +0 -167
  89. package/core/reactivity/zen-batch.ts +0 -57
  90. package/core/reactivity/zen-effect.ts +0 -139
  91. package/core/reactivity/zen-memo.ts +0 -146
  92. package/core/reactivity/zen-ref.ts +0 -52
  93. package/core/reactivity/zen-signal.ts +0 -121
  94. package/core/reactivity/zen-state.ts +0 -180
  95. package/core/reactivity/zen-untrack.ts +0 -44
  96. package/dist/cli.js +0 -11665
  97. package/dist/zen-build.js +0 -21172
  98. package/dist/zen-dev.js +0 -21172
  99. package/dist/zen-preview.js +0 -21172
  100. package/dist/zenith.js +0 -21172
  101. package/router/index.ts +0 -28
  102. package/router/manifest.ts +0 -314
  103. package/router/navigation/ZenLink.zen +0 -231
  104. package/router/navigation/index.ts +0 -78
  105. package/router/navigation/zen-link.ts +0 -584
  106. package/router/runtime.ts +0 -458
  107. package/router/types.ts +0 -168
  108. package/runtime/build.ts +0 -17
  109. package/runtime/bundle-generator.ts +0 -1247
  110. package/runtime/client-runtime.ts +0 -549
  111. package/runtime/serve.ts +0 -93
  112. 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
-