lume-js 2.2.1 → 2.3.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/AGENT_GUIDE.md +224 -0
- package/README.md +97 -16
- package/dist/addons.min.mjs +1 -1
- package/dist/addons.mjs +190 -7
- package/dist/addons.mjs.map +1 -1
- package/dist/handlers.min.mjs +1 -1
- package/dist/handlers.mjs +29 -2
- package/dist/handlers.mjs.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.mjs +4 -141
- package/dist/index.mjs.map +1 -1
- package/dist/lume.global.js +1 -1
- package/dist/lume.global.js.map +1 -1
- package/dist/shared-BGg9PbiG.mjs +249 -0
- package/dist/shared-BGg9PbiG.mjs.map +1 -0
- package/dist/shared-DmpHYKx7.mjs +15 -0
- package/dist/shared-DmpHYKx7.mjs.map +1 -0
- package/dist/shared-SUXdsYBx.mjs +233 -0
- package/dist/shared-SUXdsYBx.mjs.map +1 -0
- package/dist/state.min.mjs +1 -0
- package/dist/state.mjs +7 -0
- package/dist/state.mjs.map +1 -0
- package/llms-full.txt +6999 -0
- package/llms.txt +89 -0
- package/package.json +11 -2
- package/src/addons/index.d.ts +99 -7
- package/src/addons/index.js +13 -2
- package/src/addons/persist.js +190 -0
- package/src/addons/repeat.js +159 -10
- package/src/core/batch.js +139 -0
- package/src/core/bindDom.js +7 -3
- package/src/core/effect.js +34 -5
- package/src/core/state.js +118 -73
- package/src/handlers/index.d.ts +24 -0
- package/src/handlers/index.js +1 -0
- package/src/handlers/on.js +60 -0
- package/src/handlers/stringAttr.js +9 -2
- package/src/index.d.ts +14 -200
- package/src/index.js +3 -1
- package/src/state.d.ts +252 -0
- package/src/state.js +25 -0
- package/dist/shared-x2HJmEyO.mjs +0 -260
- package/dist/shared-x2HJmEyO.mjs.map +0 -1
package/src/state.d.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lume.js Universal State Entry — TypeScript Definitions
|
|
3
|
+
*
|
|
4
|
+
* Source of truth for the DOM-free kernel: state(), batch(),
|
|
5
|
+
* withReadObserver() and their types. This file must type-check WITHOUT
|
|
6
|
+
* lib.dom — it is what `lume-js/state` consumers in Node, Deno, and
|
|
7
|
+
* workers resolve (the full `lume-js` entry re-exports everything here
|
|
8
|
+
* and adds the DOM-flavored API on top).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Unsubscribe function returned by $subscribe
|
|
13
|
+
*/
|
|
14
|
+
export type Unsubscribe = () => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Subscriber callback function
|
|
18
|
+
*/
|
|
19
|
+
export type Subscriber<T> = (value: T) => void;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Internal unique symbol for reactive state branding
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
declare const lumeReactiveSymbol: unique symbol;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Plugin interface for extending state behavior
|
|
29
|
+
*
|
|
30
|
+
* All hooks are optional. Hooks execute in the order plugins are registered.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const debugPlugin: Plugin = {
|
|
35
|
+
* name: 'debug',
|
|
36
|
+
* onGet: (key, value) => {
|
|
37
|
+
* console.log(`GET ${key}:`, value);
|
|
38
|
+
* return value; // Return value to pass to next plugin
|
|
39
|
+
* },
|
|
40
|
+
* onSet: (key, newValue, oldValue) => {
|
|
41
|
+
* console.log(`SET ${key}:`, oldValue, '→', newValue);
|
|
42
|
+
* return newValue; // Return value to pass to next plugin
|
|
43
|
+
* }
|
|
44
|
+
* };
|
|
45
|
+
*
|
|
46
|
+
* const store = withPlugins(state({ count: 0 }), [debugPlugin]);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export interface Plugin {
|
|
50
|
+
/**
|
|
51
|
+
* Plugin name (for debugging)
|
|
52
|
+
*/
|
|
53
|
+
name: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Called when state object is created
|
|
57
|
+
* Runs synchronously before Proxy is returned
|
|
58
|
+
*/
|
|
59
|
+
onInit?(): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Called when a property is accessed (before value returned)
|
|
63
|
+
* Can transform the value by returning a new value
|
|
64
|
+
*
|
|
65
|
+
* Chain pattern: Each plugin receives the output of the previous plugin
|
|
66
|
+
*
|
|
67
|
+
* @param key - Property key being accessed
|
|
68
|
+
* @param value - Current value (possibly transformed by previous plugins)
|
|
69
|
+
* @returns Transformed value, or undefined to keep current value
|
|
70
|
+
*/
|
|
71
|
+
onGet?(key: string, value: any): any;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Called when a property is updated (before subscribers notified)
|
|
75
|
+
* Can transform or validate the new value
|
|
76
|
+
*
|
|
77
|
+
* Chain pattern: Each plugin receives the output of the previous plugin
|
|
78
|
+
*
|
|
79
|
+
* @param key - Property key being updated
|
|
80
|
+
* @param newValue - New value being set (possibly transformed by previous plugins)
|
|
81
|
+
* @param oldValue - Previous value
|
|
82
|
+
* @returns Transformed value, or undefined to keep current value
|
|
83
|
+
*/
|
|
84
|
+
onSet?(key: string, newValue: any, oldValue: any): any;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Called when a subscriber is added
|
|
88
|
+
* Useful for tracking active subscriptions
|
|
89
|
+
*
|
|
90
|
+
* @param key - Property key being subscribed to
|
|
91
|
+
*/
|
|
92
|
+
onSubscribe?(key: string): void;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Called when subscribers are about to be notified
|
|
96
|
+
* Runs in microtask, before subscribers receive value
|
|
97
|
+
*
|
|
98
|
+
* @param key - Property key that changed
|
|
99
|
+
* @param value - New value being notified
|
|
100
|
+
*/
|
|
101
|
+
onNotify?(key: string, value: any): void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Type-safe plugin interface for when you know the state shape.
|
|
106
|
+
* Provides better intellisense for key names and value types.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* interface AppState {
|
|
111
|
+
* count: number;
|
|
112
|
+
* name: string;
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* const myPlugin: TypedPlugin<AppState> = {
|
|
116
|
+
* name: 'typed',
|
|
117
|
+
* onSet: (key, newValue, oldValue) => {
|
|
118
|
+
* // key is 'count' | 'name', values are properly typed
|
|
119
|
+
* return newValue;
|
|
120
|
+
* }
|
|
121
|
+
* };
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export interface TypedPlugin<T extends object> {
|
|
125
|
+
name: string;
|
|
126
|
+
onInit?(): void;
|
|
127
|
+
onGet?<K extends keyof T>(key: K, value: T[K]): T[K] | undefined;
|
|
128
|
+
onSet?<K extends keyof T>(key: K, newValue: T[K], oldValue: T[K]): T[K] | undefined;
|
|
129
|
+
onSubscribe?<K extends keyof T>(key: K): void;
|
|
130
|
+
onNotify?<K extends keyof T>(key: K, value: T[K]): void;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Reactive state object with $subscribe method
|
|
135
|
+
*/
|
|
136
|
+
export type ReactiveState<T extends object> = T & {
|
|
137
|
+
/**
|
|
138
|
+
* Subscribe to changes on a specific property key
|
|
139
|
+
* @param key - Property key to watch
|
|
140
|
+
* @param callback - Function called when property changes
|
|
141
|
+
* @returns Unsubscribe function for cleanup
|
|
142
|
+
*/
|
|
143
|
+
$subscribe<K extends keyof T>(
|
|
144
|
+
key: K,
|
|
145
|
+
callback: Subscriber<T[K]>
|
|
146
|
+
): Unsubscribe;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Register a callback to run before each flush.
|
|
150
|
+
* Dedupes duplicate function references.
|
|
151
|
+
* @param fn - Callback function
|
|
152
|
+
* @returns Unsubscribe function for cleanup
|
|
153
|
+
*/
|
|
154
|
+
$beforeFlush(fn: () => void): Unsubscribe;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Brand to identify reactive state objects at the type level
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
readonly [lumeReactiveSymbol]?: true;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create a reactive state object
|
|
165
|
+
*
|
|
166
|
+
* @param obj - Plain object to make reactive
|
|
167
|
+
* @returns Reactive proxy with $subscribe method
|
|
168
|
+
* @throws {Error} If obj is not a plain object
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* const store = state({
|
|
173
|
+
* count: 0,
|
|
174
|
+
* user: state({
|
|
175
|
+
* name: 'Alice'
|
|
176
|
+
* })
|
|
177
|
+
* });
|
|
178
|
+
*
|
|
179
|
+
* store.count++; // Triggers reactivity
|
|
180
|
+
*
|
|
181
|
+
* const unsub = store.$subscribe('count', (val) => {
|
|
182
|
+
* console.log('Count:', val);
|
|
183
|
+
* });
|
|
184
|
+
*
|
|
185
|
+
* // Cleanup
|
|
186
|
+
* unsub();
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
*/
|
|
190
|
+
export function state<T extends object>(obj: T): ReactiveState<T>;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Group multiple state writes and flush them together, synchronously,
|
|
194
|
+
* when the outermost batch() returns.
|
|
195
|
+
*
|
|
196
|
+
* Guarantees:
|
|
197
|
+
* - Subscribers see only the final value of each key written in the batch.
|
|
198
|
+
* - An effect depending on several stores mutated in the batch runs exactly
|
|
199
|
+
* ONCE (normal microtask batching is per-state: such effects run once per
|
|
200
|
+
* mutated store).
|
|
201
|
+
* - Nested batch() calls are absorbed into the outermost batch.
|
|
202
|
+
* - If fn throws, writes made before the throw still flush, then the error
|
|
203
|
+
* propagates.
|
|
204
|
+
*
|
|
205
|
+
* fn must be synchronous. Writes after an `await` fall back to normal
|
|
206
|
+
* per-state microtask flushing (a console warning is logged if fn returns
|
|
207
|
+
* a Promise).
|
|
208
|
+
*
|
|
209
|
+
* @param fn - Function performing state writes
|
|
210
|
+
* @returns The return value of fn
|
|
211
|
+
* @throws {Error} If fn is not a function
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* import { state, effect, batch } from 'lume-js';
|
|
216
|
+
*
|
|
217
|
+
* const a = state({ value: 1 });
|
|
218
|
+
* const b = state({ value: 2 });
|
|
219
|
+
*
|
|
220
|
+
* effect(() => {
|
|
221
|
+
* render(a.value + b.value);
|
|
222
|
+
* });
|
|
223
|
+
*
|
|
224
|
+
* batch(() => {
|
|
225
|
+
* a.value = 10;
|
|
226
|
+
* b.value = 20;
|
|
227
|
+
* }); // render() ran exactly once, seeing 30
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
export function batch<T>(fn: () => T): T;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Run a function with a read observer active.
|
|
234
|
+
*
|
|
235
|
+
* The observer receives `(proxy, key, registerEffect)` for every property read
|
|
236
|
+
* during the synchronous execution of `fn`. Used internally by `effect()` for
|
|
237
|
+
* auto-tracking, and exposed for building custom reactive primitives.
|
|
238
|
+
*
|
|
239
|
+
* @param onRead - Called on each property access inside fn
|
|
240
|
+
* @param fn - The function to run under observation
|
|
241
|
+
* @returns The return value of fn
|
|
242
|
+
*
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
245
|
+
export function withReadObserver<T>(
|
|
246
|
+
onRead: (
|
|
247
|
+
proxy: ReactiveState<any>,
|
|
248
|
+
key: string,
|
|
249
|
+
registerEffect: (key: string, executeFn: () => void) => () => void
|
|
250
|
+
) => void,
|
|
251
|
+
fn: () => T
|
|
252
|
+
): T;
|
package/src/state.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lume-JS Universal State Entry
|
|
3
|
+
*
|
|
4
|
+
* The DOM-free kernel: reactive state + cross-store batching. Runs anywhere
|
|
5
|
+
* JavaScript runs — Node, Deno, Bun, workers, CLI tools, browsers.
|
|
6
|
+
*
|
|
7
|
+
* Use this entry when you don't need DOM binding:
|
|
8
|
+
*
|
|
9
|
+
* import { state, batch } from "lume-js/state";
|
|
10
|
+
*
|
|
11
|
+
* const store = state({ count: 0 });
|
|
12
|
+
* store.$subscribe("count", v => console.log("count:", v));
|
|
13
|
+
*
|
|
14
|
+
* batch(() => {
|
|
15
|
+
* store.count = 1;
|
|
16
|
+
* store.count = 2;
|
|
17
|
+
* }); // subscribers see only 2, synchronously
|
|
18
|
+
*
|
|
19
|
+
* For DOM binding and effects, use the full core instead:
|
|
20
|
+
*
|
|
21
|
+
* import { state, bindDom, effect, batch } from "lume-js";
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export { state, withReadObserver } from "./core/state.js";
|
|
25
|
+
export { batch } from "./core/batch.js";
|
package/dist/shared-x2HJmEyO.mjs
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
function logWarn(msg, ...rest) {
|
|
2
|
-
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
3
|
-
console.warn(msg, ...rest);
|
|
4
|
-
}
|
|
5
|
-
}
|
|
6
|
-
function logError(msg, ...rest) {
|
|
7
|
-
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
8
|
-
console.error(msg, ...rest);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
const readers = /* @__PURE__ */ new Set();
|
|
12
|
-
function withReadObserver(onRead, fn) {
|
|
13
|
-
readers.add(onRead);
|
|
14
|
-
try {
|
|
15
|
-
return fn();
|
|
16
|
-
} finally {
|
|
17
|
-
readers.delete(onRead);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function state(obj) {
|
|
21
|
-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
22
|
-
throw new Error("state() requires a plain object");
|
|
23
|
-
}
|
|
24
|
-
if (Object.isFrozen(obj) || Object.isSealed(obj)) {
|
|
25
|
-
throw new Error("state() requires a mutable plain object");
|
|
26
|
-
}
|
|
27
|
-
const listeners = /* @__PURE__ */ Object.create(null);
|
|
28
|
-
const pendingNotifications = /* @__PURE__ */ new Map();
|
|
29
|
-
const pendingEffects = /* @__PURE__ */ new Set();
|
|
30
|
-
const beforeFlushHooks = [];
|
|
31
|
-
let flushScheduled = false;
|
|
32
|
-
function scheduleFlush() {
|
|
33
|
-
if (flushScheduled) return;
|
|
34
|
-
flushScheduled = true;
|
|
35
|
-
queueMicrotask(() => {
|
|
36
|
-
let iterations = 0;
|
|
37
|
-
const MAX_ITERATIONS = 100;
|
|
38
|
-
try {
|
|
39
|
-
while ((pendingNotifications.size > 0 || pendingEffects.size > 0) && iterations < MAX_ITERATIONS) {
|
|
40
|
-
iterations++;
|
|
41
|
-
for (let i = 0; i < beforeFlushHooks.length; i++) {
|
|
42
|
-
try {
|
|
43
|
-
beforeFlushHooks[i]();
|
|
44
|
-
} catch (err) {
|
|
45
|
-
logError("[Lume.js state] Error in beforeFlush hook:", err);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
for (const [key, value] of pendingNotifications) {
|
|
49
|
-
if (listeners[key]) {
|
|
50
|
-
const subs = listeners[key];
|
|
51
|
-
let i = 0;
|
|
52
|
-
while (i < subs.length) {
|
|
53
|
-
const fn = subs[i];
|
|
54
|
-
try {
|
|
55
|
-
fn(value);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
logError(`[Lume.js state] Error notifying subscriber for key "${String(key)}":`, err);
|
|
58
|
-
}
|
|
59
|
-
if (subs[i] === fn) i++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
pendingNotifications.clear();
|
|
64
|
-
const effects = new Array(pendingEffects.size);
|
|
65
|
-
let idx = 0;
|
|
66
|
-
for (const effect2 of pendingEffects) {
|
|
67
|
-
effects[idx++] = effect2;
|
|
68
|
-
}
|
|
69
|
-
pendingEffects.clear();
|
|
70
|
-
for (let i = 0; i < effects.length; i++) {
|
|
71
|
-
try {
|
|
72
|
-
effects[i]();
|
|
73
|
-
} catch (err) {
|
|
74
|
-
logError("[Lume.js state] Error in effect:", err);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} finally {
|
|
79
|
-
flushScheduled = false;
|
|
80
|
-
}
|
|
81
|
-
if (iterations >= MAX_ITERATIONS) {
|
|
82
|
-
logError(
|
|
83
|
-
"[Lume.js state] Maximum flush iterations reached (100). This usually indicates an infinite loop caused by an effect or computed mutating state it depends on."
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const REACTIVE_BRAND = Symbol("lume.reactive");
|
|
89
|
-
obj[REACTIVE_BRAND] = true;
|
|
90
|
-
const registerEffect = (key, executeFn) => {
|
|
91
|
-
if (!listeners[key]) listeners[key] = [];
|
|
92
|
-
const callback = () => {
|
|
93
|
-
pendingEffects.add(executeFn);
|
|
94
|
-
};
|
|
95
|
-
listeners[key].push(callback);
|
|
96
|
-
return () => {
|
|
97
|
-
if (listeners[key]) {
|
|
98
|
-
const idx = listeners[key].indexOf(callback);
|
|
99
|
-
if (idx !== -1) {
|
|
100
|
-
listeners[key].splice(idx, 1);
|
|
101
|
-
if (listeners[key].length === 0) delete listeners[key];
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
const BLOCKED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
107
|
-
const proxy = new Proxy(obj, {
|
|
108
|
-
get(target, key) {
|
|
109
|
-
if (typeof key === "string" && key.startsWith("$")) {
|
|
110
|
-
return target[key];
|
|
111
|
-
}
|
|
112
|
-
const value = target[key];
|
|
113
|
-
if (readers.size > 0) {
|
|
114
|
-
for (const reader of readers) {
|
|
115
|
-
reader(proxy, key, registerEffect);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return value;
|
|
119
|
-
},
|
|
120
|
-
set(target, key, value) {
|
|
121
|
-
if (typeof key === "string" && BLOCKED_KEYS.has(key)) {
|
|
122
|
-
logWarn(`[Lume.js state] Blocked write to reserved key "${key}"`);
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
const oldValue = target[key];
|
|
126
|
-
if (Object.is(oldValue, value)) return true;
|
|
127
|
-
target[key] = value;
|
|
128
|
-
pendingNotifications.set(key, value);
|
|
129
|
-
scheduleFlush();
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
obj.$beforeFlush = (fn) => {
|
|
134
|
-
if (typeof fn !== "function") {
|
|
135
|
-
throw new Error("$beforeFlush requires a function");
|
|
136
|
-
}
|
|
137
|
-
if (beforeFlushHooks.indexOf(fn) === -1) {
|
|
138
|
-
beforeFlushHooks.push(fn);
|
|
139
|
-
}
|
|
140
|
-
return () => {
|
|
141
|
-
const idx = beforeFlushHooks.indexOf(fn);
|
|
142
|
-
if (idx !== -1) {
|
|
143
|
-
beforeFlushHooks.splice(idx, 1);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
const MAX_SUBSCRIBERS = 1e3;
|
|
148
|
-
obj.$subscribe = (key, fn) => {
|
|
149
|
-
if (typeof fn !== "function") {
|
|
150
|
-
throw new Error("Subscriber must be a function");
|
|
151
|
-
}
|
|
152
|
-
if (!listeners[key]) listeners[key] = [];
|
|
153
|
-
if (listeners[key].length >= MAX_SUBSCRIBERS) {
|
|
154
|
-
logWarn(`[Lume.js state] Subscriber limit (${MAX_SUBSCRIBERS}) reached for key "${key}". New subscriber ignored.`);
|
|
155
|
-
return () => {
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
listeners[key].push(fn);
|
|
159
|
-
fn(proxy[key]);
|
|
160
|
-
return () => {
|
|
161
|
-
if (listeners[key]) {
|
|
162
|
-
const idx = listeners[key].indexOf(fn);
|
|
163
|
-
if (idx !== -1) {
|
|
164
|
-
listeners[key].splice(idx, 1);
|
|
165
|
-
if (listeners[key].length === 0) delete listeners[key];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
};
|
|
170
|
-
return proxy;
|
|
171
|
-
}
|
|
172
|
-
let currentEffect = null;
|
|
173
|
-
function effect(fn, deps) {
|
|
174
|
-
if (typeof fn !== "function") {
|
|
175
|
-
throw new Error("effect() requires a function");
|
|
176
|
-
}
|
|
177
|
-
const cleanups = [];
|
|
178
|
-
let isRunning = false;
|
|
179
|
-
const execute = () => {
|
|
180
|
-
if (isRunning) return;
|
|
181
|
-
isRunning = true;
|
|
182
|
-
try {
|
|
183
|
-
fn();
|
|
184
|
-
} catch (error) {
|
|
185
|
-
logError("[Lume.js effect] Error in effect:", error);
|
|
186
|
-
throw error;
|
|
187
|
-
} finally {
|
|
188
|
-
isRunning = false;
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
if (Array.isArray(deps)) {
|
|
192
|
-
for (const dep of deps) {
|
|
193
|
-
if (Array.isArray(dep) && dep.length >= 2) {
|
|
194
|
-
const [store, ...keys] = dep;
|
|
195
|
-
if (store && typeof store.$subscribe === "function") {
|
|
196
|
-
for (const key of keys) {
|
|
197
|
-
let isFirst = true;
|
|
198
|
-
const unsub = store.$subscribe(key, () => {
|
|
199
|
-
if (isFirst) {
|
|
200
|
-
isFirst = false;
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
execute();
|
|
204
|
-
});
|
|
205
|
-
cleanups.push(unsub);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
execute();
|
|
211
|
-
} else {
|
|
212
|
-
const executeWithTracking = () => {
|
|
213
|
-
if (isRunning) return;
|
|
214
|
-
const oldCleanups = cleanups.splice(0);
|
|
215
|
-
const myContext = {
|
|
216
|
-
fn,
|
|
217
|
-
cleanups,
|
|
218
|
-
execute: executeWithTracking,
|
|
219
|
-
tracking: {}
|
|
220
|
-
};
|
|
221
|
-
const previousEffect = currentEffect;
|
|
222
|
-
currentEffect = myContext;
|
|
223
|
-
isRunning = true;
|
|
224
|
-
try {
|
|
225
|
-
const onRead = (proxy, key, registerEffect) => {
|
|
226
|
-
if (currentEffect !== myContext) return;
|
|
227
|
-
if (myContext.tracking[key]) return;
|
|
228
|
-
myContext.tracking[key] = true;
|
|
229
|
-
myContext.cleanups.push(registerEffect(key, myContext.execute));
|
|
230
|
-
};
|
|
231
|
-
withReadObserver(onRead, fn);
|
|
232
|
-
} catch (error) {
|
|
233
|
-
cleanups.length = 0;
|
|
234
|
-
cleanups.push(...oldCleanups);
|
|
235
|
-
logError("[Lume.js effect] Error in effect:", error);
|
|
236
|
-
throw error;
|
|
237
|
-
} finally {
|
|
238
|
-
currentEffect = previousEffect;
|
|
239
|
-
isRunning = false;
|
|
240
|
-
}
|
|
241
|
-
if (cleanups.length > 0) {
|
|
242
|
-
for (const cleanup of oldCleanups) cleanup();
|
|
243
|
-
} else {
|
|
244
|
-
cleanups.push(...oldCleanups);
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
executeWithTracking();
|
|
248
|
-
}
|
|
249
|
-
return () => {
|
|
250
|
-
while (cleanups.length) cleanups.pop()();
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
export {
|
|
254
|
-
logError as a,
|
|
255
|
-
effect as e,
|
|
256
|
-
logWarn as l,
|
|
257
|
-
state as s,
|
|
258
|
-
withReadObserver as w
|
|
259
|
-
};
|
|
260
|
-
//# sourceMappingURL=shared-x2HJmEyO.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shared-x2HJmEyO.mjs","sources":["../src/utils/log.js","../src/core/state.js","../src/core/effect.js"],"sourcesContent":["/**\n * Environment-safe logging utilities for constrained runtimes\n * (e.g. service workers, embedded engines, SSR environments).\n *\n * All core and addon files should import these instead of\n * calling console.* directly to avoid ReferenceError when\n * console is not defined.\n */\n\nexport function logWarn(msg, ...rest) {\n if (typeof console !== 'undefined' && typeof console.warn === 'function') {\n console.warn(msg, ...rest);\n }\n}\n\nexport function logError(msg, ...rest) {\n if (typeof console !== 'undefined' && typeof console.error === 'function') {\n console.error(msg, ...rest);\n }\n}\n","/**\n * Lume-JS Reactive State Core\n *\n * Provides minimal reactive state with standard JavaScript.\n * Features automatic microtask batching for performance.\n * Read tracking is opt-in via withReadObserver — state.js has zero permanent\n * dependency on effect.js or any other module.\n *\n * Features:\n * - Lightweight and Go-style\n * - Explicit nested states\n * - $subscribe for listening to key changes\n * - Cleanup with unsubscribe\n * - Per-state microtask batching for writes\n * - Scope-based read tracking via withReadObserver (multi-observer safe)\n *\n * Usage:\n * import { state } from \"lume-js\";\n *\n * const store = state({ count: 0 });\n * const unsub = store.$subscribe(\"count\", val => console.log(val));\n * unsub(); // cleanup\n */\n\nimport { logError, logWarn } from '../utils/log.js';\n\n// Per-state batching – each state object maintains its own microtask flush.\n// This keeps effects simple and aligned with Lume's minimal philosophy.\n\n/**\n * Creates a reactive state object.\n *\n * @param {Object} obj - Initial state object (must be plain object)\n * @returns {Proxy} Reactive proxy with $subscribe method\n *\n * @example\n * const store = state({ count: 0 });\n */\n\n// Active read observers — only populated during withReadObserver scopes.\n// This keeps state.js pure: tracking only happens when someone explicitly\n// asks to observe reads within a synchronous function call.\n//\n// Note: This Set is module-level, so all reactive state instances and effects\n// within the SAME module instance share it. This is standard behavior for\n// auto-tracking reactive libraries (Vue, MobX, Solid, etc.). Multiple copies\n// of the lume-js module (e.g. from different bundled chunks) each get their\n// own independent Set via ES module / CommonJS isolation.\nconst readers = new Set();\n\n/**\n * Run a function with a read observer active.\n * The observer receives (proxy, key, registerEffect) for every property read.\n * Multiple observers can be active simultaneously (nested effects, devtools, etc.)\n *\n * Internal API — used by effect.js for auto-tracking. May be stabilized\n * for third-party addons in a future release.\n *\n * @security The observer sees reads from ALL state instances within the same\n * module instance, including nested scopes. Only pass trusted observer functions.\n * A future scoped variant (e.g., scopedReadObserver(store, fn)) may limit\n * observation to a single state instance.\n *\n * @param {function} onRead - Called on each property access inside fn\n * @param {function} fn - The function to run under observation\n */\nexport function withReadObserver(onRead, fn) {\n readers.add(onRead);\n try {\n return fn();\n } finally {\n readers.delete(onRead);\n }\n}\n\nexport function state(obj) {\n // Validate input\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n throw new Error('state() requires a plain object');\n }\n if (Object.isFrozen(obj) || Object.isSealed(obj)) {\n throw new Error('state() requires a mutable plain object');\n }\n\n // Object.create(null) - no prototype chain lookups\n const listeners = Object.create(null);\n const pendingNotifications = new Map(); // Per-state pending changes\n const pendingEffects = new Set(); // Dedupe effects per state\n const beforeFlushHooks = [];\n let flushScheduled = false;\n\n /**\n * Schedule a single microtask flush for this state object.\n *\n * Flush order per state:\n * 1) Notify subscribers for changed keys (key → subscribers)\n * 2) Run each queued effect exactly once (Set-based dedupe)\n * 3) Repeat up to 100 iterations to handle cascading updates,\n * then log an error to prevent infinite loops.\n *\n * Notes:\n * - Batching is per state; effects that depend on multiple states\n * may run once per state that changed (by design).\n */\n function scheduleFlush() {\n if (flushScheduled) return;\n\n flushScheduled = true;\n // eslint-disable-next-line sonarjs/cognitive-complexity -- single-pass flush loop: hooks → subscribers → effects → cycle detection; must stay atomic\n queueMicrotask(() => {\n let iterations = 0;\n const MAX_ITERATIONS = 100;\n\n try {\n while ((pendingNotifications.size > 0 || pendingEffects.size > 0) && iterations < MAX_ITERATIONS) {\n iterations++;\n\n // Run registered before-flush hooks (e.g. plugin onNotify)\n for (let i = 0; i < beforeFlushHooks.length; i++) {\n try {\n beforeFlushHooks[i]();\n } catch (err) {\n logError('[Lume.js state] Error in beforeFlush hook:', err);\n }\n }\n\n // Notify all subscribers of changed keys\n for (const [key, value] of pendingNotifications) {\n if (listeners[key]) {\n const subs = listeners[key];\n let i = 0;\n while (i < subs.length) {\n const fn = subs[i];\n try {\n fn(value);\n } catch (err) {\n logError(`[Lume.js state] Error notifying subscriber for key \"${String(key)}\":`, err);\n }\n // Only advance if fn wasn't removed (something shifted into its place)\n if (subs[i] === fn) i++;\n }\n }\n }\n\n pendingNotifications.clear();\n\n // Run each effect exactly once (Set deduplicates)\n const effects = new Array(pendingEffects.size);\n let idx = 0;\n for (const effect of pendingEffects) {\n effects[idx++] = effect;\n }\n pendingEffects.clear();\n for (let i = 0; i < effects.length; i++) {\n try {\n effects[i]();\n } catch (err) {\n logError('[Lume.js state] Error in effect:', err);\n }\n }\n }\n } finally {\n flushScheduled = false;\n }\n\n if (iterations >= MAX_ITERATIONS) {\n logError(\n '[Lume.js state] Maximum flush iterations reached (100). ' +\n 'This usually indicates an infinite loop caused by an effect or computed mutating state it depends on.'\n );\n }\n });\n }\n\n // Brand symbol for type-level reactive identification\n const REACTIVE_BRAND = Symbol('lume.reactive');\n obj[REACTIVE_BRAND] = true;\n\n // Defined once per state instance — not per property read — to avoid per-read closure allocation.\n const registerEffect = (key, executeFn) => {\n if (!listeners[key]) listeners[key] = [];\n\n const callback = () => {\n pendingEffects.add(executeFn);\n };\n\n listeners[key].push(callback);\n\n return () => {\n if (listeners[key]) {\n const idx = listeners[key].indexOf(callback);\n if (idx !== -1) {\n listeners[key].splice(idx, 1);\n if (listeners[key].length === 0) delete listeners[key];\n }\n }\n };\n };\n\n const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n const proxy = new Proxy(obj, {\n get(target, key) {\n // Skip effect tracking for internal meta methods (e.g. $subscribe)\n if (typeof key === 'string' && key.startsWith('$')) {\n return target[key];\n }\n\n const value = target[key];\n\n // Notify active read observers (effects, devtools, etc.)\n if (readers.size > 0) {\n for (const reader of readers) {\n reader(proxy, key, registerEffect);\n }\n }\n\n return value;\n },\n\n set(target, key, value) {\n if (typeof key === 'string' && BLOCKED_KEYS.has(key)) {\n logWarn(`[Lume.js state] Blocked write to reserved key \"${key}\"`);\n return true;\n }\n\n const oldValue = target[key];\n\n // Skip update if value unchanged - Object.is() handles NaN and -0 correctly\n if (Object.is(oldValue, value)) return true;\n\n target[key] = value;\n\n // Batch notifications at the state level (per-state, not global)\n pendingNotifications.set(key, value);\n scheduleFlush();\n\n return true;\n }\n });\n\n /**\n * Subscribe to changes for a specific key.\n * Calls the callback immediately with the current value.\n * Returns an unsubscribe function for cleanup.\n *\n * @param {string} key - Property key to watch\n * @param {function} fn - Callback function\n * @returns {function} Unsubscribe function\n */\n // Set on obj (not proxy) to avoid triggering the set trap.\n // The get trap already returns target[key] directly for $-prefixed keys.\n /**\n * Register a callback to run before each flush.\n * Returns an unsubscribe function.\n */\n obj.$beforeFlush = (fn) => {\n if (typeof fn !== 'function') {\n throw new Error('$beforeFlush requires a function');\n }\n if (beforeFlushHooks.indexOf(fn) === -1) {\n beforeFlushHooks.push(fn);\n }\n return () => {\n const idx = beforeFlushHooks.indexOf(fn);\n if (idx !== -1) {\n beforeFlushHooks.splice(idx, 1);\n }\n };\n };\n\n const MAX_SUBSCRIBERS = 1000;\n\n obj.$subscribe = (key, fn) => {\n if (typeof fn !== 'function') {\n throw new Error('Subscriber must be a function');\n }\n\n if (!listeners[key]) listeners[key] = [];\n if (listeners[key].length >= MAX_SUBSCRIBERS) {\n logWarn(`[Lume.js state] Subscriber limit (${MAX_SUBSCRIBERS}) reached for key \"${key}\". New subscriber ignored.`);\n return () => {};\n }\n listeners[key].push(fn);\n\n // Call immediately with current value (NOT batched)\n fn(proxy[key]);\n\n // Return unsubscribe function\n return () => {\n if (listeners[key]) {\n const idx = listeners[key].indexOf(fn);\n if (idx !== -1) {\n listeners[key].splice(idx, 1);\n if (listeners[key].length === 0) delete listeners[key];\n }\n }\n };\n };\n\n return proxy;\n}\n","import { withReadObserver } from './state.js';\nimport { logError } from '../utils/log.js';\n\n/**\n * Lume-JS Effect\n *\n * Reactive effects with two modes:\n * 1. Auto-tracking (default): Tracks dependencies automatically via withReadObserver\n * 2. Explicit deps: You specify exactly what triggers re-runs\n *\n * Auto-tracking uses scope-based read observation — state.js has zero permanent\n * dependency on this module. Read tracking is only active during the synchronous\n * execution of an effect's body.\n *\n * Usage:\n * import { effect } from \"lume-js\";\n *\n * // Auto-tracking mode (existing behavior)\n * effect(() => {\n * console.log('Count is:', store.count);\n * // Automatically re-runs when store.count changes\n * });\n *\n * // Explicit deps mode (new - no magic)\n * effect(() => {\n * console.log('Count is:', store.count);\n * }, [[store, 'count']]); // Only re-runs when store.count changes\n *\n * Features:\n * - Automatic dependency collection via withReadObserver scope (default)\n * - Explicit dependencies for side-effects\n * - Returns cleanup function\n * - Compatible with per-state batching\n */\n\n// Module-scoped effect context (prevents third-party spoofing via globalThis)\nlet currentEffect = null;\n\n// withReadObserver is used below to scope read tracking to synchronous effect execution.\n\n/**\n * Creates an effect that runs reactively\n *\n * @param {function} fn - Function to run reactively\n * @param {Array<[object, string]>} [deps] - Optional explicit dependencies as [store, key] tuples\n * @returns {function} Cleanup function to stop the effect\n *\n * @example\n * // Auto-tracking (default)\n * const store = state({ count: 0 });\n * effect(() => {\n * document.title = `Count: ${store.count}`;\n * });\n * \n * @example\n * // Explicit deps (no magic)\n * effect(() => {\n * analytics.log(store.count); // Won't track store.count automatically\n * }, [[store, 'count']]); // Explicit: only re-run on store.count\n */\n// eslint-disable-next-line sonarjs/cognitive-complexity -- handles both auto-tracking and explicit-deps modes with cleanup; splitting would require exporting internal state\nexport function effect(fn, deps) {\n if (typeof fn !== 'function') {\n throw new Error('effect() requires a function');\n }\n\n const cleanups = [];\n let isRunning = false;\n\n /**\n * Execute the effect function\n */\n const execute = () => {\n /* v8 ignore next -- re-entry guard: unreachable because $subscribe fires via microtask after isRunning resets in finally */\n if (isRunning) return;\n isRunning = true;\n\n try {\n fn();\n } catch (error) {\n logError('[Lume.js effect] Error in effect:', error);\n throw error;\n } finally {\n isRunning = false;\n }\n };\n\n // EXPLICIT DEPS MODE: deps array provided\n if (Array.isArray(deps)) {\n // Subscribe to each [store, key1, key2, ...] tuple explicitly\n for (const dep of deps) {\n if (Array.isArray(dep) && dep.length >= 2) {\n const [store, ...keys] = dep;\n if (store && typeof store.$subscribe === 'function') {\n // Subscribe to each key in this tuple\n for (const key of keys) {\n // $subscribe calls immediately, then on changes\n // We want: call execute immediately once, then on changes\n let isFirst = true;\n const unsub = store.$subscribe(key, () => {\n if (isFirst) {\n isFirst = false;\n return; // Skip first call, we'll run execute() below\n }\n execute();\n });\n cleanups.push(unsub);\n }\n }\n }\n }\n // Run immediately\n execute();\n }\n // AUTO-TRACKING MODE: no deps (existing behavior)\n else {\n const executeWithTracking = () => {\n /* v8 ignore next -- defensive guard: synchronous re-entry is unreachable through the public API */\n if (isRunning) return;\n\n // Save previous subscriptions instead of cleaning immediately.\n // If fn() doesn't read any state (early return / error), we restore\n // them so the effect stays reactive.\n const oldCleanups = cleanups.splice(0);\n\n // Create effect context for tracking\n const myContext = {\n fn,\n cleanups,\n execute: executeWithTracking,\n tracking: {}\n };\n\n // Set as current effect (for state.js to detect)\n // Save previous context to support nested effects/computed\n const previousEffect = currentEffect;\n currentEffect = myContext;\n isRunning = true;\n\n try {\n const onRead = (proxy, key, registerEffect) => {\n // Only the currently active effect (not a nested one) creates subscriptions\n if (currentEffect !== myContext) return;\n if (myContext.tracking[key]) return;\n myContext.tracking[key] = true;\n myContext.cleanups.push(registerEffect(key, myContext.execute));\n };\n withReadObserver(onRead, fn);\n } catch (error) {\n // On error, restore old subscriptions so the effect stays reactive\n cleanups.length = 0;\n cleanups.push(...oldCleanups);\n logError('[Lume.js effect] Error in effect:', error);\n throw error;\n } finally {\n // Restore previous context (not undefined) to support nesting\n currentEffect = previousEffect;\n isRunning = false;\n }\n\n // If fn() created new subscriptions, clean old ones.\n // If it didn't (e.g., early return), keep old subscriptions intact.\n if (cleanups.length > 0) {\n for (const cleanup of oldCleanups) cleanup();\n } else {\n cleanups.push(...oldCleanups);\n }\n };\n\n // Run immediately to collect initial dependencies\n executeWithTracking();\n }\n\n // Return cleanup function\n return () => {\n // while/pop is faster than forEach\n while (cleanups.length) cleanups.pop()();\n };\n}"],"names":["effect"],"mappings":"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"}
|