@zenithbuild/core 0.1.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/.eslintignore +15 -0
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +25 -0
- package/.github/ISSUE_TEMPLATE/new_ticket.yaml +34 -0
- package/.github/pull_request_template.md +15 -0
- package/.github/workflows/discord-changelog.yml +141 -0
- package/.github/workflows/discord-notify.yml +242 -0
- package/.github/workflows/discord-version.yml +195 -0
- package/.prettierignore +13 -0
- package/.prettierrc +21 -0
- package/.zen.d.ts +15 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/app/components/Button.zen +46 -0
- package/app/components/Link.zen +11 -0
- package/app/favicon.ico +0 -0
- package/app/layouts/Main.zen +59 -0
- package/app/pages/about.zen +23 -0
- package/app/pages/blog/[id].zen +53 -0
- package/app/pages/blog/index.zen +32 -0
- package/app/pages/dynamic-dx.zen +712 -0
- package/app/pages/dynamic-primitives.zen +453 -0
- package/app/pages/index.zen +154 -0
- package/app/pages/navigation-demo.zen +229 -0
- package/app/pages/posts/[...slug].zen +61 -0
- package/app/pages/primitives-demo.zen +273 -0
- package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
- package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
- package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
- package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +601 -0
- package/assets/logos/README.md +54 -0
- package/assets/logos/zen.icns +0 -0
- package/bun.lock +39 -0
- package/compiler/README.md +380 -0
- package/compiler/errors/compilerError.ts +24 -0
- package/compiler/finalize/finalizeOutput.ts +163 -0
- package/compiler/finalize/generateFinalBundle.ts +82 -0
- package/compiler/index.ts +44 -0
- package/compiler/ir/types.ts +83 -0
- package/compiler/legacy/binding.ts +254 -0
- package/compiler/legacy/bindings.ts +338 -0
- package/compiler/legacy/component-process.ts +1208 -0
- package/compiler/legacy/component.ts +301 -0
- package/compiler/legacy/event.ts +50 -0
- package/compiler/legacy/expression.ts +1149 -0
- package/compiler/legacy/mutation.ts +280 -0
- package/compiler/legacy/parse.ts +299 -0
- package/compiler/legacy/split.ts +608 -0
- package/compiler/legacy/types.ts +32 -0
- package/compiler/output/types.ts +34 -0
- package/compiler/parse/detectMapExpressions.ts +102 -0
- package/compiler/parse/parseScript.ts +22 -0
- package/compiler/parse/parseTemplate.ts +425 -0
- package/compiler/parse/parseZenFile.ts +66 -0
- package/compiler/parse/trackLoopContext.ts +82 -0
- package/compiler/runtime/dataExposure.ts +291 -0
- package/compiler/runtime/generateDOM.ts +144 -0
- package/compiler/runtime/generateHydrationBundle.ts +383 -0
- package/compiler/runtime/hydration.ts +309 -0
- package/compiler/runtime/navigation.ts +432 -0
- package/compiler/runtime/thinRuntime.ts +160 -0
- package/compiler/runtime/transformIR.ts +256 -0
- package/compiler/runtime/wrapExpression.ts +84 -0
- package/compiler/runtime/wrapExpressionWithLoop.ts +77 -0
- package/compiler/spa-build.ts +1000 -0
- package/compiler/test/validate-test.ts +104 -0
- package/compiler/transform/generateBindings.ts +47 -0
- package/compiler/transform/generateHTML.ts +28 -0
- package/compiler/transform/transformNode.ts +126 -0
- package/compiler/transform/transformTemplate.ts +38 -0
- package/compiler/validate/validateExpressions.ts +168 -0
- package/core/index.ts +135 -0
- package/core/lifecycle/index.ts +49 -0
- package/core/lifecycle/zen-mount.ts +182 -0
- package/core/lifecycle/zen-unmount.ts +88 -0
- package/core/reactivity/index.ts +54 -0
- package/core/reactivity/tracking.ts +167 -0
- package/core/reactivity/zen-batch.ts +57 -0
- package/core/reactivity/zen-effect.ts +139 -0
- package/core/reactivity/zen-memo.ts +146 -0
- package/core/reactivity/zen-ref.ts +52 -0
- package/core/reactivity/zen-signal.ts +121 -0
- package/core/reactivity/zen-state.ts +180 -0
- package/core/reactivity/zen-untrack.ts +44 -0
- package/docs/COMMENTS.md +111 -0
- package/docs/COMMITS.md +36 -0
- package/docs/CONTRIBUTING.md +116 -0
- package/docs/STYLEGUIDE.md +62 -0
- package/package.json +44 -0
- package/router/index.ts +76 -0
- package/router/manifest.ts +314 -0
- package/router/navigation/ZenLink.zen +231 -0
- package/router/navigation/index.ts +78 -0
- package/router/navigation/zen-link.ts +584 -0
- package/router/runtime.ts +458 -0
- package/router/types.ts +168 -0
- package/runtime/build.ts +17 -0
- package/runtime/serve.ts +93 -0
- package/scripts/webhook-proxy.ts +213 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith State - Deep Reactive Object
|
|
3
|
+
*
|
|
4
|
+
* Creates a deeply reactive object using Proxy. Any property access
|
|
5
|
+
* is tracked, and any mutation triggers effects.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Deep reactivity via nested Proxies
|
|
9
|
+
* - Automatic dependency tracking on property access
|
|
10
|
+
* - Triggers effects on property mutation
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const user = zenState({
|
|
15
|
+
* name: 'John',
|
|
16
|
+
* address: {
|
|
17
|
+
* city: 'NYC'
|
|
18
|
+
* }
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* // Access triggers tracking
|
|
22
|
+
* console.log(user.name)
|
|
23
|
+
*
|
|
24
|
+
* // Mutation triggers effects
|
|
25
|
+
* user.name = 'Jane'
|
|
26
|
+
* user.address.city = 'LA'
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { trackDependency, notifySubscribers, type Subscriber } from './tracking'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* WeakMap to store proxy targets and their subscriber maps
|
|
34
|
+
* Key: target object
|
|
35
|
+
* Value: Map of property key -> subscriber set
|
|
36
|
+
*/
|
|
37
|
+
const proxySubscribers = new WeakMap<object, Map<string | symbol, Set<Subscriber>>>()
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* WeakMap to store original objects and their proxies
|
|
41
|
+
* Prevents creating multiple proxies for the same object
|
|
42
|
+
*/
|
|
43
|
+
const proxyCache = new WeakMap<object, object>()
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get or create subscriber set for a property
|
|
47
|
+
*/
|
|
48
|
+
function getPropertySubscribers(target: object, key: string | symbol): Set<Subscriber> {
|
|
49
|
+
let propertyMap = proxySubscribers.get(target)
|
|
50
|
+
|
|
51
|
+
if (!propertyMap) {
|
|
52
|
+
propertyMap = new Map()
|
|
53
|
+
proxySubscribers.set(target, propertyMap)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let subscribers = propertyMap.get(key)
|
|
57
|
+
|
|
58
|
+
if (!subscribers) {
|
|
59
|
+
subscribers = new Set()
|
|
60
|
+
propertyMap.set(key, subscribers)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return subscribers
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a value should be wrapped in a proxy
|
|
68
|
+
*/
|
|
69
|
+
function shouldProxy(value: unknown): value is object {
|
|
70
|
+
if (value === null || typeof value !== 'object') {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Don't proxy special objects
|
|
75
|
+
if (value instanceof Date ||
|
|
76
|
+
value instanceof RegExp ||
|
|
77
|
+
value instanceof Map ||
|
|
78
|
+
value instanceof Set ||
|
|
79
|
+
value instanceof WeakMap ||
|
|
80
|
+
value instanceof WeakSet ||
|
|
81
|
+
value instanceof Promise ||
|
|
82
|
+
ArrayBuffer.isView(value)) {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a reactive proxy for an object
|
|
91
|
+
*/
|
|
92
|
+
function createReactiveProxy<T extends object>(target: T): T {
|
|
93
|
+
// Check cache first
|
|
94
|
+
const cached = proxyCache.get(target)
|
|
95
|
+
if (cached) {
|
|
96
|
+
return cached as T
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const proxy = new Proxy(target, {
|
|
100
|
+
get(target, key, receiver) {
|
|
101
|
+
// Track dependency
|
|
102
|
+
const subscribers = getPropertySubscribers(target, key)
|
|
103
|
+
trackDependency(subscribers)
|
|
104
|
+
|
|
105
|
+
const value = Reflect.get(target, key, receiver)
|
|
106
|
+
|
|
107
|
+
// Recursively proxy nested objects
|
|
108
|
+
if (shouldProxy(value)) {
|
|
109
|
+
return createReactiveProxy(value)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return value
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
set(target, key, value, receiver) {
|
|
116
|
+
const oldValue = Reflect.get(target, key, receiver)
|
|
117
|
+
|
|
118
|
+
// Unwrap proxies before storing
|
|
119
|
+
const rawValue = value
|
|
120
|
+
|
|
121
|
+
const result = Reflect.set(target, key, rawValue, receiver)
|
|
122
|
+
|
|
123
|
+
// Only notify if value actually changed
|
|
124
|
+
if (!Object.is(oldValue, rawValue)) {
|
|
125
|
+
const subscribers = getPropertySubscribers(target, key)
|
|
126
|
+
notifySubscribers(subscribers)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
deleteProperty(target, key) {
|
|
133
|
+
const hadKey = Reflect.has(target, key)
|
|
134
|
+
const result = Reflect.deleteProperty(target, key)
|
|
135
|
+
|
|
136
|
+
if (hadKey && result) {
|
|
137
|
+
const subscribers = getPropertySubscribers(target, key)
|
|
138
|
+
notifySubscribers(subscribers)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
has(target, key) {
|
|
145
|
+
// Track dependency for 'in' operator
|
|
146
|
+
const subscribers = getPropertySubscribers(target, key)
|
|
147
|
+
trackDependency(subscribers)
|
|
148
|
+
|
|
149
|
+
return Reflect.has(target, key)
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
ownKeys(target) {
|
|
153
|
+
// Track a special 'keys' dependency for iteration
|
|
154
|
+
const subscribers = getPropertySubscribers(target, Symbol.for('zen:keys'))
|
|
155
|
+
trackDependency(subscribers)
|
|
156
|
+
|
|
157
|
+
return Reflect.ownKeys(target)
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Cache the proxy
|
|
162
|
+
proxyCache.set(target, proxy)
|
|
163
|
+
|
|
164
|
+
return proxy
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create a deeply reactive state object
|
|
169
|
+
*
|
|
170
|
+
* @param initialValue - The initial state object
|
|
171
|
+
* @returns A reactive proxy of the object
|
|
172
|
+
*/
|
|
173
|
+
export function zenState<T extends object>(initialValue: T): T {
|
|
174
|
+
if (!shouldProxy(initialValue)) {
|
|
175
|
+
throw new Error('zenState requires a plain object or array')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return createReactiveProxy(initialValue)
|
|
179
|
+
}
|
|
180
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zenith Untrack - Escape Dependency Tracking
|
|
3
|
+
*
|
|
4
|
+
* Allows reading reactive values without creating a dependency.
|
|
5
|
+
* Useful when you need to read a value inside an effect but don't
|
|
6
|
+
* want the effect to re-run when that value changes.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Disables dependency tracking within the callback
|
|
10
|
+
* - Returns the callback's return value
|
|
11
|
+
* - Can be nested
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const count = zenSignal(0)
|
|
16
|
+
* const multiplier = zenSignal(2)
|
|
17
|
+
*
|
|
18
|
+
* zenEffect(() => {
|
|
19
|
+
* // This creates a dependency on 'count'
|
|
20
|
+
* const c = count()
|
|
21
|
+
*
|
|
22
|
+
* // This does NOT create a dependency on 'multiplier'
|
|
23
|
+
* const m = zenUntrack(() => multiplier())
|
|
24
|
+
*
|
|
25
|
+
* console.log(c * m)
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* count(5) // Effect re-runs
|
|
29
|
+
* multiplier(3) // Effect does NOT re-run
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { runUntracked } from './tracking'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Execute a function without tracking dependencies
|
|
37
|
+
*
|
|
38
|
+
* @param fn - The function to execute
|
|
39
|
+
* @returns The return value of the function
|
|
40
|
+
*/
|
|
41
|
+
export function zenUntrack<T>(fn: () => T): T {
|
|
42
|
+
return runUntracked(fn)
|
|
43
|
+
}
|
|
44
|
+
|
package/docs/COMMENTS.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Conventional Comments
|
|
2
|
+
|
|
3
|
+
Zenith uses [Conventional Comments](https://conventionalcomments.org/) to provide clear, actionable feedback in pull requests.
|
|
4
|
+
|
|
5
|
+
## Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<label> [decorations]: <subject>
|
|
9
|
+
|
|
10
|
+
[discussion]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 💫 Quick Reference
|
|
14
|
+
### Labels
|
|
15
|
+
|
|
16
|
+
| Label | Description | Example Use Case |
|
|
17
|
+
|--------------|------------------------------------------------------|-------------------------------------------|
|
|
18
|
+
| **praise** | Highlight something positive | Great implementation, well-documented code |
|
|
19
|
+
| **nitpick** | Minor suggestions that don't require changes | Variable naming, minor style preferences |
|
|
20
|
+
| **suggestion** | Propose improvements to consider | Alternative approaches, optimizations |
|
|
21
|
+
| **issue** | Highlight problems that need to be resolved | Bugs, errors, critical problems |
|
|
22
|
+
| **question** | Ask for clarification or explanation | Understanding intent, approach questions |
|
|
23
|
+
| **thought** | Share ideas or considerations for the future | Architectural thoughts, future improvements |
|
|
24
|
+
| **chore** | Simple tasks like formatting or typos | Missing semicolons, typos, formatting |
|
|
25
|
+
|
|
26
|
+
### Decorations
|
|
27
|
+
|
|
28
|
+
| Decoration | Meaning |
|
|
29
|
+
|-------------------|------------------------------------------------------|
|
|
30
|
+
| **(non-blocking)** | Optional feedback - PR can merge without addressing |
|
|
31
|
+
| **(blocking)** | Must be addressed before merge |
|
|
32
|
+
| **(if-minor)** | Address only if it's a quick fix |
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
### Praise
|
|
37
|
+
```
|
|
38
|
+
praise: Excellent error handling here!
|
|
39
|
+
|
|
40
|
+
This covers all the edge cases I was worried about.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Nitpick (non-blocking)
|
|
44
|
+
```
|
|
45
|
+
nitpick (non-blocking): Consider renaming `temp` to `processedData`
|
|
46
|
+
|
|
47
|
+
While `temp` works, a more descriptive name might help future maintainers.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Suggestion
|
|
51
|
+
```
|
|
52
|
+
suggestion: We could use a Map instead of an Object here for better performance
|
|
53
|
+
|
|
54
|
+
Since we're doing frequent lookups, a Map would give us O(1) access time
|
|
55
|
+
and better memory characteristics for this use case.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Issue (blocking)
|
|
59
|
+
```
|
|
60
|
+
issue (blocking): This will throw when `items` is undefined
|
|
61
|
+
|
|
62
|
+
We need to add a null/undefined check before calling `.map()` on line 42.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Question
|
|
66
|
+
```
|
|
67
|
+
question: Why are we processing this data twice?
|
|
68
|
+
|
|
69
|
+
I see similar logic on lines 15 and 78. Is there a reason we can't
|
|
70
|
+
consolidate these operations?
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Thought
|
|
74
|
+
```
|
|
75
|
+
thought: This might be a good candidate for a custom hook in the future
|
|
76
|
+
|
|
77
|
+
Not for this PR, but as we add more components with similar behavior,
|
|
78
|
+
extracting this pattern could be valuable.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Chore
|
|
82
|
+
```
|
|
83
|
+
chore (if-minor): Missing semicolon on line 23
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Best Practices
|
|
87
|
+
|
|
88
|
+
1. **Be specific** - Reference line numbers or code sections
|
|
89
|
+
2. **Be kind** - Remember there's a human on the other side
|
|
90
|
+
3. **Be clear** - Explain the "why" behind your feedback
|
|
91
|
+
4. **Use blocking sparingly** - Only for issues that truly need resolution
|
|
92
|
+
5. **Praise good work** - Positive feedback is valuable!
|
|
93
|
+
|
|
94
|
+
## Quick Tips for Reviewers
|
|
95
|
+
|
|
96
|
+
- Start with **praise** for good work
|
|
97
|
+
- Use **suggestion** for most feedback (not blocking unless critical)
|
|
98
|
+
- Reserve **issue (blocking)** for bugs or critical problems
|
|
99
|
+
- Use **question** when you don't understand something
|
|
100
|
+
- Mark style preferences as **nitpick (non-blocking)**
|
|
101
|
+
|
|
102
|
+
## Quick Tips for Authors
|
|
103
|
+
|
|
104
|
+
- Don't take **nitpicks** personally - they're optional
|
|
105
|
+
- Ask for clarification on unclear **questions** or **suggestions**
|
|
106
|
+
- Address all **blocking** comments before requesting re-review
|
|
107
|
+
- Thank reviewers for their time and feedback
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
**Remember**: The goal is constructive collaboration, not perfection. Use these conventions to make reviews clearer and more productive for everyone.
|
package/docs/COMMITS.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## TL;DR
|
|
2
|
+
***If you find that this is tanking your productivity just use `feat` or `fix` and always apply `!` if pushing breaking changes***
|
|
3
|
+
|
|
4
|
+
## Why bother with conventional commits?
|
|
5
|
+
- _The most important reason for us is to simplify/automate SemVer and our Release strategy_
|
|
6
|
+
- For this reason, please prefix all commits with one of the below [Prefixes](#prefixes)
|
|
7
|
+
|
|
8
|
+
## How does this relate to SemVer?
|
|
9
|
+
- `fix` type commits should be translated to `PATCH` releases.
|
|
10
|
+
- `feat` type commits should be translated to `MINOR` releases.
|
|
11
|
+
- Commits with `BREAKING CHANGE` or `!` (e.g. `feat!: extend parser`) in the commits, regardless of type, should be translated to `MAJOR` releases.
|
|
12
|
+
|
|
13
|
+
## Prefixes
|
|
14
|
+
| Commit Prefix | SemVer Equivalent | Example |
|
|
15
|
+
| --------------- | ----------------- | ------------------------------------ |
|
|
16
|
+
| fix: | PATCH - 0.0.n | fix: html not recognizing state |
|
|
17
|
+
| feat: | MINOR - 0.n.0 | feat: phase 2 event loop |
|
|
18
|
+
| fix!: | MAJOR - n.0.0 | fix!: html not recognizing state |
|
|
19
|
+
| feat!: | MAJOR - n.0.0 | feat!: phase 2 event loop |
|
|
20
|
+
| BREAKING CHANGE | MAJOR - n.0.0 | fix: BREAKING CHANGE component state |
|
|
21
|
+
| BREAKING-CHANGE | MAJOR - n.0.0 | feat: BREAKING-CHANGE build phase 2 |
|
|
22
|
+
| docs: | CHANGELOG | ... |
|
|
23
|
+
| chore: | CHANGELOG | ... |
|
|
24
|
+
| style: | CHANGELOG | ... |
|
|
25
|
+
| test: | CHANGELOG | ... |
|
|
26
|
+
| refactor: | CHANGELOG | ... |
|
|
27
|
+
|
|
28
|
+
### Glossary
|
|
29
|
+
|
|
30
|
+
- `feat:` - New features
|
|
31
|
+
- `fix:` - Bug fixes
|
|
32
|
+
- `docs:` - Documentation changes
|
|
33
|
+
- `style:` - Code style changes (formatting, no logic change)
|
|
34
|
+
- `refactor:` - Code refactoring
|
|
35
|
+
- `test:` - Adding or updating tests
|
|
36
|
+
- `chore:` - Maintenance tasks
|