nabd 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # 🚀 Nabd Core
2
+
3
+ A lightweight, high-performance reactivity engine designed for Fintech applications. Built for precision, speed, and safety.
4
+
5
+ # ✨ Features
6
+
7
+ 1. Fine-Grained Reactivity: Updates only what changes—no more unnecessary re-renders.
8
+ 2. Atomic Transactions: Native action and batch support for consistent state.
9
+ 3. Bank-Grade Safety: Integrated withReversion for atomic optimistic UI rollbacks.
10
+ 4. React Ready: Seamless integration with useSignal and useSyncExternalStore.
11
+ 5. Type Safe: 100% TypeScript with first-class IDE support.
12
+
13
+ # 📦 Installation
14
+
15
+ ```Bash
16
+
17
+ npm install nabd
18
+ # or
19
+ yarn add nabd
20
+ ```
21
+
22
+ # Quick Start Guide
23
+
24
+ 1. Create your first "Store"
25
+
26
+ Instead of putting state inside components, create a dedicated file for your domain logic. This makes the state shareable and easy to test.
27
+
28
+ ```typescript
29
+ // stores/counterStore.ts
30
+ import { signal, computed, action, asReadonly } from "nabd";
31
+
32
+ // 1. Private state (cannot be modified outside this file)
33
+ const _count = signal(0);
34
+
35
+ // 2. Public Read-only view
36
+ export const count = asReadonly(_count);
37
+
38
+ // 3. Derived state (Automatic)
39
+ export const doubleCount = computed(() => _count.get() * 2);
40
+
41
+ // 4. Actions (Logic with automatic batching)
42
+ export const increment = action(() => {
43
+ _count.update((n) => n + 1);
44
+ });
45
+
46
+ export const reset = action(() => {
47
+ _count.set(0);
48
+ });
49
+ ```
50
+
51
+ 2. Connect to a React Component
52
+ Use the useSignal hook to "peek" into the store. React will handle the subscription and unsubscription automatically.
53
+
54
+ ```typescript
55
+ // components/Counter.tsx
56
+ "use client"; // Required for Next.js App Router
57
+
58
+ import { useSignal } from "nabd/react";
59
+ import { count, doubleCount, increment } from "../stores/counterStore";
60
+
61
+ export default function Counter() {
62
+ // Component re-renders ONLY when count changes
63
+ const c = useSignal(count);
64
+ const dc = useSignal(doubleCount);
65
+
66
+ return (
67
+ <div className="card">
68
+ <h1>Count: {c}</h1>
69
+ <h2>Double: {dc}</h2>
70
+ <button onClick={increment}>Add +1</button>
71
+ </div>
72
+ );
73
+ }
74
+ ```
75
+
76
+ 3. Handle Async Data (The Resource Pattern)
77
+ For fetching data, use the resource handler. It tracks loading states so you don't have to create three separate signals manually.
78
+
79
+ ```typescript
80
+ // stores/userStore.ts
81
+ import { resource, signal } from "nabd";
82
+
83
+ const userId = signal(1);
84
+
85
+ export const userResource = resource(async () => {
86
+ const response = await fetch(`https://api.example.com/user/${userId.get()}`);
87
+ return response.json();
88
+ });
89
+
90
+ export const nextUser = () => userId.update((id) => id + 1);
91
+ ```
92
+
93
+ # Fintech Patterns: Optimistic Updates
94
+
95
+ Nabd makes handling failed transactions easy with withReversion.
96
+
97
+ ```typeScript
98
+
99
+ import { withReversion, action } from 'nabd';
100
+
101
+ export const sendMoney = action(async (amount: number) => {
102
+ balance.update(n => n - amount); // Update UI immediately
103
+
104
+ try {
105
+ await withReversion([balance], async () => {
106
+ await api.post('/transfer', { amount }); // If this fails, balance rolls back!
107
+ });
108
+ } catch (e) {
109
+ showNotification("Transfer failed, balance restored.");
110
+ }
111
+ });
112
+ ```
113
+
114
+ # Pro-Tips for the Team
115
+
116
+ 🟢 DO: Use asReadonly
117
+ Always export the readonly version of your signals. This prevents components from doing count.set(999) directly, forcing all state changes to happen through defined Actions.
118
+
119
+ 🔴 DON'T: Use Signals for EVERYTHING
120
+ If a piece of state is only used inside one small component and never shared (like a "isDropdownOpen" toggle), standard useState is perfectly fine. Use Signals for shared state or high-frequency updates.
121
+
122
+ 🟡 WATCH OUT: Destructuring
123
+ Do not destructure signals in your component body.
124
+
125
+ ```typescript
126
+ ❌ const { get } = count; (Tracking might break)
127
+
128
+ ✅ const value = useSignal(count);
129
+ ```
130
+
131
+ 🛠️ Debugging with "Effects"
132
+ If you're wondering why a value isn't updating, add a temporary effect in your store file. It will log every change to the console:
133
+
134
+ ```typeScript
135
+
136
+ effect(() => {
137
+ console.log("[DEBUG] Count changed to:", count.get());
138
+ });
139
+ ```
140
+
141
+ # License
142
+
143
+ MIT © akinnez/Nabd
@@ -0,0 +1,69 @@
1
+ type Subscriber = {
2
+ notify: () => void;
3
+ dependencies: Set<any>;
4
+ };
5
+ declare class Signal<T> {
6
+ private value;
7
+ private subscribers;
8
+ constructor(value: T);
9
+ get(): T;
10
+ peek(): T;
11
+ set(newValue: T): void;
12
+ update(fn: (val: T) => T): void;
13
+ subscribe(cb: () => void): () => void;
14
+ _unsubscribe(sub: Subscriber): void;
15
+ }
16
+ declare class Computed<T> {
17
+ private getter;
18
+ private cachedValue;
19
+ private dirty;
20
+ private signal;
21
+ private subscriber;
22
+ constructor(getter: () => T);
23
+ get(): T;
24
+ peek(): T;
25
+ subscribe(cb: () => void): () => void;
26
+ }
27
+ declare class Effect {
28
+ private fn;
29
+ private subscriber;
30
+ private cleanup?;
31
+ constructor(fn: () => void | (() => void));
32
+ private run;
33
+ dispose(): void;
34
+ }
35
+ declare const signal: <T>(v: T) => Signal<T>;
36
+ declare const computed: <T>(fn: () => T) => Computed<T>;
37
+ declare const effect: (fn: () => void | (() => void)) => Effect;
38
+ declare const batch: (fn: () => void) => void;
39
+
40
+ /**
41
+ * Executes an async task. If it fails, reverts signals to their previous state.
42
+ */
43
+ declare function withReversion(signals: Signal<any>[], task: () => Promise<void>): Promise<void>;
44
+
45
+ declare class ReadOnlySignal<T> {
46
+ protected ref: Signal<T> | Computed<T>;
47
+ constructor(ref: Signal<T> | Computed<T>);
48
+ get(): T;
49
+ peek(): T;
50
+ subscribe(cb: () => void): () => void;
51
+ }
52
+ declare const asReadonly: <T>(s: Signal<T> | Computed<T>) => ReadOnlySignal<T>;
53
+ declare function action<T extends (...args: any[]) => any>(fn: T): T;
54
+ declare function untracked<T>(fn: () => T): T;
55
+
56
+ declare function resource<T>(fetcher: () => Promise<T>): {
57
+ data: Signal<T | undefined>;
58
+ loading: Signal<boolean>;
59
+ error: Signal<any>;
60
+ refetch: () => Promise<void>;
61
+ };
62
+
63
+ declare function debounceSignal<T>(source: Signal<T> | Computed<T>, delay: number): Signal<T>;
64
+
65
+ type AnySignal<T> = Signal<T> | Computed<T> | ReadOnlySignal<T>;
66
+ declare function useSignal<T>(source: AnySignal<T>): T;
67
+ declare function useSignalEffect(fn: () => void | (() => void)): void;
68
+
69
+ export { Computed, Effect, ReadOnlySignal, Signal, action, asReadonly, batch, computed, debounceSignal, effect, resource, signal, untracked, useSignal, useSignalEffect, withReversion };
@@ -0,0 +1,69 @@
1
+ type Subscriber = {
2
+ notify: () => void;
3
+ dependencies: Set<any>;
4
+ };
5
+ declare class Signal<T> {
6
+ private value;
7
+ private subscribers;
8
+ constructor(value: T);
9
+ get(): T;
10
+ peek(): T;
11
+ set(newValue: T): void;
12
+ update(fn: (val: T) => T): void;
13
+ subscribe(cb: () => void): () => void;
14
+ _unsubscribe(sub: Subscriber): void;
15
+ }
16
+ declare class Computed<T> {
17
+ private getter;
18
+ private cachedValue;
19
+ private dirty;
20
+ private signal;
21
+ private subscriber;
22
+ constructor(getter: () => T);
23
+ get(): T;
24
+ peek(): T;
25
+ subscribe(cb: () => void): () => void;
26
+ }
27
+ declare class Effect {
28
+ private fn;
29
+ private subscriber;
30
+ private cleanup?;
31
+ constructor(fn: () => void | (() => void));
32
+ private run;
33
+ dispose(): void;
34
+ }
35
+ declare const signal: <T>(v: T) => Signal<T>;
36
+ declare const computed: <T>(fn: () => T) => Computed<T>;
37
+ declare const effect: (fn: () => void | (() => void)) => Effect;
38
+ declare const batch: (fn: () => void) => void;
39
+
40
+ /**
41
+ * Executes an async task. If it fails, reverts signals to their previous state.
42
+ */
43
+ declare function withReversion(signals: Signal<any>[], task: () => Promise<void>): Promise<void>;
44
+
45
+ declare class ReadOnlySignal<T> {
46
+ protected ref: Signal<T> | Computed<T>;
47
+ constructor(ref: Signal<T> | Computed<T>);
48
+ get(): T;
49
+ peek(): T;
50
+ subscribe(cb: () => void): () => void;
51
+ }
52
+ declare const asReadonly: <T>(s: Signal<T> | Computed<T>) => ReadOnlySignal<T>;
53
+ declare function action<T extends (...args: any[]) => any>(fn: T): T;
54
+ declare function untracked<T>(fn: () => T): T;
55
+
56
+ declare function resource<T>(fetcher: () => Promise<T>): {
57
+ data: Signal<T | undefined>;
58
+ loading: Signal<boolean>;
59
+ error: Signal<any>;
60
+ refetch: () => Promise<void>;
61
+ };
62
+
63
+ declare function debounceSignal<T>(source: Signal<T> | Computed<T>, delay: number): Signal<T>;
64
+
65
+ type AnySignal<T> = Signal<T> | Computed<T> | ReadOnlySignal<T>;
66
+ declare function useSignal<T>(source: AnySignal<T>): T;
67
+ declare function useSignalEffect(fn: () => void | (() => void)): void;
68
+
69
+ export { Computed, Effect, ReadOnlySignal, Signal, action, asReadonly, batch, computed, debounceSignal, effect, resource, signal, untracked, useSignal, useSignalEffect, withReversion };
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Computed: () => Computed,
24
+ Effect: () => Effect,
25
+ ReadOnlySignal: () => ReadOnlySignal,
26
+ Signal: () => Signal,
27
+ action: () => action,
28
+ asReadonly: () => asReadonly,
29
+ batch: () => batch,
30
+ computed: () => computed,
31
+ debounceSignal: () => debounceSignal,
32
+ effect: () => effect,
33
+ resource: () => resource,
34
+ signal: () => signal,
35
+ untracked: () => untracked,
36
+ useSignal: () => useSignal,
37
+ useSignalEffect: () => useSignalEffect,
38
+ withReversion: () => withReversion
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/core/signals.ts
43
+ var activeConsumer = null;
44
+ var isBatching = false;
45
+ var pendingNotifications = /* @__PURE__ */ new Set();
46
+ var Signal = class {
47
+ constructor(value) {
48
+ this.value = value;
49
+ }
50
+ subscribers = /* @__PURE__ */ new Set();
51
+ get() {
52
+ if (activeConsumer) {
53
+ activeConsumer.dependencies.add(this);
54
+ this.subscribers.add(activeConsumer);
55
+ }
56
+ return this.value;
57
+ }
58
+ peek() {
59
+ return this.value;
60
+ }
61
+ set(newValue) {
62
+ if (Object.is(this.value, newValue)) return;
63
+ this.value = newValue;
64
+ if (isBatching) this.subscribers.forEach((sub) => pendingNotifications.add(sub));
65
+ else [...this.subscribers].forEach((sub) => sub.notify());
66
+ }
67
+ update(fn) {
68
+ this.set(fn(this.value));
69
+ }
70
+ subscribe(cb) {
71
+ const sub = { notify: cb, dependencies: /* @__PURE__ */ new Set([this]) };
72
+ this.subscribers.add(sub);
73
+ return () => this.subscribers.delete(sub);
74
+ }
75
+ _unsubscribe(sub) {
76
+ this.subscribers.delete(sub);
77
+ }
78
+ };
79
+ var Computed = class {
80
+ constructor(getter) {
81
+ this.getter = getter;
82
+ this.signal = new Signal(void 0);
83
+ this.subscriber = {
84
+ dependencies: /* @__PURE__ */ new Set(),
85
+ notify: () => {
86
+ this.dirty = true;
87
+ this.signal.set(this.get());
88
+ }
89
+ };
90
+ }
91
+ cachedValue;
92
+ dirty = true;
93
+ signal;
94
+ subscriber;
95
+ get() {
96
+ if (this.dirty) {
97
+ this.subscriber.dependencies.forEach((dep) => dep._unsubscribe(this.subscriber));
98
+ this.subscriber.dependencies.clear();
99
+ const prev = activeConsumer;
100
+ activeConsumer = this.subscriber;
101
+ try {
102
+ this.cachedValue = this.getter();
103
+ this.signal.value = this.cachedValue;
104
+ } finally {
105
+ activeConsumer = prev;
106
+ }
107
+ this.dirty = false;
108
+ }
109
+ return this.signal.get();
110
+ }
111
+ peek() {
112
+ return this.get();
113
+ }
114
+ subscribe(cb) {
115
+ return this.signal.subscribe(cb);
116
+ }
117
+ };
118
+ var Effect = class {
119
+ constructor(fn) {
120
+ this.fn = fn;
121
+ this.subscriber = { dependencies: /* @__PURE__ */ new Set(), notify: () => this.run() };
122
+ this.run();
123
+ }
124
+ subscriber;
125
+ cleanup;
126
+ run() {
127
+ this.dispose();
128
+ const prev = activeConsumer;
129
+ activeConsumer = this.subscriber;
130
+ try {
131
+ this.cleanup = this.fn() || void 0;
132
+ } finally {
133
+ activeConsumer = prev;
134
+ }
135
+ }
136
+ dispose() {
137
+ if (this.cleanup) this.cleanup();
138
+ this.subscriber.dependencies.forEach((s) => s._unsubscribe(this.subscriber));
139
+ this.subscriber.dependencies.clear();
140
+ }
141
+ };
142
+ var signal = (v) => new Signal(v);
143
+ var computed = (fn) => new Computed(fn);
144
+ var effect = (fn) => new Effect(fn);
145
+ var batch = (fn) => {
146
+ isBatching = true;
147
+ try {
148
+ fn();
149
+ } finally {
150
+ isBatching = false;
151
+ pendingNotifications.forEach((s) => s.notify());
152
+ pendingNotifications.clear();
153
+ }
154
+ };
155
+
156
+ // src/core/reversion.ts
157
+ async function withReversion(signals, task) {
158
+ const snapshots = signals.map((s) => s.peek());
159
+ try {
160
+ await task();
161
+ } catch (error) {
162
+ batch(() => {
163
+ signals.forEach((s, i) => s.set(snapshots[i]));
164
+ });
165
+ console.error("[Pulse] Operation failed. State reverted.", error);
166
+ throw error;
167
+ }
168
+ }
169
+
170
+ // src/core/action.ts
171
+ var ReadOnlySignal = class {
172
+ constructor(ref) {
173
+ this.ref = ref;
174
+ }
175
+ get() {
176
+ return this.ref.get();
177
+ }
178
+ peek() {
179
+ return this.ref.peek();
180
+ }
181
+ subscribe(cb) {
182
+ return this.ref.subscribe(cb);
183
+ }
184
+ };
185
+ var asReadonly = (s) => new ReadOnlySignal(s);
186
+ function action(fn) {
187
+ return ((...args) => {
188
+ let res;
189
+ batch(() => {
190
+ res = fn(...args);
191
+ });
192
+ return res;
193
+ });
194
+ }
195
+ function untracked(fn) {
196
+ const prev = globalThis.__activeConsumer;
197
+ globalThis.__activeConsumer = null;
198
+ const res = fn();
199
+ globalThis.__activeConsumer = prev;
200
+ return res;
201
+ }
202
+
203
+ // src/handlers/resources.ts
204
+ function resource(fetcher) {
205
+ const data = signal(void 0);
206
+ const loading = signal(true);
207
+ const error = signal(null);
208
+ const exec = async () => {
209
+ loading.set(true);
210
+ try {
211
+ data.set(await fetcher());
212
+ error.set(null);
213
+ } catch (e) {
214
+ error.set(e);
215
+ } finally {
216
+ loading.set(false);
217
+ }
218
+ };
219
+ exec();
220
+ return { data, loading, error, refetch: exec };
221
+ }
222
+
223
+ // src/handlers/debounce.ts
224
+ function debounceSignal(source, delay) {
225
+ const debounced = signal(source.peek());
226
+ let timeout;
227
+ effect(() => {
228
+ const val = source.get();
229
+ clearTimeout(timeout);
230
+ timeout = setTimeout(() => debounced.set(val), delay);
231
+ });
232
+ return debounced;
233
+ }
234
+
235
+ // src/react/hook.tsx
236
+ var import_react = require("react");
237
+ function useSignal(source) {
238
+ return (0, import_react.useSyncExternalStore)(
239
+ (cb) => source.subscribe(cb),
240
+ () => source.peek(),
241
+ () => source.peek()
242
+ );
243
+ }
244
+ function useSignalEffect(fn) {
245
+ (0, import_react.useEffect)(() => {
246
+ const eff = new Effect(fn);
247
+ return () => eff.dispose();
248
+ }, []);
249
+ }
250
+ // Annotate the CommonJS export names for ESM import in node:
251
+ 0 && (module.exports = {
252
+ Computed,
253
+ Effect,
254
+ ReadOnlySignal,
255
+ Signal,
256
+ action,
257
+ asReadonly,
258
+ batch,
259
+ computed,
260
+ debounceSignal,
261
+ effect,
262
+ resource,
263
+ signal,
264
+ untracked,
265
+ useSignal,
266
+ useSignalEffect,
267
+ withReversion
268
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,226 @@
1
+ // src/core/signals.ts
2
+ var activeConsumer = null;
3
+ var isBatching = false;
4
+ var pendingNotifications = /* @__PURE__ */ new Set();
5
+ var Signal = class {
6
+ constructor(value) {
7
+ this.value = value;
8
+ }
9
+ subscribers = /* @__PURE__ */ new Set();
10
+ get() {
11
+ if (activeConsumer) {
12
+ activeConsumer.dependencies.add(this);
13
+ this.subscribers.add(activeConsumer);
14
+ }
15
+ return this.value;
16
+ }
17
+ peek() {
18
+ return this.value;
19
+ }
20
+ set(newValue) {
21
+ if (Object.is(this.value, newValue)) return;
22
+ this.value = newValue;
23
+ if (isBatching) this.subscribers.forEach((sub) => pendingNotifications.add(sub));
24
+ else [...this.subscribers].forEach((sub) => sub.notify());
25
+ }
26
+ update(fn) {
27
+ this.set(fn(this.value));
28
+ }
29
+ subscribe(cb) {
30
+ const sub = { notify: cb, dependencies: /* @__PURE__ */ new Set([this]) };
31
+ this.subscribers.add(sub);
32
+ return () => this.subscribers.delete(sub);
33
+ }
34
+ _unsubscribe(sub) {
35
+ this.subscribers.delete(sub);
36
+ }
37
+ };
38
+ var Computed = class {
39
+ constructor(getter) {
40
+ this.getter = getter;
41
+ this.signal = new Signal(void 0);
42
+ this.subscriber = {
43
+ dependencies: /* @__PURE__ */ new Set(),
44
+ notify: () => {
45
+ this.dirty = true;
46
+ this.signal.set(this.get());
47
+ }
48
+ };
49
+ }
50
+ cachedValue;
51
+ dirty = true;
52
+ signal;
53
+ subscriber;
54
+ get() {
55
+ if (this.dirty) {
56
+ this.subscriber.dependencies.forEach((dep) => dep._unsubscribe(this.subscriber));
57
+ this.subscriber.dependencies.clear();
58
+ const prev = activeConsumer;
59
+ activeConsumer = this.subscriber;
60
+ try {
61
+ this.cachedValue = this.getter();
62
+ this.signal.value = this.cachedValue;
63
+ } finally {
64
+ activeConsumer = prev;
65
+ }
66
+ this.dirty = false;
67
+ }
68
+ return this.signal.get();
69
+ }
70
+ peek() {
71
+ return this.get();
72
+ }
73
+ subscribe(cb) {
74
+ return this.signal.subscribe(cb);
75
+ }
76
+ };
77
+ var Effect = class {
78
+ constructor(fn) {
79
+ this.fn = fn;
80
+ this.subscriber = { dependencies: /* @__PURE__ */ new Set(), notify: () => this.run() };
81
+ this.run();
82
+ }
83
+ subscriber;
84
+ cleanup;
85
+ run() {
86
+ this.dispose();
87
+ const prev = activeConsumer;
88
+ activeConsumer = this.subscriber;
89
+ try {
90
+ this.cleanup = this.fn() || void 0;
91
+ } finally {
92
+ activeConsumer = prev;
93
+ }
94
+ }
95
+ dispose() {
96
+ if (this.cleanup) this.cleanup();
97
+ this.subscriber.dependencies.forEach((s) => s._unsubscribe(this.subscriber));
98
+ this.subscriber.dependencies.clear();
99
+ }
100
+ };
101
+ var signal = (v) => new Signal(v);
102
+ var computed = (fn) => new Computed(fn);
103
+ var effect = (fn) => new Effect(fn);
104
+ var batch = (fn) => {
105
+ isBatching = true;
106
+ try {
107
+ fn();
108
+ } finally {
109
+ isBatching = false;
110
+ pendingNotifications.forEach((s) => s.notify());
111
+ pendingNotifications.clear();
112
+ }
113
+ };
114
+
115
+ // src/core/reversion.ts
116
+ async function withReversion(signals, task) {
117
+ const snapshots = signals.map((s) => s.peek());
118
+ try {
119
+ await task();
120
+ } catch (error) {
121
+ batch(() => {
122
+ signals.forEach((s, i) => s.set(snapshots[i]));
123
+ });
124
+ console.error("[Pulse] Operation failed. State reverted.", error);
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ // src/core/action.ts
130
+ var ReadOnlySignal = class {
131
+ constructor(ref) {
132
+ this.ref = ref;
133
+ }
134
+ get() {
135
+ return this.ref.get();
136
+ }
137
+ peek() {
138
+ return this.ref.peek();
139
+ }
140
+ subscribe(cb) {
141
+ return this.ref.subscribe(cb);
142
+ }
143
+ };
144
+ var asReadonly = (s) => new ReadOnlySignal(s);
145
+ function action(fn) {
146
+ return ((...args) => {
147
+ let res;
148
+ batch(() => {
149
+ res = fn(...args);
150
+ });
151
+ return res;
152
+ });
153
+ }
154
+ function untracked(fn) {
155
+ const prev = globalThis.__activeConsumer;
156
+ globalThis.__activeConsumer = null;
157
+ const res = fn();
158
+ globalThis.__activeConsumer = prev;
159
+ return res;
160
+ }
161
+
162
+ // src/handlers/resources.ts
163
+ function resource(fetcher) {
164
+ const data = signal(void 0);
165
+ const loading = signal(true);
166
+ const error = signal(null);
167
+ const exec = async () => {
168
+ loading.set(true);
169
+ try {
170
+ data.set(await fetcher());
171
+ error.set(null);
172
+ } catch (e) {
173
+ error.set(e);
174
+ } finally {
175
+ loading.set(false);
176
+ }
177
+ };
178
+ exec();
179
+ return { data, loading, error, refetch: exec };
180
+ }
181
+
182
+ // src/handlers/debounce.ts
183
+ function debounceSignal(source, delay) {
184
+ const debounced = signal(source.peek());
185
+ let timeout;
186
+ effect(() => {
187
+ const val = source.get();
188
+ clearTimeout(timeout);
189
+ timeout = setTimeout(() => debounced.set(val), delay);
190
+ });
191
+ return debounced;
192
+ }
193
+
194
+ // src/react/hook.tsx
195
+ import { useSyncExternalStore, useEffect } from "react";
196
+ function useSignal(source) {
197
+ return useSyncExternalStore(
198
+ (cb) => source.subscribe(cb),
199
+ () => source.peek(),
200
+ () => source.peek()
201
+ );
202
+ }
203
+ function useSignalEffect(fn) {
204
+ useEffect(() => {
205
+ const eff = new Effect(fn);
206
+ return () => eff.dispose();
207
+ }, []);
208
+ }
209
+ export {
210
+ Computed,
211
+ Effect,
212
+ ReadOnlySignal,
213
+ Signal,
214
+ action,
215
+ asReadonly,
216
+ batch,
217
+ computed,
218
+ debounceSignal,
219
+ effect,
220
+ resource,
221
+ signal,
222
+ untracked,
223
+ useSignal,
224
+ useSignalEffect,
225
+ withReversion
226
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "nabd",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight, high-performance reactivity engine designed mostly for Fintech applications. Built for precision, speed, and safety.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
13
+ "test": "vitest run",
14
+ "bench": "tsx bench/signals.bench.ts"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "package.json"
20
+ ],
21
+ "peerDependencies": {
22
+ "react": ">=18"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^25.0.9",
26
+ "@types/react": "^19.2.8",
27
+ "mitata": "^0.1.14",
28
+ "tsup": "^8.5.1",
29
+ "tsx": "^4.21.0",
30
+ "typescript": "^5.9.3",
31
+ "vitest": "^1.6.1"
32
+ },
33
+
34
+ "keywords": [
35
+ "react",
36
+ "reactivity",
37
+ "rx",
38
+ "web",
39
+ "signals",
40
+ "nabd",
41
+ "reversion",
42
+ "debounce",
43
+ "debounceSignal",
44
+ "resources",
45
+ "useSignal",
46
+ "useSignalEffect"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/akinnez/nabd.git"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/akinnez/nabd/issues"
54
+ },
55
+ "homepage": "https://github.com/akinnez/nabd#readme",
56
+
57
+ "author": "Oladejo Akinola Idris",
58
+ "contributors": [
59
+ "Oladejo Akinola Idris (https://github.com/akinnez)"
60
+ ]
61
+ }
62
+