zylaris 1.0.2
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/LICENSE +21 -0
- package/README.md +558 -0
- package/Zylaris.js.png +0 -0
- package/examples/default/index.html +13 -0
- package/examples/default/package.json +23 -0
- package/examples/default/src/app/about/page.tsx +18 -0
- package/examples/default/src/app/counter/page.tsx +22 -0
- package/examples/default/src/app/global.css +225 -0
- package/examples/default/src/app/layout.tsx +33 -0
- package/examples/default/src/app/page.tsx +14 -0
- package/examples/default/src/entry-client.tsx +87 -0
- package/examples/default/src/entry-server.tsx +52 -0
- package/examples/default/src/router.ts +60 -0
- package/examples/default/tsconfig.json +28 -0
- package/examples/default/zylaris.config.ts +24 -0
- package/package.json +34 -0
- package/packages/adapter/package.json +59 -0
- package/packages/adapter/src/adapters/bun.ts +215 -0
- package/packages/adapter/src/adapters/cloudflare.ts +278 -0
- package/packages/adapter/src/adapters/deno.ts +219 -0
- package/packages/adapter/src/adapters/netlify.ts +274 -0
- package/packages/adapter/src/adapters/node.ts +155 -0
- package/packages/adapter/src/adapters/static.ts +134 -0
- package/packages/adapter/src/adapters/vercel.ts +239 -0
- package/packages/adapter/src/index.ts +115 -0
- package/packages/adapter/src/lib/builder.ts +361 -0
- package/packages/adapter/src/types.ts +191 -0
- package/packages/adapter/tsconfig.json +8 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/bin.ts +107 -0
- package/packages/cli/src/commands/build.ts +197 -0
- package/packages/cli/src/commands/create.ts +222 -0
- package/packages/cli/src/commands/deploy.ts +90 -0
- package/packages/cli/src/commands/dev.ts +108 -0
- package/packages/cli/src/index.ts +6 -0
- package/packages/cli/tsconfig.json +9 -0
- package/packages/compiler/package.json +39 -0
- package/packages/compiler/src/index.ts +210 -0
- package/packages/compiler/src/jit.ts +187 -0
- package/packages/compiler/tsconfig.json +9 -0
- package/packages/core/package.json +55 -0
- package/packages/core/src/components.test.ts +125 -0
- package/packages/core/src/components.ts +181 -0
- package/packages/core/src/config.ts +204 -0
- package/packages/core/src/hooks.ts +142 -0
- package/packages/core/src/index.ts +59 -0
- package/packages/core/src/jsx-runtime.ts +46 -0
- package/packages/core/tsconfig.json +16 -0
- package/packages/dev-server/package.json +51 -0
- package/packages/dev-server/src/index.ts +306 -0
- package/packages/dev-server/src/jit-middleware.ts +78 -0
- package/packages/dev-server/tsconfig.json +9 -0
- package/packages/plugins/package.json +44 -0
- package/packages/plugins/src/cdn/loader.ts +275 -0
- package/packages/plugins/src/index.ts +238 -0
- package/packages/plugins/src/loaders/auto-import.ts +219 -0
- package/packages/plugins/src/loaders/external.ts +332 -0
- package/packages/plugins/src/transforms/index.ts +407 -0
- package/packages/plugins/src/types.ts +296 -0
- package/packages/plugins/tsconfig.json +8 -0
- package/packages/reactivity/package.json +36 -0
- package/packages/reactivity/src/computed.d.ts +3 -0
- package/packages/reactivity/src/computed.d.ts.map +1 -0
- package/packages/reactivity/src/computed.js +64 -0
- package/packages/reactivity/src/computed.js.map +1 -0
- package/packages/reactivity/src/computed.test.ts +83 -0
- package/packages/reactivity/src/computed.ts +69 -0
- package/packages/reactivity/src/index.d.ts +6 -0
- package/packages/reactivity/src/index.d.ts.map +1 -0
- package/packages/reactivity/src/index.js +7 -0
- package/packages/reactivity/src/index.js.map +1 -0
- package/packages/reactivity/src/index.ts +18 -0
- package/packages/reactivity/src/resource.d.ts +6 -0
- package/packages/reactivity/src/resource.d.ts.map +1 -0
- package/packages/reactivity/src/resource.js +43 -0
- package/packages/reactivity/src/resource.js.map +1 -0
- package/packages/reactivity/src/resource.test.ts +70 -0
- package/packages/reactivity/src/resource.ts +59 -0
- package/packages/reactivity/src/signal.d.ts +7 -0
- package/packages/reactivity/src/signal.d.ts.map +1 -0
- package/packages/reactivity/src/signal.js +145 -0
- package/packages/reactivity/src/signal.js.map +1 -0
- package/packages/reactivity/src/signal.test.ts +130 -0
- package/packages/reactivity/src/signal.ts +207 -0
- package/packages/reactivity/src/store.d.ts +4 -0
- package/packages/reactivity/src/store.d.ts.map +1 -0
- package/packages/reactivity/src/store.js +62 -0
- package/packages/reactivity/src/store.js.map +1 -0
- package/packages/reactivity/src/store.test.ts +38 -0
- package/packages/reactivity/src/store.ts +111 -0
- package/packages/reactivity/src/types.d.ts +43 -0
- package/packages/reactivity/src/types.d.ts.map +1 -0
- package/packages/reactivity/src/types.js +3 -0
- package/packages/reactivity/src/types.js.map +1 -0
- package/packages/reactivity/src/types.ts +43 -0
- package/packages/reactivity/tsconfig.json +9 -0
- package/packages/router/package.json +44 -0
- package/packages/router/src/components.tsx +150 -0
- package/packages/router/src/fs-router.ts +163 -0
- package/packages/router/src/index.ts +22 -0
- package/packages/router/src/router.test.ts +111 -0
- package/packages/router/src/router.ts +112 -0
- package/packages/router/src/types.ts +69 -0
- package/packages/router/tsconfig.json +10 -0
- package/packages/server/package.json +41 -0
- package/packages/server/src/action.test.ts +102 -0
- package/packages/server/src/action.ts +201 -0
- package/packages/server/src/api.ts +143 -0
- package/packages/server/src/index.ts +18 -0
- package/packages/server/src/types.ts +72 -0
- package/packages/server/tsconfig.json +9 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/publish.ps1 +138 -0
- package/scripts/publish.sh +142 -0
- package/tsconfig.json +28 -0
- package/turbo.json +24 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { signal, createEffect, batch } from './signal.js';
|
|
3
|
+
|
|
4
|
+
describe('signal', () => {
|
|
5
|
+
it('should create a signal with initial value', () => {
|
|
6
|
+
const s = signal(10);
|
|
7
|
+
expect(s()).toBe(10);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should update signal value', () => {
|
|
11
|
+
const s = signal(10);
|
|
12
|
+
s.set(20);
|
|
13
|
+
expect(s()).toBe(20);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should update signal with function', () => {
|
|
17
|
+
const s = signal(10);
|
|
18
|
+
s.update(v => v * 2);
|
|
19
|
+
expect(s()).toBe(20);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should peek value without tracking', () => {
|
|
23
|
+
const s = signal(10);
|
|
24
|
+
expect(s.peek()).toBe(10);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should subscribe to changes', () => {
|
|
28
|
+
const s = signal(10);
|
|
29
|
+
const fn = vi.fn();
|
|
30
|
+
const unsubscribe = s.subscribe(fn);
|
|
31
|
+
|
|
32
|
+
s.set(20);
|
|
33
|
+
// Wait for microtask
|
|
34
|
+
return new Promise(resolve => {
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
expect(fn).toHaveBeenCalledWith(20);
|
|
37
|
+
unsubscribe();
|
|
38
|
+
resolve(undefined);
|
|
39
|
+
}, 10);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should not update if value is the same', async () => {
|
|
44
|
+
const s = signal(10);
|
|
45
|
+
const fn = vi.fn();
|
|
46
|
+
s.subscribe(fn);
|
|
47
|
+
|
|
48
|
+
// Wait for initial call
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
50
|
+
expect(fn).toHaveBeenCalledTimes(1); // Initial call
|
|
51
|
+
expect(fn).toHaveBeenCalledWith(10);
|
|
52
|
+
|
|
53
|
+
fn.mockClear();
|
|
54
|
+
s.set(10); // Same value
|
|
55
|
+
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
57
|
+
expect(fn).not.toHaveBeenCalled(); // No additional call
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('createEffect', () => {
|
|
62
|
+
it('should run effect immediately', () => {
|
|
63
|
+
const fn = vi.fn();
|
|
64
|
+
createEffect(fn);
|
|
65
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should track signal dependencies', () => {
|
|
69
|
+
const s = signal(10);
|
|
70
|
+
const fn = vi.fn();
|
|
71
|
+
|
|
72
|
+
createEffect(() => {
|
|
73
|
+
fn(s());
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
s.set(20);
|
|
77
|
+
return new Promise(resolve => {
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
expect(fn).toHaveBeenCalledWith(20);
|
|
80
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
81
|
+
resolve(undefined);
|
|
82
|
+
}, 10);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should cleanup on dispose', () => {
|
|
87
|
+
const s = signal(10);
|
|
88
|
+
const fn = vi.fn();
|
|
89
|
+
|
|
90
|
+
const dispose = createEffect(() => {
|
|
91
|
+
fn(s());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
dispose();
|
|
95
|
+
s.set(20);
|
|
96
|
+
|
|
97
|
+
return new Promise(resolve => {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
expect(fn).toHaveBeenCalledTimes(1); // Should not run again
|
|
100
|
+
resolve(undefined);
|
|
101
|
+
}, 10);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('batch', () => {
|
|
107
|
+
it('should batch updates', () => {
|
|
108
|
+
const s1 = signal(10);
|
|
109
|
+
const s2 = signal(20);
|
|
110
|
+
const fn = vi.fn();
|
|
111
|
+
|
|
112
|
+
createEffect(() => {
|
|
113
|
+
fn(s1(), s2());
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
batch(() => {
|
|
117
|
+
s1.set(100);
|
|
118
|
+
s2.set(200);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return new Promise(resolve => {
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
// Effect should only run twice: initial + after batch
|
|
124
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
125
|
+
expect(fn).toHaveBeenLastCalledWith(100, 200);
|
|
126
|
+
resolve(undefined);
|
|
127
|
+
}, 10);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { Signal, SignalNode, Effect, EffectFn } from './types.js';
|
|
2
|
+
import { getCurrentComputed } from './computed.js';
|
|
3
|
+
|
|
4
|
+
// Global context for tracking dependencies
|
|
5
|
+
let currentObserver: Effect | null = null;
|
|
6
|
+
const effectStack: Effect[] = [];
|
|
7
|
+
|
|
8
|
+
// Effect scheduling queue
|
|
9
|
+
const effectQueue: Effect[] = [];
|
|
10
|
+
let isFlushing = false;
|
|
11
|
+
|
|
12
|
+
// Track if we're in batch mode
|
|
13
|
+
let batchDepth = 0;
|
|
14
|
+
const pendingEffects = new Set<Effect>();
|
|
15
|
+
|
|
16
|
+
export function signal<T>(initialValue: T): Signal<T> {
|
|
17
|
+
const node: SignalNode<T> = {
|
|
18
|
+
value: initialValue,
|
|
19
|
+
observers: new Set(),
|
|
20
|
+
version: 0,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function read(): T {
|
|
24
|
+
// Track dependency if inside an effect
|
|
25
|
+
if (currentObserver) {
|
|
26
|
+
node.observers.add(currentObserver);
|
|
27
|
+
currentObserver.sources = currentObserver.sources || new Set();
|
|
28
|
+
currentObserver.sources.add(node as SignalNode<unknown>);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Track dependency if inside computed
|
|
32
|
+
const computed = getCurrentComputed();
|
|
33
|
+
if (computed) {
|
|
34
|
+
computed.deps.add(node as SignalNode<unknown>);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return node.value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function set(value: T): void {
|
|
41
|
+
if (Object.is(node.value, value)) return;
|
|
42
|
+
node.value = value;
|
|
43
|
+
node.version++;
|
|
44
|
+
notifyObservers(node);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function update(fn: (value: T) => T): void {
|
|
48
|
+
set(fn(node.value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function peek(): T {
|
|
52
|
+
return node.value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function subscribe(fn: (value: T) => void): () => void {
|
|
56
|
+
return createEffect(() => fn(read()));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Object.assign(read, { set, update, peek, subscribe });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function notifyObservers<T>(node: SignalNode<T>): void {
|
|
63
|
+
const observers = Array.from(node.observers);
|
|
64
|
+
for (const effect of observers) {
|
|
65
|
+
if (batchDepth > 0) {
|
|
66
|
+
pendingEffects.add(effect);
|
|
67
|
+
} else {
|
|
68
|
+
scheduleEffect(effect);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createEffect(fn: EffectFn): () => void {
|
|
74
|
+
const effect: Effect = {
|
|
75
|
+
fn,
|
|
76
|
+
dependencies: new Set(),
|
|
77
|
+
version: 0,
|
|
78
|
+
scheduled: false,
|
|
79
|
+
sources: new Set(),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Initial run
|
|
83
|
+
runEffect(effect);
|
|
84
|
+
|
|
85
|
+
return () => disposeEffect(effect);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runEffect(effect: Effect): void {
|
|
89
|
+
// Cleanup previous dependencies
|
|
90
|
+
cleanupEffect(effect);
|
|
91
|
+
|
|
92
|
+
// Push to stack
|
|
93
|
+
effectStack.push(effect);
|
|
94
|
+
const prevObserver = currentObserver;
|
|
95
|
+
currentObserver = effect;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const cleanup = effect.fn();
|
|
99
|
+
if (typeof cleanup === 'function') {
|
|
100
|
+
effect.cleanup = cleanup;
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
// Pop from stack
|
|
104
|
+
effectStack.pop();
|
|
105
|
+
currentObserver = prevObserver;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cleanupEffect(effect: Effect): void {
|
|
110
|
+
// Remove this effect from all signal observers
|
|
111
|
+
if (effect.sources) {
|
|
112
|
+
for (const source of effect.sources) {
|
|
113
|
+
source.observers.delete(effect);
|
|
114
|
+
}
|
|
115
|
+
effect.sources.clear();
|
|
116
|
+
}
|
|
117
|
+
effect.dependencies.clear();
|
|
118
|
+
|
|
119
|
+
// Run cleanup function if exists
|
|
120
|
+
if (effect.cleanup) {
|
|
121
|
+
effect.cleanup();
|
|
122
|
+
effect.cleanup = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function disposeEffect(effect: Effect): void {
|
|
127
|
+
cleanupEffect(effect);
|
|
128
|
+
// Remove from queue if scheduled
|
|
129
|
+
const index = effectQueue.indexOf(effect);
|
|
130
|
+
if (index !== -1) {
|
|
131
|
+
effectQueue.splice(index, 1);
|
|
132
|
+
}
|
|
133
|
+
pendingEffects.delete(effect);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function scheduleEffect(effect: Effect): void {
|
|
137
|
+
if (effect.scheduled) return;
|
|
138
|
+
effect.scheduled = true;
|
|
139
|
+
effectQueue.push(effect);
|
|
140
|
+
|
|
141
|
+
if (!isFlushing && batchDepth === 0) {
|
|
142
|
+
isFlushing = true;
|
|
143
|
+
queueMicrotask(flushEffects);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function flushEffects(): void {
|
|
148
|
+
isFlushing = true;
|
|
149
|
+
|
|
150
|
+
while (effectQueue.length > 0) {
|
|
151
|
+
const effect = effectQueue.shift()!;
|
|
152
|
+
effect.scheduled = false;
|
|
153
|
+
runEffect(effect);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
isFlushing = false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Export for testing - force synchronous flush
|
|
160
|
+
export function flushSync(): void {
|
|
161
|
+
flushEffects();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function batch<T>(fn: () => T): T {
|
|
165
|
+
batchDepth++;
|
|
166
|
+
try {
|
|
167
|
+
return fn();
|
|
168
|
+
} finally {
|
|
169
|
+
batchDepth--;
|
|
170
|
+
if (batchDepth === 0) {
|
|
171
|
+
// Flush all pending effects
|
|
172
|
+
const effects = Array.from(pendingEffects);
|
|
173
|
+
pendingEffects.clear();
|
|
174
|
+
for (const effect of effects) {
|
|
175
|
+
scheduleEffect(effect);
|
|
176
|
+
}
|
|
177
|
+
if (!isFlushing && effectQueue.length > 0) {
|
|
178
|
+
isFlushing = true;
|
|
179
|
+
queueMicrotask(flushEffects);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Lifecycle hooks
|
|
186
|
+
export function onMount(fn: () => void | (() => void)): void {
|
|
187
|
+
if (typeof window !== 'undefined') {
|
|
188
|
+
createEffect(() => {
|
|
189
|
+
fn();
|
|
190
|
+
// Only run once
|
|
191
|
+
return () => {};
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function onCleanup(fn: () => void): void {
|
|
197
|
+
if (currentObserver) {
|
|
198
|
+
const prevCleanup = currentObserver.cleanup;
|
|
199
|
+
currentObserver.cleanup = () => {
|
|
200
|
+
prevCleanup?.();
|
|
201
|
+
fn();
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Export for computed.ts
|
|
207
|
+
export { currentObserver };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKxC,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAwCvE;AA6BD,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAI9D"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { signal } from './signal.js';
|
|
2
|
+
const STORE_SYMBOL = Symbol('zylaris-store');
|
|
3
|
+
export function createStore(initialValue) {
|
|
4
|
+
const state = signal(initialValue);
|
|
5
|
+
const subscribers = new Map();
|
|
6
|
+
function notify(key) {
|
|
7
|
+
const subs = subscribers.get(key);
|
|
8
|
+
if (subs) {
|
|
9
|
+
for (const sub of subs) {
|
|
10
|
+
sub();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const proxy = new Proxy(initialValue, {
|
|
15
|
+
get(target, key) {
|
|
16
|
+
if (key === STORE_SYMBOL)
|
|
17
|
+
return true;
|
|
18
|
+
if (key === '$set')
|
|
19
|
+
return (value) => state.set(value);
|
|
20
|
+
const value = target[key];
|
|
21
|
+
// Return reactive wrapper for nested objects
|
|
22
|
+
if (typeof value === 'object' && value !== null) {
|
|
23
|
+
return createNestedProxy(value, () => notify(key));
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
},
|
|
27
|
+
set(target, key, value) {
|
|
28
|
+
const oldValue = target[key];
|
|
29
|
+
if (Object.is(oldValue, value))
|
|
30
|
+
return true;
|
|
31
|
+
target[key] = value;
|
|
32
|
+
notify(key);
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return proxy;
|
|
37
|
+
}
|
|
38
|
+
function createNestedProxy(target, notify) {
|
|
39
|
+
return new Proxy(target, {
|
|
40
|
+
get(target, key) {
|
|
41
|
+
const value = target[key];
|
|
42
|
+
if (typeof value === 'object' && value !== null) {
|
|
43
|
+
return createNestedProxy(value, notify);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
},
|
|
47
|
+
set(target, key, value) {
|
|
48
|
+
const oldValue = target[key];
|
|
49
|
+
if (Object.is(oldValue, value))
|
|
50
|
+
return true;
|
|
51
|
+
target[key] = value;
|
|
52
|
+
notify();
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function isStore(value) {
|
|
58
|
+
return typeof value === 'object' &&
|
|
59
|
+
value !== null &&
|
|
60
|
+
STORE_SYMBOL in value;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,YAAY,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;AAE7C,MAAM,UAAU,WAAW,CAAmB,YAAe;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAoC,CAAC;IAEhE,SAAS,MAAM,CAAC,GAAoB;QAClC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,GAAG,EAAE,CAAC;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE;QACpC,GAAG,CAAC,MAAM,EAAE,GAAG;YACb,IAAI,GAAG,KAAK,YAAY;gBAAE,OAAO,IAAI,CAAC;YACtC,IAAI,GAAG,KAAK,MAAM;gBAAE,OAAO,CAAC,KAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAE1D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAc,CAAC,CAAC;YAErC,6CAA6C;YAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAChD,OAAO,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACrD,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK;YACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAc,CAAC,CAAC;YAExC,IAAI,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAE5C,MAAM,CAAC,GAAc,CAAC,GAAG,KAAK,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,CAAC;YAEZ,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAa,CAAC;IAEf,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CACxB,MAAS,EACT,MAAkB;IAElB,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,MAAM,EAAE,GAAG;YACb,MAAM,KAAK,GAAG,MAAM,CAAC,GAAc,CAAC,CAAC;YAErC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAChD,OAAO,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC1C,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK;YACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAc,CAAC,CAAC;YAExC,IAAI,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YAE5C,MAAM,CAAC,GAAc,CAAC,GAAG,KAAK,CAAC;YAC/B,MAAM,EAAE,CAAC;YAET,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,YAAY,IAAI,KAAK,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createStore, isStore } from './store.js';
|
|
3
|
+
|
|
4
|
+
describe('createStore', () => {
|
|
5
|
+
it('should create a store from object', () => {
|
|
6
|
+
const store = createStore({ count: 0, name: 'test' });
|
|
7
|
+
|
|
8
|
+
expect(store.count).toBe(0);
|
|
9
|
+
expect(store.name).toBe('test');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should update store values', () => {
|
|
13
|
+
const store = createStore({ count: 0 });
|
|
14
|
+
store.count = 5;
|
|
15
|
+
|
|
16
|
+
expect(store.count).toBe(5);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should handle nested objects', () => {
|
|
20
|
+
const store = createStore({ user: { name: 'John', age: 30 } });
|
|
21
|
+
|
|
22
|
+
expect(store.user.name).toBe('John');
|
|
23
|
+
expect(store.user.age).toBe(30);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('isStore', () => {
|
|
28
|
+
it('should detect store correctly', () => {
|
|
29
|
+
const store = createStore({ count: 0 });
|
|
30
|
+
const plain = { count: 0 };
|
|
31
|
+
|
|
32
|
+
expect(isStore(store)).toBe(true);
|
|
33
|
+
expect(isStore(plain)).toBe(false);
|
|
34
|
+
expect(isStore(null)).toBe(false);
|
|
35
|
+
expect(isStore(undefined)).toBe(false);
|
|
36
|
+
expect(isStore('string')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Store } from './types.js';
|
|
2
|
+
import { signal } from './signal.js';
|
|
3
|
+
|
|
4
|
+
// Use a string key instead of Symbol for cross-realm compatibility
|
|
5
|
+
const STORE_MARKER = '__zylaris_store__';
|
|
6
|
+
|
|
7
|
+
export function createStore<T extends object>(initialValue: T): Store<T> {
|
|
8
|
+
const state = signal<T>(initialValue);
|
|
9
|
+
const subscribers = new Map<string | symbol, Set<() => void>>();
|
|
10
|
+
|
|
11
|
+
function notify(key: string | symbol) {
|
|
12
|
+
const subs = subscribers.get(key);
|
|
13
|
+
if (subs) {
|
|
14
|
+
for (const sub of subs) {
|
|
15
|
+
sub();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create store object with marker
|
|
21
|
+
const storeBase: Record<string, unknown> = {
|
|
22
|
+
...initialValue,
|
|
23
|
+
[STORE_MARKER]: true
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const proxy = new Proxy(storeBase, {
|
|
27
|
+
get(target, key) {
|
|
28
|
+
if (key === STORE_MARKER) return true;
|
|
29
|
+
if (key === '$set') return (value: T) => state.set(value);
|
|
30
|
+
|
|
31
|
+
const value = target[key as string];
|
|
32
|
+
|
|
33
|
+
// Skip internal keys
|
|
34
|
+
if (key === Symbol.toStringTag || key === 'constructor') {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Return reactive wrapper for nested objects
|
|
39
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
40
|
+
return createNestedProxy(value as object, () => notify(key));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return value;
|
|
44
|
+
},
|
|
45
|
+
set(target, key, value) {
|
|
46
|
+
const oldValue = target[key as string];
|
|
47
|
+
|
|
48
|
+
if (Object.is(oldValue, value)) return true;
|
|
49
|
+
|
|
50
|
+
target[key as string] = value;
|
|
51
|
+
notify(key);
|
|
52
|
+
|
|
53
|
+
// Update underlying signal
|
|
54
|
+
state.set({ ...target } as T);
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
has(target, key) {
|
|
59
|
+
return key in target || key === STORE_MARKER;
|
|
60
|
+
},
|
|
61
|
+
ownKeys(target) {
|
|
62
|
+
return Reflect.ownKeys(target);
|
|
63
|
+
},
|
|
64
|
+
getOwnPropertyDescriptor(target, key) {
|
|
65
|
+
if (key === STORE_MARKER) {
|
|
66
|
+
return {
|
|
67
|
+
value: true,
|
|
68
|
+
writable: false,
|
|
69
|
+
enumerable: false,
|
|
70
|
+
configurable: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
74
|
+
},
|
|
75
|
+
}) as unknown as Store<T>;
|
|
76
|
+
|
|
77
|
+
return proxy;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createNestedProxy<T extends object>(
|
|
81
|
+
target: T,
|
|
82
|
+
notify: () => void
|
|
83
|
+
): T {
|
|
84
|
+
return new Proxy(target, {
|
|
85
|
+
get(target, key) {
|
|
86
|
+
const value = target[key as keyof T];
|
|
87
|
+
|
|
88
|
+
if (typeof value === 'object' && value !== null) {
|
|
89
|
+
return createNestedProxy(value, notify);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return value;
|
|
93
|
+
},
|
|
94
|
+
set(target, key, value) {
|
|
95
|
+
const oldValue = target[key as keyof T];
|
|
96
|
+
|
|
97
|
+
if (Object.is(oldValue, value)) return true;
|
|
98
|
+
|
|
99
|
+
target[key as keyof T] = value;
|
|
100
|
+
notify();
|
|
101
|
+
|
|
102
|
+
return true;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function isStore(value: unknown): value is Store<object> {
|
|
108
|
+
return typeof value === 'object' &&
|
|
109
|
+
value !== null &&
|
|
110
|
+
STORE_MARKER in value;
|
|
111
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface Signal<T> {
|
|
2
|
+
(): T;
|
|
3
|
+
set(value: T): void;
|
|
4
|
+
update(fn: (value: T) => T): void;
|
|
5
|
+
peek(): T;
|
|
6
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface Computed<T> extends Signal<T> {
|
|
9
|
+
}
|
|
10
|
+
export type ResourceState<T> = {
|
|
11
|
+
status: 'pending';
|
|
12
|
+
data?: undefined;
|
|
13
|
+
error?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
status: 'success';
|
|
16
|
+
data: T;
|
|
17
|
+
error?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
status: 'error';
|
|
20
|
+
data?: undefined;
|
|
21
|
+
error: Error;
|
|
22
|
+
};
|
|
23
|
+
export interface Resource<T> extends Signal<ResourceState<T>> {
|
|
24
|
+
refetch(): Promise<void>;
|
|
25
|
+
mutate(data: T): void;
|
|
26
|
+
}
|
|
27
|
+
export type EffectFn = () => void | (() => void);
|
|
28
|
+
export interface Effect {
|
|
29
|
+
fn: EffectFn;
|
|
30
|
+
dependencies: Set<SignalNode<unknown>>;
|
|
31
|
+
version: number;
|
|
32
|
+
scheduled: boolean;
|
|
33
|
+
cleanup?: () => void;
|
|
34
|
+
}
|
|
35
|
+
export interface SignalNode<T> {
|
|
36
|
+
value: T;
|
|
37
|
+
observers: Set<Effect>;
|
|
38
|
+
version: number;
|
|
39
|
+
}
|
|
40
|
+
export type Store<T> = T & {
|
|
41
|
+
$set(value: T): void;
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC;IACN,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAClC,IAAI,IAAI,CAAC,CAAC;IACV,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;CAAG;AAEjD,MAAM,MAAM,aAAa,CAAC,CAAC,IACvB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAC1D;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,QAAQ,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;AAEjD,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,QAAQ,CAAC;IACb,YAAY,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAGD,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG;IACzB,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CACtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,uCAAuC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Core types for the reactivity system
|
|
2
|
+
|
|
3
|
+
export interface Signal<T> {
|
|
4
|
+
(): T;
|
|
5
|
+
set(value: T): void;
|
|
6
|
+
update(fn: (value: T) => T): void;
|
|
7
|
+
peek(): T;
|
|
8
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Computed<T> extends Signal<T> {}
|
|
12
|
+
|
|
13
|
+
export type ResourceState<T> =
|
|
14
|
+
| { status: 'pending'; data?: undefined; error?: undefined }
|
|
15
|
+
| { status: 'success'; data: T; error?: undefined }
|
|
16
|
+
| { status: 'error'; data?: undefined; error: Error };
|
|
17
|
+
|
|
18
|
+
export interface Resource<T> extends Signal<ResourceState<T>> {
|
|
19
|
+
refetch(): Promise<void>;
|
|
20
|
+
mutate(data: T): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type EffectFn = () => void | (() => void);
|
|
24
|
+
|
|
25
|
+
export interface Effect {
|
|
26
|
+
fn: EffectFn;
|
|
27
|
+
dependencies: Set<SignalNode<unknown>>;
|
|
28
|
+
sources?: Set<SignalNode<unknown>>;
|
|
29
|
+
version: number;
|
|
30
|
+
scheduled: boolean;
|
|
31
|
+
cleanup?: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SignalNode<T> {
|
|
35
|
+
value: T;
|
|
36
|
+
observers: Set<Effect>;
|
|
37
|
+
version: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Store types
|
|
41
|
+
export type Store<T> = T & {
|
|
42
|
+
$set(value: T): void;
|
|
43
|
+
};
|