@minejs/signals 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Maysara Elshewehy (https://github.com/maysara-elshewehy)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,420 @@
1
+ <!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
2
+
3
+ <br>
4
+ <div align="center">
5
+ <p>
6
+ <img src="./assets/img/logo.png" alt="logo" style="" height="60" />
7
+ </p>
8
+ </div>
9
+
10
+ <div align="center">
11
+ <img src="https://img.shields.io/badge/v-0.0.1-black"/>
12
+ <img src="https://img.shields.io/badge/🔥-@minjs-black"/>
13
+ <img src="https://img.shields.io/badge/zero-dependencies-black" alt="Test Coverage" />
14
+ <br>
15
+ <img src="https://img.shields.io/badge/coverage-99.14%25-brightgreen" alt="Test Coverage" />
16
+ <img src="https://img.shields.io/github/issues/minejs/signals?style=flat" alt="Github Repo Issues" />
17
+ <img src="https://img.shields.io/github/stars/minejs/signals?style=social" alt="GitHub Repo stars" />
18
+ </div>
19
+ <br>
20
+
21
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
22
+
23
+
24
+
25
+ <!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
26
+
27
+ - ## Quick Start 🔥
28
+
29
+ > **_A lightweight, zero-dependency signals library for reactive JavaScript applications._**
30
+
31
+ - ### Setup
32
+
33
+ > install [`space`](https://github.com/solution-lib/space) first.
34
+
35
+ ```bash
36
+ space i @minejs/signals
37
+ ```
38
+
39
+ <div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
40
+
41
+ - ### Usage
42
+
43
+ ```ts
44
+ import { signal, effect, computed, batch } from '@minejs/signals'
45
+ ```
46
+
47
+ - ### 1. Basic Signal
48
+
49
+ ```typescript
50
+ // Create a signal
51
+ const count = signal(0)
52
+
53
+ // Read value
54
+ console.log(count()) // 0
55
+
56
+ // Update value
57
+ count.set(5)
58
+ console.log(count()) // 5
59
+
60
+ // Update with function
61
+ count.update(n => n + 1)
62
+ console.log(count()) // 6
63
+ ```
64
+
65
+ - ### 2. Effects (Auto-run on changes)
66
+
67
+ ```typescript
68
+ const name = signal('John')
69
+
70
+ // Effect runs automatically when dependencies change
71
+ effect(() => {
72
+ console.log('Hello,', name())
73
+ })
74
+ // Logs: "Hello, John"
75
+
76
+ name.set('Jane')
77
+ // Logs: "Hello, Jane"
78
+ ```
79
+
80
+ - ### 3. Computed Values (Derived state)
81
+
82
+ ```typescript
83
+ const firstName = signal('John')
84
+ const lastName = signal('Doe')
85
+
86
+ // Computed value updates automatically
87
+ const fullName = computed(() => {
88
+ return `${firstName()} ${lastName()}`
89
+ })
90
+
91
+ console.log(fullName()) // "John Doe"
92
+
93
+ firstName.set('Jane')
94
+ console.log(fullName()) // "Jane Doe"
95
+ ```
96
+
97
+ - ### 4. Batch Updates (Optimize performance)
98
+
99
+ ```typescript
100
+ const a = signal(0)
101
+ const b = signal(0)
102
+
103
+ effect(() => {
104
+ console.log('Sum:', a() + b())
105
+ })
106
+ // Logs: "Sum: 0"
107
+
108
+ // Without batch: effect runs twice
109
+ a.set(1) // Logs: "Sum: 1"
110
+ b.set(2) // Logs: "Sum: 3"
111
+
112
+ // With batch: effect runs once
113
+ batch(() => {
114
+ a.set(10)
115
+ b.set(20)
116
+ })
117
+ // Logs: "Sum: 30" (only once!)
118
+ ```
119
+
120
+
121
+ <br>
122
+
123
+ - ## API Reference 🔥
124
+
125
+ - #### `signal<T>(value: T): Signal<T>`
126
+ > Create a reactive signal.
127
+
128
+ ```typescript
129
+ const count = signal(0)
130
+
131
+ count() // Read: 0
132
+ count.set(5) // Write: 5
133
+ count.update(n => n + 1) // Update: 6
134
+ count.peek() // Read without tracking: 6
135
+ ```
136
+
137
+ - #### `effect(fn: () => void | (() => void)): () => void`
138
+
139
+ > Run code automatically when dependencies change.
140
+
141
+ ```typescript
142
+ const count = signal(0)
143
+
144
+ // Effect with cleanup
145
+ const dispose = effect(() => {
146
+ console.log('Count:', count())
147
+
148
+ // Optional cleanup function
149
+ return () => {
150
+ console.log('Cleaning up...')
151
+ }
152
+ })
153
+
154
+ // Stop the effect
155
+ dispose()
156
+ ```
157
+
158
+ - #### `computed<T>(fn: () => T): Signal<T>`
159
+
160
+ > Create a derived signal (memoized).
161
+
162
+ ```typescript
163
+ const count = signal(0)
164
+ const doubled = computed(() => count() * 2)
165
+
166
+ console.log(doubled()) // 0
167
+ count.set(5)
168
+ console.log(doubled()) // 10
169
+ ```
170
+
171
+ - #### `batch<T>(fn: () => T): T`
172
+
173
+ > Batch multiple updates into one.
174
+
175
+ ```typescript
176
+ const a = signal(0)
177
+ const b = signal(0)
178
+
179
+ batch(() => {
180
+ a.set(1)
181
+ b.set(2)
182
+ // Effects run only once here
183
+ })
184
+ ```
185
+
186
+ - #### `untrack<T>(fn: () => T): T`
187
+
188
+ > Read signals without tracking dependencies.
189
+
190
+ ```typescript
191
+ const count = signal(0)
192
+
193
+ effect(() => {
194
+ const value = untrack(() => count())
195
+ // count() is read but NOT tracked
196
+ console.log(value)
197
+ })
198
+
199
+ count.set(1) // Effect does NOT run
200
+ ```
201
+
202
+ - #### `on<T>(signal: Signal<T>, fn: (value: T, prev: T) => void): () => void`
203
+
204
+ > Run effect only when specific signal changes.
205
+
206
+ ```typescript
207
+ const count = signal(0)
208
+ const other = signal('hello')
209
+
210
+ on(count, (value, prevValue) => {
211
+ console.log(`Changed from ${prevValue} to ${value}`)
212
+ other() // Can read but won't track
213
+ })
214
+
215
+ other.set('world') // Does NOT trigger
216
+ count.set(1) // DOES trigger
217
+ ```
218
+
219
+ - #### `store<T>(obj: T): { [K in keyof T]: Signal<T[K]> }`
220
+
221
+ > Create an object of signals.
222
+
223
+ ```typescript
224
+ const state = store({
225
+ count : 0,
226
+ name : 'John'
227
+ })
228
+
229
+ state.count() // 0
230
+ state.name() // 'John'
231
+
232
+ state.count.set(5)
233
+ state.name.set('Jane')
234
+ ```
235
+
236
+ - #### `root<T>(fn: (dispose: () => void) => T): T`
237
+
238
+ > Create a disposal scope for effects.
239
+
240
+ ```typescript
241
+ root((dispose) => {
242
+ effect(() => {
243
+ // ... effects ...
244
+ })
245
+
246
+ // Clean up all effects at once
247
+ dispose()
248
+ })
249
+ ```
250
+
251
+ - #### `memo<T>(fn: () => T): () => T`
252
+
253
+ > Memoize expensive computations.
254
+
255
+ ```typescript
256
+ const expensiveComputation = memo(() => {
257
+ // This computation runs only once and returns cached value
258
+ return Math.sqrt(16)
259
+ })
260
+
261
+ const result1 = expensiveComputation() // Computes
262
+ const result2 = expensiveComputation() // Returns cached value
263
+ ```
264
+
265
+ - #### `Signal.subscribe(fn: () => void): () => void`
266
+
267
+ > Subscribe to signal changes manually.
268
+
269
+ ```typescript
270
+ const count = signal(0)
271
+
272
+ const unsubscribe = count.subscribe(() => {
273
+ console.log('Signal changed!')
274
+ })
275
+
276
+ count.set(1) // Logs: "Signal changed!"
277
+
278
+ unsubscribe() // Stop listening
279
+ ```
280
+
281
+ <br>
282
+
283
+
284
+ - ## Real-World Examples
285
+
286
+ - #### Counter Component
287
+
288
+ ```typescript
289
+ import { signal, effect } from '@minejs/signals'
290
+
291
+ function Counter() {
292
+ const count = signal(0)
293
+
294
+ const button = document.createElement('button')
295
+
296
+ effect(() => {
297
+ button.textContent = `Count: ${count()}`
298
+ })
299
+
300
+ button.onclick = () => count.update(n => n + 1)
301
+
302
+ return button
303
+ }
304
+ ```
305
+
306
+ - #### Todo App
307
+
308
+ ```typescript
309
+ import { signal, computed } from '@minejs/signals'
310
+
311
+ interface Todo {
312
+ id : number
313
+ text : string
314
+ done : boolean
315
+ }
316
+
317
+ const todos = signal<Todo[]>([])
318
+ const filter = signal<'all' | 'active' | 'completed'>('all')
319
+
320
+ const filteredTodos = computed(() => {
321
+ const f = filter()
322
+ const t = todos()
323
+
324
+ if (f === 'active') return t.filter(todo => !todo.done)
325
+ if (f === 'completed') return t.filter(todo => todo.done)
326
+ return t
327
+ })
328
+
329
+ const activeTodoCount = computed(() => {
330
+ return todos().filter(t => !t.done).length
331
+ })
332
+
333
+ // Actions
334
+ function addTodo(text: string) {
335
+ todos.update(list => [
336
+ ...list,
337
+ { id: Date.now(), text, done: false }
338
+ ])
339
+ }
340
+
341
+ function toggleTodo(id: number) {
342
+ todos.update(list =>
343
+ list.map(todo =>
344
+ todo.id === id ? { ...todo, done: !todo.done } : todo
345
+ )
346
+ )
347
+ }
348
+ ```
349
+
350
+ - #### Form with Validation
351
+
352
+ ```typescript
353
+ import { signal, computed } from '@crux/signals'
354
+
355
+ const email = signal('')
356
+ const password = signal('')
357
+
358
+ const isEmailValid = computed(() => {
359
+ return email().includes('@') && email().length > 3
360
+ })
361
+
362
+ const isPasswordValid = computed(() => {
363
+ return password().length >= 8
364
+ })
365
+
366
+ const canSubmit = computed(() => {
367
+ return isEmailValid() && isPasswordValid()
368
+ })
369
+
370
+ effect(() => {
371
+ const button = document.querySelector('#submit')
372
+ button.disabled = !canSubmit()
373
+ })
374
+ ```
375
+
376
+ - #### Data Fetching
377
+
378
+ ```typescript
379
+ import { signal, effect } from '@minejs/signals'
380
+
381
+ const userId = signal<number | null>(null)
382
+ const userData = signal<any>(null)
383
+ const loading = signal(false)
384
+
385
+ effect(async () => {
386
+ const id = userId()
387
+
388
+ if (!id) return
389
+
390
+ loading.set(true)
391
+
392
+ try {
393
+ const response = await fetch(`/api/users/${id}`)
394
+ const data = await response.json()
395
+ userData.set(data)
396
+ } finally {
397
+ loading.set(false)
398
+ }
399
+ })
400
+
401
+ // Fetch user when ID changes
402
+ userId.set(123)
403
+ ```
404
+
405
+
406
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
407
+
408
+
409
+
410
+ <!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
411
+
412
+ <br>
413
+
414
+ ---
415
+
416
+ <div align="center">
417
+ <a href="https://github.com/maysara-elshewehy"><img src="https://img.shields.io/badge/by-Maysara-black"/></a>
418
+ </div>
419
+
420
+ <!-- ╚═════════════════════════════════════════════════════════════════╝ -->
package/dist/main.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var i=null,s=null,c=0,d=new Set,a=new Set;function v(n){let e=n,t=new Set;function r(){return i&&t.add(i),e}function o(u){Object.is(e,u)||(e=u,c>0?t.forEach(T=>d.add(T)):t.forEach(T=>T()));}function p(u){o(u(e));}function l(){return e}function x(u){return t.add(u),()=>t.delete(u)}let f=r;return f.set=o,f.update=p,f.peek=l,f.subscribe=x,f}function y(n){let e,t=false,r=()=>{if(t)return;e&&(e(),e=void 0);let p=i;i=r;try{let l=n();typeof l=="function"&&(e=l);}finally{i=p;}};r();let o=()=>{t||(t=true,e&&e());};return s&&s.push(o),o}function b(n){let e=v(void 0);y(()=>{e.set(n());});let t=e;return Object.defineProperty(t,"isComputed",{value:true,writable:false}),t}function g(n){c++;try{return n()}finally{if(c--,c===0){c++,a.clear();try{for(;d.size>0;){let e=Array.from(d);d.clear(),e.forEach(t=>{a.has(t)||(a.add(t),t());});}}finally{c--,a.clear();}}}}function h(n){let e=i;i=null;try{return n()}finally{i=e;}}function E(n,e){let t=n.peek();return y(()=>{let r=n(),o=h(()=>e(r,t));return t=r,o})}function C(n){let e={};for(let t in n)e[t]=v(n[t]);return e}function S(n){let e,t=false;return ()=>{if(t)return e;let r=n();return e=r,t=true,r}}function k(n){let e=[],t=s;s=e;try{return n(()=>{e.forEach(o=>o()),e.length=0,s=t;})}finally{s=t;}}var w={getCurrentEffect(){return i},getBatchDepth(){return c},getBatchedEffectsCount(){return d.size}};function m(n){return typeof n=="function"&&"set"in n&&"update"in n&&"peek"in n}function D(n){return m(n)&&"isComputed"in n}var R={signal:v,effect:y,computed:b,batch:g,untrack:h,on:E,store:C,memo:S,root:k,isSignal:m,isComputed:D,dev:w};exports.batch=g;exports.computed=b;exports.default=R;exports.dev=w;exports.effect=y;exports.isComputed=D;exports.isSignal=m;exports.memo=S;exports.on=E;exports.root=k;exports.signal=v;exports.store=C;exports.untrack=h;//# sourceMappingURL=main.cjs.map
2
+ //# sourceMappingURL=main.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/main.ts"],"names":["currentEffect","currentRoot","batchDepth","batchedEffects","flushedEffects","signal","initialValue","value","subscribers","read","write","newValue","fn","update","peek","subscribe","sig","effect","cleanup","isDisposed","execute","prevEffect","result","disposer","computed","batch","effects","untrack","on","prevValue","store","initialState","key","memo","cachedValue","hasCachedValue","root","disposers","prevRoot","d","dev","isSignal","isComputed","main_default"],"mappings":"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","file":"main.cjs","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/main.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import { Signal, EffectCleanup, Computed } from './types';\r\n export type * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ INIT ════════════════════════════════════════╗\r\n\r\n let currentEffect : (() => void) | null = null;\r\n let currentRoot : (() => void)[] | null = null;\r\n let batchDepth : number = 0;\r\n const batchedEffects : Set<() => void> = new Set<() => void>();\r\n const flushedEffects : Set<() => void> = new Set<() => void>();\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Creates a reactive signal that can be read, written, and subscribed to.\r\n * @template T - The type of value stored in the signal\r\n * @param {T} initialValue - The initial value of the signal\r\n * @returns {Signal<T>} A signal object with read, set, update, peek, and subscribe methods\r\n * @example\r\n * const count = signal(0);\r\n * console.log(count()); // 0\r\n * count.set(5); // Update value\r\n */\r\n export function signal<T>(initialValue: T): Signal<T> {\r\n let value = initialValue;\r\n const subscribers = new Set<() => void>();\r\n\r\n function read(): T {\r\n // Track dependency if inside effect\r\n if (currentEffect) {\r\n subscribers.add(currentEffect);\r\n }\r\n return value;\r\n }\r\n\r\n function write(newValue: T): void {\r\n // Only update if value actually changed\r\n if (Object.is(value, newValue)) return;\r\n\r\n value = newValue;\r\n\r\n // Notify all subscribers\r\n if (batchDepth > 0) {\r\n // Batch mode: collect effects\r\n subscribers.forEach(fn => batchedEffects.add(fn));\r\n } else {\r\n // Immediate mode: run effects now\r\n subscribers.forEach(fn => fn());\r\n }\r\n }\r\n\r\n function update(fn: (prev: T) => T): void {\r\n write(fn(value));\r\n }\r\n\r\n function peek(): T {\r\n // Read without tracking\r\n return value;\r\n }\r\n\r\n function subscribe(fn: () => void): () => void {\r\n subscribers.add(fn);\r\n return () => subscribers.delete(fn);\r\n }\r\n\r\n // Create signal function with methods\r\n const sig = read as Signal<T>;\r\n sig.set = write;\r\n sig.update = update;\r\n sig.peek = peek;\r\n sig.subscribe = subscribe;\r\n\r\n return sig;\r\n }\r\n\r\n /**\r\n * Automatically runs a function when its signal dependencies change.\r\n * @param {() => EffectCleanup} fn - The effect function to run. Can optionally return a cleanup function.\r\n * @returns {() => void} A dispose function to stop the effect and clean up\r\n * @example\r\n * const count = signal(0);\r\n * effect(() => {\r\n * console.log('Count:', count());\r\n * return () => console.log('Cleaning up');\r\n * });\r\n */\r\n export function effect(fn: () => EffectCleanup): () => void {\r\n let cleanup: (() => void) | undefined;\r\n let isDisposed = false;\r\n\r\n const execute = () => {\r\n if (isDisposed) return;\r\n\r\n // Run cleanup from previous execution\r\n if (cleanup) {\r\n cleanup();\r\n cleanup = undefined;\r\n }\r\n\r\n // Set as current effect for dependency tracking\r\n const prevEffect = currentEffect;\r\n currentEffect = execute;\r\n\r\n try {\r\n // Run the effect function\r\n const result = fn();\r\n\r\n // Store cleanup if returned\r\n if (typeof result === 'function') {\r\n cleanup = result;\r\n }\r\n } finally {\r\n // Restore previous effect\r\n currentEffect = prevEffect;\r\n }\r\n };\r\n\r\n // Run immediately\r\n execute();\r\n\r\n // Create dispose function\r\n const disposer = () => {\r\n if (isDisposed) return;\r\n isDisposed = true;\r\n if (cleanup) cleanup();\r\n };\r\n\r\n // Register with current root if one exists\r\n if (currentRoot) {\r\n currentRoot.push(disposer);\r\n }\r\n\r\n // Return dispose function\r\n return disposer;\r\n }\r\n\r\n /**\r\n * Creates a computed signal that automatically updates when its dependencies change.\r\n * The computation result is cached and only recomputed when dependencies change.\r\n * @template T - The type of value computed\r\n * @param {() => T} fn - The computation function\r\n * @returns {Computed<T>} A read-only computed signal\r\n * @example\r\n * const count = signal(5);\r\n * const doubled = computed(() => count() * 2);\r\n * console.log(doubled()); // 10\r\n */\r\n export function computed<T>(fn: () => T): Computed<T> {\r\n const sig = signal<T>(undefined as T);\r\n\r\n // Create effect that updates the signal\r\n effect(() => {\r\n sig.set(fn());\r\n });\r\n\r\n // Mark as computed\r\n const computed = sig as Computed<T>;\r\n Object.defineProperty(computed, 'isComputed', {\r\n value: true,\r\n writable: false\r\n });\r\n\r\n return computed;\r\n }\r\n\r\n /**\r\n * Groups multiple signal updates together, deferring effect execution until all updates complete.\r\n * This improves performance by preventing cascading effect runs.\r\n * @template T - The return type of the function\r\n * @param {() => T} fn - A function that performs multiple signal updates\r\n * @returns {T} The return value of the function\r\n * @example\r\n * const a = signal(1);\r\n * const b = signal(2);\r\n * batch(() => {\r\n * a.set(10);\r\n * b.set(20);\r\n * }); // Effects only run once\r\n */\r\n export function batch<T>(fn: () => T): T {\r\n batchDepth++;\r\n\r\n try {\r\n return fn();\r\n } finally {\r\n batchDepth--;\r\n\r\n // If we're back at depth 0, flush batched effects\r\n if (batchDepth === 0) {\r\n // Keep batch mode active while flushing to prevent cascading effects\r\n batchDepth++;\r\n flushedEffects.clear();\r\n try {\r\n // Keep running effects until no more are queued\r\n while (batchedEffects.size > 0) {\r\n const effects = Array.from(batchedEffects);\r\n batchedEffects.clear();\r\n effects.forEach(fn => {\r\n // Only run if we haven't run it in this batch\r\n if (!flushedEffects.has(fn)) {\r\n flushedEffects.add(fn);\r\n fn();\r\n }\r\n });\r\n }\r\n } finally {\r\n batchDepth--;\r\n flushedEffects.clear();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Reads signals without creating dependencies on them.\r\n * Useful for accessing signal values without triggering effect re-runs.\r\n * @template T - The return type of the function\r\n * @param {() => T} fn - A function that accesses signals\r\n * @returns {T} The return value of the function\r\n * @example\r\n * const count = signal(0);\r\n * effect(() => {\r\n * const value = untrack(() => count()); // Won't trigger re-run\r\n * console.log(value);\r\n * });\r\n */\r\n export function untrack<T>(fn: () => T): T {\r\n const prevEffect = currentEffect;\r\n currentEffect = null;\r\n\r\n try {\r\n return fn();\r\n } finally {\r\n currentEffect = prevEffect;\r\n }\r\n }\r\n\r\n /**\r\n * Runs an effect only when a specific signal changes, providing both new and previous values.\r\n * @template T - The type of the signal\r\n * @param {Signal<T>} sig - The signal to watch\r\n * @param {(value: T, prevValue: T) => EffectCleanup} fn - Effect function called with new and previous values\r\n * @returns {() => void} A dispose function to stop watching\r\n * @example\r\n * const count = signal(0);\r\n * on(count, (newVal, oldVal) => {\r\n * console.log(`Changed from ${oldVal} to ${newVal}`);\r\n * });\r\n */\r\n export function on<T>(\r\n sig: Signal<T>,\r\n fn: (value: T, prevValue: T) => EffectCleanup\r\n ): () => void {\r\n let prevValue = sig.peek();\r\n\r\n return effect(() => {\r\n // Read the signal to create dependency\r\n const value = sig();\r\n\r\n // Run callback without tracking new dependencies\r\n const cleanup = untrack(() => fn(value, prevValue));\r\n prevValue = value;\r\n return cleanup;\r\n });\r\n }\r\n\r\n /**\r\n * Creates a store object where each property is a signal.\r\n * Provides a convenient way to manage multiple related reactive values.\r\n * @template T - The type of the initial state object\r\n * @param {T} initialState - An object with initial values\r\n * @returns {{ [K in keyof T]: Signal<T[K]> }} An object with signals for each property\r\n * @example\r\n * const state = store({ count: 0, name: 'John' });\r\n * console.log(state.count()); // 0\r\n * state.name.set('Jane');\r\n */\r\n export function store<T extends Record<string, any>>(\r\n initialState: T\r\n ): { [K in keyof T]: Signal<T[K]> } {\r\n const store = {} as any;\r\n\r\n for (const key in initialState) {\r\n store[key] = signal(initialState[key]);\r\n }\r\n\r\n return store;\r\n }\r\n\r\n /**\r\n * Memoizes the result of an expensive computation, caching it indefinitely.\r\n * Unlike computed, this doesn't depend on reactive signals.\r\n * @template T - The type of the memoized value\r\n * @param {() => T} fn - A function that performs the computation\r\n * @returns {() => T} A function that returns the cached result\r\n * @example\r\n * const expensiveCalc = memo(() => {\r\n * return Array.from({ length: 1000 }).map(expensiveOp);\r\n * });\r\n * const result = expensiveCalc(); // Computed only once\r\n */\r\n export function memo<T>(fn: () => T): () => T {\r\n let cachedValue: T | undefined;\r\n let hasCachedValue = false;\r\n\r\n return () => {\r\n if (hasCachedValue) {\r\n return cachedValue as T;\r\n }\r\n\r\n // Compute the value\r\n const value = fn();\r\n\r\n // Cache it\r\n cachedValue = value;\r\n hasCachedValue = true;\r\n\r\n return value;\r\n };\r\n }\r\n\r\n /**\r\n * Creates a root scope for managing effect and computed signal lifecycles.\r\n * All effects and disposers created within the function are collected and can be cleaned up together.\r\n * @template T - The return type of the function\r\n * @param {(dispose: () => void) => T} fn - A function that receives a dispose function\r\n * @returns {T} The return value of the function\r\n * @example\r\n * const dispose = root((dispose) => {\r\n * effect(() => console.log('Running'));\r\n * return 42;\r\n * });\r\n * dispose(); // Cleans up all effects created in the root\r\n */\r\n export function root<T>(fn: (dispose: () => void) => T): T {\r\n const disposers: (() => void)[] = [];\r\n const prevRoot = currentRoot;\r\n currentRoot = disposers;\r\n\r\n try {\r\n const dispose = () => {\r\n disposers.forEach(d => d());\r\n disposers.length = 0;\r\n currentRoot = prevRoot;\r\n };\r\n\r\n return fn(dispose);\r\n } finally {\r\n currentRoot = prevRoot;\r\n }\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ HELP ════════════════════════════════════════╗\r\n\r\n /**\r\n * Development utilities for debugging signal reactivity\r\n */\r\n export const dev = {\r\n /**\r\n * Returns the currently executing effect, or null if no effect is running\r\n * @returns {(() => void) | null} The current effect function or null\r\n */\r\n getCurrentEffect(): (() => void) | null {\r\n return currentEffect;\r\n },\r\n\r\n /**\r\n * Returns the current batch depth (for debugging nested batch calls)\r\n * @returns {number} The current batch nesting level\r\n */\r\n getBatchDepth(): number {\r\n return batchDepth;\r\n },\r\n\r\n /**\r\n * Returns the count of effects currently pending in the batch queue\r\n * @returns {number} The number of batched effects waiting to run\r\n */\r\n getBatchedEffectsCount(): number {\r\n return batchedEffects.size;\r\n }\r\n };\r\n\r\n /**\r\n * Type guard to check if a value is a signal\r\n * @template T - The type of value the signal contains\r\n * @param {any} value - The value to check\r\n * @returns {boolean} True if the value is a signal\r\n * @example\r\n * if (isSignal(myValue)) {\r\n * console.log(myValue());\r\n * }\r\n */\r\n export function isSignal<T>(value: any): value is Signal<T> {\r\n return (\r\n typeof value === 'function' &&\r\n 'set' in value &&\r\n 'update' in value &&\r\n 'peek' in value\r\n );\r\n }\r\n\r\n /**\r\n * Type guard to check if a value is a computed signal\r\n * @template T - The type of value the computed signal contains\r\n * @param {any} value - The value to check\r\n * @returns {boolean} True if the value is a computed signal\r\n * @example\r\n * if (isComputed(myValue)) {\r\n * console.log('This is a computed signal');\r\n * }\r\n */\r\n export function isComputed<T>(value: any): value is Computed<T> {\r\n return isSignal(value) && 'isComputed' in value;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export default {\r\n signal,\r\n effect,\r\n computed,\r\n batch,\r\n untrack,\r\n on,\r\n store,\r\n memo,\r\n root,\r\n isSignal,\r\n isComputed,\r\n dev\r\n };\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
@@ -0,0 +1,253 @@
1
+ // src/types.d.ts
2
+ //
3
+ // Made with ❤️ by Maysara.
4
+
5
+
6
+
7
+ // ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗
8
+
9
+ /**
10
+ * Represents a reactive signal that holds a value and notifies dependents when changed.
11
+ * @template T - The type of value stored in the signal
12
+ */
13
+ interface Signal<T> {
14
+ /**
15
+ * Reads the current signal value and establishes a dependency if inside an effect.
16
+ * @returns {T} The current value
17
+ */
18
+ (): T;
19
+
20
+ /**
21
+ * Sets the signal to a new value and notifies all subscribers.
22
+ * @param {T} value - The new value to set
23
+ */
24
+
25
+ set(value: T): void;
26
+ /**
27
+ * Updates the signal by applying a function to its current value.
28
+ * @param {(prev: T) => T} fn - Function that receives current value and returns new value
29
+ */
30
+
31
+ update(fn: (prev: T) => T): void;
32
+ /**
33
+ * Reads the signal value without creating a dependency relationship.
34
+ * @returns {T} The current value
35
+ */
36
+
37
+ peek(): T;
38
+ /**
39
+ * Subscribes to signal changes.
40
+ * @param {() => void} fn - Callback function to execute when signal changes
41
+ * @returns {() => void} Unsubscribe function
42
+ */
43
+ subscribe(fn: () => void): () => void;
44
+ }
45
+
46
+ /**
47
+ * Represents a computed (memoized) signal derived from other signals.
48
+ * Automatically updates when dependencies change and is read-only.
49
+ * @template T - The type of value computed
50
+ */
51
+ interface Computed<T> extends Signal<T> {
52
+ /** Marks this signal as computed for type checking purposes */
53
+ readonly isComputed: true;
54
+ }
55
+
56
+ /**
57
+ * The return type of an effect function.
58
+ * Can be void or a cleanup function that runs when the effect is disposed.
59
+ */
60
+ type EffectCleanup = void | (() => void);
61
+
62
+ /**
63
+ * Creates a reactive signal that can be read, written, and subscribed to.
64
+ * @template T - The type of value stored in the signal
65
+ * @param {T} initialValue - The initial value of the signal
66
+ * @returns {Signal<T>} A signal object with read, set, update, peek, and subscribe methods
67
+ * @example
68
+ * const count = signal(0);
69
+ * console.log(count()); // 0
70
+ * count.set(5); // Update value
71
+ */
72
+ declare function signal<T>(initialValue: T): Signal<T>;
73
+ /**
74
+ * Automatically runs a function when its signal dependencies change.
75
+ * @param {() => EffectCleanup} fn - The effect function to run. Can optionally return a cleanup function.
76
+ * @returns {() => void} A dispose function to stop the effect and clean up
77
+ * @example
78
+ * const count = signal(0);
79
+ * effect(() => {
80
+ * console.log('Count:', count());
81
+ * return () => console.log('Cleaning up');
82
+ * });
83
+ */
84
+ declare function effect(fn: () => EffectCleanup): () => void;
85
+ /**
86
+ * Creates a computed signal that automatically updates when its dependencies change.
87
+ * The computation result is cached and only recomputed when dependencies change.
88
+ * @template T - The type of value computed
89
+ * @param {() => T} fn - The computation function
90
+ * @returns {Computed<T>} A read-only computed signal
91
+ * @example
92
+ * const count = signal(5);
93
+ * const doubled = computed(() => count() * 2);
94
+ * console.log(doubled()); // 10
95
+ */
96
+ declare function computed<T>(fn: () => T): Computed<T>;
97
+ /**
98
+ * Groups multiple signal updates together, deferring effect execution until all updates complete.
99
+ * This improves performance by preventing cascading effect runs.
100
+ * @template T - The return type of the function
101
+ * @param {() => T} fn - A function that performs multiple signal updates
102
+ * @returns {T} The return value of the function
103
+ * @example
104
+ * const a = signal(1);
105
+ * const b = signal(2);
106
+ * batch(() => {
107
+ * a.set(10);
108
+ * b.set(20);
109
+ * }); // Effects only run once
110
+ */
111
+ declare function batch<T>(fn: () => T): T;
112
+ /**
113
+ * Reads signals without creating dependencies on them.
114
+ * Useful for accessing signal values without triggering effect re-runs.
115
+ * @template T - The return type of the function
116
+ * @param {() => T} fn - A function that accesses signals
117
+ * @returns {T} The return value of the function
118
+ * @example
119
+ * const count = signal(0);
120
+ * effect(() => {
121
+ * const value = untrack(() => count()); // Won't trigger re-run
122
+ * console.log(value);
123
+ * });
124
+ */
125
+ declare function untrack<T>(fn: () => T): T;
126
+ /**
127
+ * Runs an effect only when a specific signal changes, providing both new and previous values.
128
+ * @template T - The type of the signal
129
+ * @param {Signal<T>} sig - The signal to watch
130
+ * @param {(value: T, prevValue: T) => EffectCleanup} fn - Effect function called with new and previous values
131
+ * @returns {() => void} A dispose function to stop watching
132
+ * @example
133
+ * const count = signal(0);
134
+ * on(count, (newVal, oldVal) => {
135
+ * console.log(`Changed from ${oldVal} to ${newVal}`);
136
+ * });
137
+ */
138
+ declare function on<T>(sig: Signal<T>, fn: (value: T, prevValue: T) => EffectCleanup): () => void;
139
+ /**
140
+ * Creates a store object where each property is a signal.
141
+ * Provides a convenient way to manage multiple related reactive values.
142
+ * @template T - The type of the initial state object
143
+ * @param {T} initialState - An object with initial values
144
+ * @returns {{ [K in keyof T]: Signal<T[K]> }} An object with signals for each property
145
+ * @example
146
+ * const state = store({ count: 0, name: 'John' });
147
+ * console.log(state.count()); // 0
148
+ * state.name.set('Jane');
149
+ */
150
+ declare function store<T extends Record<string, any>>(initialState: T): {
151
+ [K in keyof T]: Signal<T[K]>;
152
+ };
153
+ /**
154
+ * Memoizes the result of an expensive computation, caching it indefinitely.
155
+ * Unlike computed, this doesn't depend on reactive signals.
156
+ * @template T - The type of the memoized value
157
+ * @param {() => T} fn - A function that performs the computation
158
+ * @returns {() => T} A function that returns the cached result
159
+ * @example
160
+ * const expensiveCalc = memo(() => {
161
+ * return Array.from({ length: 1000 }).map(expensiveOp);
162
+ * });
163
+ * const result = expensiveCalc(); // Computed only once
164
+ */
165
+ declare function memo<T>(fn: () => T): () => T;
166
+ /**
167
+ * Creates a root scope for managing effect and computed signal lifecycles.
168
+ * All effects and disposers created within the function are collected and can be cleaned up together.
169
+ * @template T - The return type of the function
170
+ * @param {(dispose: () => void) => T} fn - A function that receives a dispose function
171
+ * @returns {T} The return value of the function
172
+ * @example
173
+ * const dispose = root((dispose) => {
174
+ * effect(() => console.log('Running'));
175
+ * return 42;
176
+ * });
177
+ * dispose(); // Cleans up all effects created in the root
178
+ */
179
+ declare function root<T>(fn: (dispose: () => void) => T): T;
180
+ /**
181
+ * Development utilities for debugging signal reactivity
182
+ */
183
+ declare const dev: {
184
+ /**
185
+ * Returns the currently executing effect, or null if no effect is running
186
+ * @returns {(() => void) | null} The current effect function or null
187
+ */
188
+ getCurrentEffect(): (() => void) | null;
189
+ /**
190
+ * Returns the current batch depth (for debugging nested batch calls)
191
+ * @returns {number} The current batch nesting level
192
+ */
193
+ getBatchDepth(): number;
194
+ /**
195
+ * Returns the count of effects currently pending in the batch queue
196
+ * @returns {number} The number of batched effects waiting to run
197
+ */
198
+ getBatchedEffectsCount(): number;
199
+ };
200
+ /**
201
+ * Type guard to check if a value is a signal
202
+ * @template T - The type of value the signal contains
203
+ * @param {any} value - The value to check
204
+ * @returns {boolean} True if the value is a signal
205
+ * @example
206
+ * if (isSignal(myValue)) {
207
+ * console.log(myValue());
208
+ * }
209
+ */
210
+ declare function isSignal<T>(value: any): value is Signal<T>;
211
+ /**
212
+ * Type guard to check if a value is a computed signal
213
+ * @template T - The type of value the computed signal contains
214
+ * @param {any} value - The value to check
215
+ * @returns {boolean} True if the value is a computed signal
216
+ * @example
217
+ * if (isComputed(myValue)) {
218
+ * console.log('This is a computed signal');
219
+ * }
220
+ */
221
+ declare function isComputed<T>(value: any): value is Computed<T>;
222
+ declare const _default: {
223
+ signal: typeof signal;
224
+ effect: typeof effect;
225
+ computed: typeof computed;
226
+ batch: typeof batch;
227
+ untrack: typeof untrack;
228
+ on: typeof on;
229
+ store: typeof store;
230
+ memo: typeof memo;
231
+ root: typeof root;
232
+ isSignal: typeof isSignal;
233
+ isComputed: typeof isComputed;
234
+ dev: {
235
+ /**
236
+ * Returns the currently executing effect, or null if no effect is running
237
+ * @returns {(() => void) | null} The current effect function or null
238
+ */
239
+ getCurrentEffect(): (() => void) | null;
240
+ /**
241
+ * Returns the current batch depth (for debugging nested batch calls)
242
+ * @returns {number} The current batch nesting level
243
+ */
244
+ getBatchDepth(): number;
245
+ /**
246
+ * Returns the count of effects currently pending in the batch queue
247
+ * @returns {number} The number of batched effects waiting to run
248
+ */
249
+ getBatchedEffectsCount(): number;
250
+ };
251
+ };
252
+
253
+ export { type Computed, type EffectCleanup, type Signal, batch, computed, _default as default, dev, effect, isComputed, isSignal, memo, on, root, signal, store, untrack };
package/dist/main.d.ts ADDED
@@ -0,0 +1,253 @@
1
+ // src/types.d.ts
2
+ //
3
+ // Made with ❤️ by Maysara.
4
+
5
+
6
+
7
+ // ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗
8
+
9
+ /**
10
+ * Represents a reactive signal that holds a value and notifies dependents when changed.
11
+ * @template T - The type of value stored in the signal
12
+ */
13
+ interface Signal<T> {
14
+ /**
15
+ * Reads the current signal value and establishes a dependency if inside an effect.
16
+ * @returns {T} The current value
17
+ */
18
+ (): T;
19
+
20
+ /**
21
+ * Sets the signal to a new value and notifies all subscribers.
22
+ * @param {T} value - The new value to set
23
+ */
24
+
25
+ set(value: T): void;
26
+ /**
27
+ * Updates the signal by applying a function to its current value.
28
+ * @param {(prev: T) => T} fn - Function that receives current value and returns new value
29
+ */
30
+
31
+ update(fn: (prev: T) => T): void;
32
+ /**
33
+ * Reads the signal value without creating a dependency relationship.
34
+ * @returns {T} The current value
35
+ */
36
+
37
+ peek(): T;
38
+ /**
39
+ * Subscribes to signal changes.
40
+ * @param {() => void} fn - Callback function to execute when signal changes
41
+ * @returns {() => void} Unsubscribe function
42
+ */
43
+ subscribe(fn: () => void): () => void;
44
+ }
45
+
46
+ /**
47
+ * Represents a computed (memoized) signal derived from other signals.
48
+ * Automatically updates when dependencies change and is read-only.
49
+ * @template T - The type of value computed
50
+ */
51
+ interface Computed<T> extends Signal<T> {
52
+ /** Marks this signal as computed for type checking purposes */
53
+ readonly isComputed: true;
54
+ }
55
+
56
+ /**
57
+ * The return type of an effect function.
58
+ * Can be void or a cleanup function that runs when the effect is disposed.
59
+ */
60
+ type EffectCleanup = void | (() => void);
61
+
62
+ /**
63
+ * Creates a reactive signal that can be read, written, and subscribed to.
64
+ * @template T - The type of value stored in the signal
65
+ * @param {T} initialValue - The initial value of the signal
66
+ * @returns {Signal<T>} A signal object with read, set, update, peek, and subscribe methods
67
+ * @example
68
+ * const count = signal(0);
69
+ * console.log(count()); // 0
70
+ * count.set(5); // Update value
71
+ */
72
+ declare function signal<T>(initialValue: T): Signal<T>;
73
+ /**
74
+ * Automatically runs a function when its signal dependencies change.
75
+ * @param {() => EffectCleanup} fn - The effect function to run. Can optionally return a cleanup function.
76
+ * @returns {() => void} A dispose function to stop the effect and clean up
77
+ * @example
78
+ * const count = signal(0);
79
+ * effect(() => {
80
+ * console.log('Count:', count());
81
+ * return () => console.log('Cleaning up');
82
+ * });
83
+ */
84
+ declare function effect(fn: () => EffectCleanup): () => void;
85
+ /**
86
+ * Creates a computed signal that automatically updates when its dependencies change.
87
+ * The computation result is cached and only recomputed when dependencies change.
88
+ * @template T - The type of value computed
89
+ * @param {() => T} fn - The computation function
90
+ * @returns {Computed<T>} A read-only computed signal
91
+ * @example
92
+ * const count = signal(5);
93
+ * const doubled = computed(() => count() * 2);
94
+ * console.log(doubled()); // 10
95
+ */
96
+ declare function computed<T>(fn: () => T): Computed<T>;
97
+ /**
98
+ * Groups multiple signal updates together, deferring effect execution until all updates complete.
99
+ * This improves performance by preventing cascading effect runs.
100
+ * @template T - The return type of the function
101
+ * @param {() => T} fn - A function that performs multiple signal updates
102
+ * @returns {T} The return value of the function
103
+ * @example
104
+ * const a = signal(1);
105
+ * const b = signal(2);
106
+ * batch(() => {
107
+ * a.set(10);
108
+ * b.set(20);
109
+ * }); // Effects only run once
110
+ */
111
+ declare function batch<T>(fn: () => T): T;
112
+ /**
113
+ * Reads signals without creating dependencies on them.
114
+ * Useful for accessing signal values without triggering effect re-runs.
115
+ * @template T - The return type of the function
116
+ * @param {() => T} fn - A function that accesses signals
117
+ * @returns {T} The return value of the function
118
+ * @example
119
+ * const count = signal(0);
120
+ * effect(() => {
121
+ * const value = untrack(() => count()); // Won't trigger re-run
122
+ * console.log(value);
123
+ * });
124
+ */
125
+ declare function untrack<T>(fn: () => T): T;
126
+ /**
127
+ * Runs an effect only when a specific signal changes, providing both new and previous values.
128
+ * @template T - The type of the signal
129
+ * @param {Signal<T>} sig - The signal to watch
130
+ * @param {(value: T, prevValue: T) => EffectCleanup} fn - Effect function called with new and previous values
131
+ * @returns {() => void} A dispose function to stop watching
132
+ * @example
133
+ * const count = signal(0);
134
+ * on(count, (newVal, oldVal) => {
135
+ * console.log(`Changed from ${oldVal} to ${newVal}`);
136
+ * });
137
+ */
138
+ declare function on<T>(sig: Signal<T>, fn: (value: T, prevValue: T) => EffectCleanup): () => void;
139
+ /**
140
+ * Creates a store object where each property is a signal.
141
+ * Provides a convenient way to manage multiple related reactive values.
142
+ * @template T - The type of the initial state object
143
+ * @param {T} initialState - An object with initial values
144
+ * @returns {{ [K in keyof T]: Signal<T[K]> }} An object with signals for each property
145
+ * @example
146
+ * const state = store({ count: 0, name: 'John' });
147
+ * console.log(state.count()); // 0
148
+ * state.name.set('Jane');
149
+ */
150
+ declare function store<T extends Record<string, any>>(initialState: T): {
151
+ [K in keyof T]: Signal<T[K]>;
152
+ };
153
+ /**
154
+ * Memoizes the result of an expensive computation, caching it indefinitely.
155
+ * Unlike computed, this doesn't depend on reactive signals.
156
+ * @template T - The type of the memoized value
157
+ * @param {() => T} fn - A function that performs the computation
158
+ * @returns {() => T} A function that returns the cached result
159
+ * @example
160
+ * const expensiveCalc = memo(() => {
161
+ * return Array.from({ length: 1000 }).map(expensiveOp);
162
+ * });
163
+ * const result = expensiveCalc(); // Computed only once
164
+ */
165
+ declare function memo<T>(fn: () => T): () => T;
166
+ /**
167
+ * Creates a root scope for managing effect and computed signal lifecycles.
168
+ * All effects and disposers created within the function are collected and can be cleaned up together.
169
+ * @template T - The return type of the function
170
+ * @param {(dispose: () => void) => T} fn - A function that receives a dispose function
171
+ * @returns {T} The return value of the function
172
+ * @example
173
+ * const dispose = root((dispose) => {
174
+ * effect(() => console.log('Running'));
175
+ * return 42;
176
+ * });
177
+ * dispose(); // Cleans up all effects created in the root
178
+ */
179
+ declare function root<T>(fn: (dispose: () => void) => T): T;
180
+ /**
181
+ * Development utilities for debugging signal reactivity
182
+ */
183
+ declare const dev: {
184
+ /**
185
+ * Returns the currently executing effect, or null if no effect is running
186
+ * @returns {(() => void) | null} The current effect function or null
187
+ */
188
+ getCurrentEffect(): (() => void) | null;
189
+ /**
190
+ * Returns the current batch depth (for debugging nested batch calls)
191
+ * @returns {number} The current batch nesting level
192
+ */
193
+ getBatchDepth(): number;
194
+ /**
195
+ * Returns the count of effects currently pending in the batch queue
196
+ * @returns {number} The number of batched effects waiting to run
197
+ */
198
+ getBatchedEffectsCount(): number;
199
+ };
200
+ /**
201
+ * Type guard to check if a value is a signal
202
+ * @template T - The type of value the signal contains
203
+ * @param {any} value - The value to check
204
+ * @returns {boolean} True if the value is a signal
205
+ * @example
206
+ * if (isSignal(myValue)) {
207
+ * console.log(myValue());
208
+ * }
209
+ */
210
+ declare function isSignal<T>(value: any): value is Signal<T>;
211
+ /**
212
+ * Type guard to check if a value is a computed signal
213
+ * @template T - The type of value the computed signal contains
214
+ * @param {any} value - The value to check
215
+ * @returns {boolean} True if the value is a computed signal
216
+ * @example
217
+ * if (isComputed(myValue)) {
218
+ * console.log('This is a computed signal');
219
+ * }
220
+ */
221
+ declare function isComputed<T>(value: any): value is Computed<T>;
222
+ declare const _default: {
223
+ signal: typeof signal;
224
+ effect: typeof effect;
225
+ computed: typeof computed;
226
+ batch: typeof batch;
227
+ untrack: typeof untrack;
228
+ on: typeof on;
229
+ store: typeof store;
230
+ memo: typeof memo;
231
+ root: typeof root;
232
+ isSignal: typeof isSignal;
233
+ isComputed: typeof isComputed;
234
+ dev: {
235
+ /**
236
+ * Returns the currently executing effect, or null if no effect is running
237
+ * @returns {(() => void) | null} The current effect function or null
238
+ */
239
+ getCurrentEffect(): (() => void) | null;
240
+ /**
241
+ * Returns the current batch depth (for debugging nested batch calls)
242
+ * @returns {number} The current batch nesting level
243
+ */
244
+ getBatchDepth(): number;
245
+ /**
246
+ * Returns the count of effects currently pending in the batch queue
247
+ * @returns {number} The number of batched effects waiting to run
248
+ */
249
+ getBatchedEffectsCount(): number;
250
+ };
251
+ };
252
+
253
+ export { type Computed, type EffectCleanup, type Signal, batch, computed, _default as default, dev, effect, isComputed, isSignal, memo, on, root, signal, store, untrack };
package/dist/main.js ADDED
@@ -0,0 +1,2 @@
1
+ var i=null,s=null,c=0,d=new Set,a=new Set;function v(n){let e=n,t=new Set;function r(){return i&&t.add(i),e}function o(u){Object.is(e,u)||(e=u,c>0?t.forEach(T=>d.add(T)):t.forEach(T=>T()));}function p(u){o(u(e));}function l(){return e}function x(u){return t.add(u),()=>t.delete(u)}let f=r;return f.set=o,f.update=p,f.peek=l,f.subscribe=x,f}function y(n){let e,t=false,r=()=>{if(t)return;e&&(e(),e=void 0);let p=i;i=r;try{let l=n();typeof l=="function"&&(e=l);}finally{i=p;}};r();let o=()=>{t||(t=true,e&&e());};return s&&s.push(o),o}function b(n){let e=v(void 0);y(()=>{e.set(n());});let t=e;return Object.defineProperty(t,"isComputed",{value:true,writable:false}),t}function g(n){c++;try{return n()}finally{if(c--,c===0){c++,a.clear();try{for(;d.size>0;){let e=Array.from(d);d.clear(),e.forEach(t=>{a.has(t)||(a.add(t),t());});}}finally{c--,a.clear();}}}}function h(n){let e=i;i=null;try{return n()}finally{i=e;}}function E(n,e){let t=n.peek();return y(()=>{let r=n(),o=h(()=>e(r,t));return t=r,o})}function C(n){let e={};for(let t in n)e[t]=v(n[t]);return e}function S(n){let e,t=false;return ()=>{if(t)return e;let r=n();return e=r,t=true,r}}function k(n){let e=[],t=s;s=e;try{return n(()=>{e.forEach(o=>o()),e.length=0,s=t;})}finally{s=t;}}var w={getCurrentEffect(){return i},getBatchDepth(){return c},getBatchedEffectsCount(){return d.size}};function m(n){return typeof n=="function"&&"set"in n&&"update"in n&&"peek"in n}function D(n){return m(n)&&"isComputed"in n}var R={signal:v,effect:y,computed:b,batch:g,untrack:h,on:E,store:C,memo:S,root:k,isSignal:m,isComputed:D,dev:w};export{g as batch,b as computed,R as default,w as dev,y as effect,D as isComputed,m as isSignal,S as memo,E as on,k as root,v as signal,C as store,h as untrack};//# sourceMappingURL=main.js.map
2
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/main.ts"],"names":["currentEffect","currentRoot","batchDepth","batchedEffects","flushedEffects","signal","initialValue","value","subscribers","read","write","newValue","fn","update","peek","subscribe","sig","effect","cleanup","isDisposed","execute","prevEffect","result","disposer","computed","batch","effects","untrack","on","prevValue","store","initialState","key","memo","cachedValue","hasCachedValue","root","disposers","prevRoot","d","dev","isSignal","isComputed","main_default"],"mappings":"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","file":"main.js","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/main.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import { Signal, EffectCleanup, Computed } from './types';\r\n export type * from './types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ INIT ════════════════════════════════════════╗\r\n\r\n let currentEffect : (() => void) | null = null;\r\n let currentRoot : (() => void)[] | null = null;\r\n let batchDepth : number = 0;\r\n const batchedEffects : Set<() => void> = new Set<() => void>();\r\n const flushedEffects : Set<() => void> = new Set<() => void>();\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Creates a reactive signal that can be read, written, and subscribed to.\r\n * @template T - The type of value stored in the signal\r\n * @param {T} initialValue - The initial value of the signal\r\n * @returns {Signal<T>} A signal object with read, set, update, peek, and subscribe methods\r\n * @example\r\n * const count = signal(0);\r\n * console.log(count()); // 0\r\n * count.set(5); // Update value\r\n */\r\n export function signal<T>(initialValue: T): Signal<T> {\r\n let value = initialValue;\r\n const subscribers = new Set<() => void>();\r\n\r\n function read(): T {\r\n // Track dependency if inside effect\r\n if (currentEffect) {\r\n subscribers.add(currentEffect);\r\n }\r\n return value;\r\n }\r\n\r\n function write(newValue: T): void {\r\n // Only update if value actually changed\r\n if (Object.is(value, newValue)) return;\r\n\r\n value = newValue;\r\n\r\n // Notify all subscribers\r\n if (batchDepth > 0) {\r\n // Batch mode: collect effects\r\n subscribers.forEach(fn => batchedEffects.add(fn));\r\n } else {\r\n // Immediate mode: run effects now\r\n subscribers.forEach(fn => fn());\r\n }\r\n }\r\n\r\n function update(fn: (prev: T) => T): void {\r\n write(fn(value));\r\n }\r\n\r\n function peek(): T {\r\n // Read without tracking\r\n return value;\r\n }\r\n\r\n function subscribe(fn: () => void): () => void {\r\n subscribers.add(fn);\r\n return () => subscribers.delete(fn);\r\n }\r\n\r\n // Create signal function with methods\r\n const sig = read as Signal<T>;\r\n sig.set = write;\r\n sig.update = update;\r\n sig.peek = peek;\r\n sig.subscribe = subscribe;\r\n\r\n return sig;\r\n }\r\n\r\n /**\r\n * Automatically runs a function when its signal dependencies change.\r\n * @param {() => EffectCleanup} fn - The effect function to run. Can optionally return a cleanup function.\r\n * @returns {() => void} A dispose function to stop the effect and clean up\r\n * @example\r\n * const count = signal(0);\r\n * effect(() => {\r\n * console.log('Count:', count());\r\n * return () => console.log('Cleaning up');\r\n * });\r\n */\r\n export function effect(fn: () => EffectCleanup): () => void {\r\n let cleanup: (() => void) | undefined;\r\n let isDisposed = false;\r\n\r\n const execute = () => {\r\n if (isDisposed) return;\r\n\r\n // Run cleanup from previous execution\r\n if (cleanup) {\r\n cleanup();\r\n cleanup = undefined;\r\n }\r\n\r\n // Set as current effect for dependency tracking\r\n const prevEffect = currentEffect;\r\n currentEffect = execute;\r\n\r\n try {\r\n // Run the effect function\r\n const result = fn();\r\n\r\n // Store cleanup if returned\r\n if (typeof result === 'function') {\r\n cleanup = result;\r\n }\r\n } finally {\r\n // Restore previous effect\r\n currentEffect = prevEffect;\r\n }\r\n };\r\n\r\n // Run immediately\r\n execute();\r\n\r\n // Create dispose function\r\n const disposer = () => {\r\n if (isDisposed) return;\r\n isDisposed = true;\r\n if (cleanup) cleanup();\r\n };\r\n\r\n // Register with current root if one exists\r\n if (currentRoot) {\r\n currentRoot.push(disposer);\r\n }\r\n\r\n // Return dispose function\r\n return disposer;\r\n }\r\n\r\n /**\r\n * Creates a computed signal that automatically updates when its dependencies change.\r\n * The computation result is cached and only recomputed when dependencies change.\r\n * @template T - The type of value computed\r\n * @param {() => T} fn - The computation function\r\n * @returns {Computed<T>} A read-only computed signal\r\n * @example\r\n * const count = signal(5);\r\n * const doubled = computed(() => count() * 2);\r\n * console.log(doubled()); // 10\r\n */\r\n export function computed<T>(fn: () => T): Computed<T> {\r\n const sig = signal<T>(undefined as T);\r\n\r\n // Create effect that updates the signal\r\n effect(() => {\r\n sig.set(fn());\r\n });\r\n\r\n // Mark as computed\r\n const computed = sig as Computed<T>;\r\n Object.defineProperty(computed, 'isComputed', {\r\n value: true,\r\n writable: false\r\n });\r\n\r\n return computed;\r\n }\r\n\r\n /**\r\n * Groups multiple signal updates together, deferring effect execution until all updates complete.\r\n * This improves performance by preventing cascading effect runs.\r\n * @template T - The return type of the function\r\n * @param {() => T} fn - A function that performs multiple signal updates\r\n * @returns {T} The return value of the function\r\n * @example\r\n * const a = signal(1);\r\n * const b = signal(2);\r\n * batch(() => {\r\n * a.set(10);\r\n * b.set(20);\r\n * }); // Effects only run once\r\n */\r\n export function batch<T>(fn: () => T): T {\r\n batchDepth++;\r\n\r\n try {\r\n return fn();\r\n } finally {\r\n batchDepth--;\r\n\r\n // If we're back at depth 0, flush batched effects\r\n if (batchDepth === 0) {\r\n // Keep batch mode active while flushing to prevent cascading effects\r\n batchDepth++;\r\n flushedEffects.clear();\r\n try {\r\n // Keep running effects until no more are queued\r\n while (batchedEffects.size > 0) {\r\n const effects = Array.from(batchedEffects);\r\n batchedEffects.clear();\r\n effects.forEach(fn => {\r\n // Only run if we haven't run it in this batch\r\n if (!flushedEffects.has(fn)) {\r\n flushedEffects.add(fn);\r\n fn();\r\n }\r\n });\r\n }\r\n } finally {\r\n batchDepth--;\r\n flushedEffects.clear();\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Reads signals without creating dependencies on them.\r\n * Useful for accessing signal values without triggering effect re-runs.\r\n * @template T - The return type of the function\r\n * @param {() => T} fn - A function that accesses signals\r\n * @returns {T} The return value of the function\r\n * @example\r\n * const count = signal(0);\r\n * effect(() => {\r\n * const value = untrack(() => count()); // Won't trigger re-run\r\n * console.log(value);\r\n * });\r\n */\r\n export function untrack<T>(fn: () => T): T {\r\n const prevEffect = currentEffect;\r\n currentEffect = null;\r\n\r\n try {\r\n return fn();\r\n } finally {\r\n currentEffect = prevEffect;\r\n }\r\n }\r\n\r\n /**\r\n * Runs an effect only when a specific signal changes, providing both new and previous values.\r\n * @template T - The type of the signal\r\n * @param {Signal<T>} sig - The signal to watch\r\n * @param {(value: T, prevValue: T) => EffectCleanup} fn - Effect function called with new and previous values\r\n * @returns {() => void} A dispose function to stop watching\r\n * @example\r\n * const count = signal(0);\r\n * on(count, (newVal, oldVal) => {\r\n * console.log(`Changed from ${oldVal} to ${newVal}`);\r\n * });\r\n */\r\n export function on<T>(\r\n sig: Signal<T>,\r\n fn: (value: T, prevValue: T) => EffectCleanup\r\n ): () => void {\r\n let prevValue = sig.peek();\r\n\r\n return effect(() => {\r\n // Read the signal to create dependency\r\n const value = sig();\r\n\r\n // Run callback without tracking new dependencies\r\n const cleanup = untrack(() => fn(value, prevValue));\r\n prevValue = value;\r\n return cleanup;\r\n });\r\n }\r\n\r\n /**\r\n * Creates a store object where each property is a signal.\r\n * Provides a convenient way to manage multiple related reactive values.\r\n * @template T - The type of the initial state object\r\n * @param {T} initialState - An object with initial values\r\n * @returns {{ [K in keyof T]: Signal<T[K]> }} An object with signals for each property\r\n * @example\r\n * const state = store({ count: 0, name: 'John' });\r\n * console.log(state.count()); // 0\r\n * state.name.set('Jane');\r\n */\r\n export function store<T extends Record<string, any>>(\r\n initialState: T\r\n ): { [K in keyof T]: Signal<T[K]> } {\r\n const store = {} as any;\r\n\r\n for (const key in initialState) {\r\n store[key] = signal(initialState[key]);\r\n }\r\n\r\n return store;\r\n }\r\n\r\n /**\r\n * Memoizes the result of an expensive computation, caching it indefinitely.\r\n * Unlike computed, this doesn't depend on reactive signals.\r\n * @template T - The type of the memoized value\r\n * @param {() => T} fn - A function that performs the computation\r\n * @returns {() => T} A function that returns the cached result\r\n * @example\r\n * const expensiveCalc = memo(() => {\r\n * return Array.from({ length: 1000 }).map(expensiveOp);\r\n * });\r\n * const result = expensiveCalc(); // Computed only once\r\n */\r\n export function memo<T>(fn: () => T): () => T {\r\n let cachedValue: T | undefined;\r\n let hasCachedValue = false;\r\n\r\n return () => {\r\n if (hasCachedValue) {\r\n return cachedValue as T;\r\n }\r\n\r\n // Compute the value\r\n const value = fn();\r\n\r\n // Cache it\r\n cachedValue = value;\r\n hasCachedValue = true;\r\n\r\n return value;\r\n };\r\n }\r\n\r\n /**\r\n * Creates a root scope for managing effect and computed signal lifecycles.\r\n * All effects and disposers created within the function are collected and can be cleaned up together.\r\n * @template T - The return type of the function\r\n * @param {(dispose: () => void) => T} fn - A function that receives a dispose function\r\n * @returns {T} The return value of the function\r\n * @example\r\n * const dispose = root((dispose) => {\r\n * effect(() => console.log('Running'));\r\n * return 42;\r\n * });\r\n * dispose(); // Cleans up all effects created in the root\r\n */\r\n export function root<T>(fn: (dispose: () => void) => T): T {\r\n const disposers: (() => void)[] = [];\r\n const prevRoot = currentRoot;\r\n currentRoot = disposers;\r\n\r\n try {\r\n const dispose = () => {\r\n disposers.forEach(d => d());\r\n disposers.length = 0;\r\n currentRoot = prevRoot;\r\n };\r\n\r\n return fn(dispose);\r\n } finally {\r\n currentRoot = prevRoot;\r\n }\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ HELP ════════════════════════════════════════╗\r\n\r\n /**\r\n * Development utilities for debugging signal reactivity\r\n */\r\n export const dev = {\r\n /**\r\n * Returns the currently executing effect, or null if no effect is running\r\n * @returns {(() => void) | null} The current effect function or null\r\n */\r\n getCurrentEffect(): (() => void) | null {\r\n return currentEffect;\r\n },\r\n\r\n /**\r\n * Returns the current batch depth (for debugging nested batch calls)\r\n * @returns {number} The current batch nesting level\r\n */\r\n getBatchDepth(): number {\r\n return batchDepth;\r\n },\r\n\r\n /**\r\n * Returns the count of effects currently pending in the batch queue\r\n * @returns {number} The number of batched effects waiting to run\r\n */\r\n getBatchedEffectsCount(): number {\r\n return batchedEffects.size;\r\n }\r\n };\r\n\r\n /**\r\n * Type guard to check if a value is a signal\r\n * @template T - The type of value the signal contains\r\n * @param {any} value - The value to check\r\n * @returns {boolean} True if the value is a signal\r\n * @example\r\n * if (isSignal(myValue)) {\r\n * console.log(myValue());\r\n * }\r\n */\r\n export function isSignal<T>(value: any): value is Signal<T> {\r\n return (\r\n typeof value === 'function' &&\r\n 'set' in value &&\r\n 'update' in value &&\r\n 'peek' in value\r\n );\r\n }\r\n\r\n /**\r\n * Type guard to check if a value is a computed signal\r\n * @template T - The type of value the computed signal contains\r\n * @param {any} value - The value to check\r\n * @returns {boolean} True if the value is a computed signal\r\n * @example\r\n * if (isComputed(myValue)) {\r\n * console.log('This is a computed signal');\r\n * }\r\n */\r\n export function isComputed<T>(value: any): value is Computed<T> {\r\n return isSignal(value) && 'isComputed' in value;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export default {\r\n signal,\r\n effect,\r\n computed,\r\n batch,\r\n untrack,\r\n on,\r\n store,\r\n memo,\r\n root,\r\n isSignal,\r\n isComputed,\r\n dev\r\n };\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@minejs/signals",
3
+ "version": "0.0.1",
4
+ "description": "A lightweight, zero-dependency signals library for reactive JavaScript applications",
5
+ "keywords": ["minejs", "signals"],
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/minejs-org/signals#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/minejs-org/signals/issues"
10
+ },
11
+ "author": {
12
+ "name": "Maysara",
13
+ "email": "maysara.elshewehy@gmail.com",
14
+ "url": "https://github.com/maysara-elshewehy"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/minejs-org/signals.git"
19
+ },
20
+ "type": "module",
21
+ "main": "./dist/main.js",
22
+ "types": "./dist/main.d.ts",
23
+ "files": ["dist"],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/main.d.ts",
27
+ "import": "./dist/main.js",
28
+ "require": "./dist/main.js"
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "lint": "eslint src --ext .ts",
34
+ "test": "bun test"
35
+ },
36
+ "engines": {
37
+ "bun": ">=1.3.3"
38
+ },
39
+ "peerDependencies": {
40
+ "bun": ">=1.3.3"
41
+ },
42
+ "devDependencies": {
43
+ "@eslint/js": "^9.34.0",
44
+ "@stylistic/eslint-plugin": "^5.3.1",
45
+ "@types/bun": "^1.1.13",
46
+ "@types/node": "^20.12.7",
47
+ "bun-plugin-dts": "^0.3.0",
48
+ "bun-types": "^1.1.38",
49
+ "ts-node": "^10.9.2",
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.4.5",
52
+ "typescript-eslint": "^8.42.0"
53
+ }
54
+ }