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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +558 -0
  3. package/Zylaris.js.png +0 -0
  4. package/examples/default/index.html +13 -0
  5. package/examples/default/package.json +23 -0
  6. package/examples/default/src/app/about/page.tsx +18 -0
  7. package/examples/default/src/app/counter/page.tsx +22 -0
  8. package/examples/default/src/app/global.css +225 -0
  9. package/examples/default/src/app/layout.tsx +33 -0
  10. package/examples/default/src/app/page.tsx +14 -0
  11. package/examples/default/src/entry-client.tsx +87 -0
  12. package/examples/default/src/entry-server.tsx +52 -0
  13. package/examples/default/src/router.ts +60 -0
  14. package/examples/default/tsconfig.json +28 -0
  15. package/examples/default/zylaris.config.ts +24 -0
  16. package/package.json +34 -0
  17. package/packages/adapter/package.json +59 -0
  18. package/packages/adapter/src/adapters/bun.ts +215 -0
  19. package/packages/adapter/src/adapters/cloudflare.ts +278 -0
  20. package/packages/adapter/src/adapters/deno.ts +219 -0
  21. package/packages/adapter/src/adapters/netlify.ts +274 -0
  22. package/packages/adapter/src/adapters/node.ts +155 -0
  23. package/packages/adapter/src/adapters/static.ts +134 -0
  24. package/packages/adapter/src/adapters/vercel.ts +239 -0
  25. package/packages/adapter/src/index.ts +115 -0
  26. package/packages/adapter/src/lib/builder.ts +361 -0
  27. package/packages/adapter/src/types.ts +191 -0
  28. package/packages/adapter/tsconfig.json +8 -0
  29. package/packages/cli/package.json +43 -0
  30. package/packages/cli/src/bin.ts +107 -0
  31. package/packages/cli/src/commands/build.ts +197 -0
  32. package/packages/cli/src/commands/create.ts +222 -0
  33. package/packages/cli/src/commands/deploy.ts +90 -0
  34. package/packages/cli/src/commands/dev.ts +108 -0
  35. package/packages/cli/src/index.ts +6 -0
  36. package/packages/cli/tsconfig.json +9 -0
  37. package/packages/compiler/package.json +39 -0
  38. package/packages/compiler/src/index.ts +210 -0
  39. package/packages/compiler/src/jit.ts +187 -0
  40. package/packages/compiler/tsconfig.json +9 -0
  41. package/packages/core/package.json +55 -0
  42. package/packages/core/src/components.test.ts +125 -0
  43. package/packages/core/src/components.ts +181 -0
  44. package/packages/core/src/config.ts +204 -0
  45. package/packages/core/src/hooks.ts +142 -0
  46. package/packages/core/src/index.ts +59 -0
  47. package/packages/core/src/jsx-runtime.ts +46 -0
  48. package/packages/core/tsconfig.json +16 -0
  49. package/packages/dev-server/package.json +51 -0
  50. package/packages/dev-server/src/index.ts +306 -0
  51. package/packages/dev-server/src/jit-middleware.ts +78 -0
  52. package/packages/dev-server/tsconfig.json +9 -0
  53. package/packages/plugins/package.json +44 -0
  54. package/packages/plugins/src/cdn/loader.ts +275 -0
  55. package/packages/plugins/src/index.ts +238 -0
  56. package/packages/plugins/src/loaders/auto-import.ts +219 -0
  57. package/packages/plugins/src/loaders/external.ts +332 -0
  58. package/packages/plugins/src/transforms/index.ts +407 -0
  59. package/packages/plugins/src/types.ts +296 -0
  60. package/packages/plugins/tsconfig.json +8 -0
  61. package/packages/reactivity/package.json +36 -0
  62. package/packages/reactivity/src/computed.d.ts +3 -0
  63. package/packages/reactivity/src/computed.d.ts.map +1 -0
  64. package/packages/reactivity/src/computed.js +64 -0
  65. package/packages/reactivity/src/computed.js.map +1 -0
  66. package/packages/reactivity/src/computed.test.ts +83 -0
  67. package/packages/reactivity/src/computed.ts +69 -0
  68. package/packages/reactivity/src/index.d.ts +6 -0
  69. package/packages/reactivity/src/index.d.ts.map +1 -0
  70. package/packages/reactivity/src/index.js +7 -0
  71. package/packages/reactivity/src/index.js.map +1 -0
  72. package/packages/reactivity/src/index.ts +18 -0
  73. package/packages/reactivity/src/resource.d.ts +6 -0
  74. package/packages/reactivity/src/resource.d.ts.map +1 -0
  75. package/packages/reactivity/src/resource.js +43 -0
  76. package/packages/reactivity/src/resource.js.map +1 -0
  77. package/packages/reactivity/src/resource.test.ts +70 -0
  78. package/packages/reactivity/src/resource.ts +59 -0
  79. package/packages/reactivity/src/signal.d.ts +7 -0
  80. package/packages/reactivity/src/signal.d.ts.map +1 -0
  81. package/packages/reactivity/src/signal.js +145 -0
  82. package/packages/reactivity/src/signal.js.map +1 -0
  83. package/packages/reactivity/src/signal.test.ts +130 -0
  84. package/packages/reactivity/src/signal.ts +207 -0
  85. package/packages/reactivity/src/store.d.ts +4 -0
  86. package/packages/reactivity/src/store.d.ts.map +1 -0
  87. package/packages/reactivity/src/store.js +62 -0
  88. package/packages/reactivity/src/store.js.map +1 -0
  89. package/packages/reactivity/src/store.test.ts +38 -0
  90. package/packages/reactivity/src/store.ts +111 -0
  91. package/packages/reactivity/src/types.d.ts +43 -0
  92. package/packages/reactivity/src/types.d.ts.map +1 -0
  93. package/packages/reactivity/src/types.js +3 -0
  94. package/packages/reactivity/src/types.js.map +1 -0
  95. package/packages/reactivity/src/types.ts +43 -0
  96. package/packages/reactivity/tsconfig.json +9 -0
  97. package/packages/router/package.json +44 -0
  98. package/packages/router/src/components.tsx +150 -0
  99. package/packages/router/src/fs-router.ts +163 -0
  100. package/packages/router/src/index.ts +22 -0
  101. package/packages/router/src/router.test.ts +111 -0
  102. package/packages/router/src/router.ts +112 -0
  103. package/packages/router/src/types.ts +69 -0
  104. package/packages/router/tsconfig.json +10 -0
  105. package/packages/server/package.json +41 -0
  106. package/packages/server/src/action.test.ts +102 -0
  107. package/packages/server/src/action.ts +201 -0
  108. package/packages/server/src/api.ts +143 -0
  109. package/packages/server/src/index.ts +18 -0
  110. package/packages/server/src/types.ts +72 -0
  111. package/packages/server/tsconfig.json +9 -0
  112. package/pnpm-workspace.yaml +4 -0
  113. package/scripts/publish.ps1 +138 -0
  114. package/scripts/publish.sh +142 -0
  115. package/tsconfig.json +28 -0
  116. 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,4 @@
1
+ import type { Store } from './types.js';
2
+ export declare function createStore<T extends object>(initialValue: T): Store<T>;
3
+ export declare function isStore(value: unknown): value is Store<object>;
4
+ //# sourceMappingURL=store.d.ts.map
@@ -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,3 @@
1
+ // Core types for the reactivity system
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -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
+ };
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["dist", "node_modules", "**/*.test.ts"]
9
+ }