dalila 1.4.2 → 1.4.4
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/dist/context/auto-scope.d.ts +167 -0
- package/dist/context/auto-scope.js +381 -0
- package/dist/context/context.d.ts +111 -0
- package/dist/context/context.js +283 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/context/raw.d.ts +2 -0
- package/dist/context/raw.js +2 -0
- package/dist/core/dev.d.ts +7 -0
- package/dist/core/dev.js +14 -0
- package/dist/core/for.d.ts +42 -0
- package/dist/core/for.js +311 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +14 -0
- package/dist/core/key.d.ts +33 -0
- package/dist/core/key.js +83 -0
- package/dist/core/match.d.ts +22 -0
- package/dist/core/match.js +175 -0
- package/dist/core/mutation.d.ts +55 -0
- package/dist/core/mutation.js +128 -0
- package/dist/core/persist.d.ts +63 -0
- package/dist/core/persist.js +371 -0
- package/dist/core/query.d.ts +72 -0
- package/dist/core/query.js +184 -0
- package/dist/core/resource.d.ts +299 -0
- package/dist/core/resource.js +924 -0
- package/dist/core/scheduler.d.ts +111 -0
- package/dist/core/scheduler.js +243 -0
- package/dist/core/scope.d.ts +74 -0
- package/dist/core/scope.js +171 -0
- package/dist/core/signal.d.ts +88 -0
- package/dist/core/signal.js +451 -0
- package/dist/core/store.d.ts +130 -0
- package/dist/core/store.js +234 -0
- package/dist/core/virtual.d.ts +26 -0
- package/dist/core/virtual.js +277 -0
- package/dist/core/watch-testing.d.ts +13 -0
- package/dist/core/watch-testing.js +16 -0
- package/dist/core/watch.d.ts +81 -0
- package/dist/core/watch.js +353 -0
- package/dist/core/when.d.ts +23 -0
- package/dist/core/when.js +124 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/internal/watch-testing.d.ts +1 -0
- package/dist/internal/watch-testing.js +8 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.js +1 -0
- package/dist/router/route.d.ts +23 -0
- package/dist/router/route.js +48 -0
- package/dist/router/router.d.ts +23 -0
- package/dist/router/router.js +169 -0
- package/dist/runtime/bind.d.ts +65 -0
- package/dist/runtime/bind.js +616 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.js +9 -0
- package/dist/simple.d.ts +11 -0
- package/dist/simple.js +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { getCurrentScope, withScope } from './scope.js';
|
|
2
|
+
import { scheduleMicrotask, isBatching, queueInBatch } from './scheduler.js';
|
|
3
|
+
/**
|
|
4
|
+
* Optional global error handler for reactive execution.
|
|
5
|
+
*
|
|
6
|
+
* The runtime keeps running even if an effect/computed throws:
|
|
7
|
+
* - if a handler is registered, we forward errors to it
|
|
8
|
+
* - otherwise we log to the console
|
|
9
|
+
*/
|
|
10
|
+
let effectErrorHandler = null;
|
|
11
|
+
/**
|
|
12
|
+
* Register a global error handler for effects/computed invalidations.
|
|
13
|
+
*
|
|
14
|
+
* Use this to report errors without crashing the reactive graph.
|
|
15
|
+
*/
|
|
16
|
+
export function setEffectErrorHandler(handler) {
|
|
17
|
+
effectErrorHandler = handler;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Normalize unknown throws into Error and route to the global handler (or console).
|
|
21
|
+
*/
|
|
22
|
+
function reportEffectError(error, source) {
|
|
23
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
24
|
+
if (effectErrorHandler)
|
|
25
|
+
effectErrorHandler(err, source);
|
|
26
|
+
else
|
|
27
|
+
console.error(`[Dalila] Error in ${source}:`, err);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Currently executing effect for dependency collection.
|
|
31
|
+
* Any signal read while this is set subscribes this effect.
|
|
32
|
+
*/
|
|
33
|
+
let activeEffect = null;
|
|
34
|
+
/**
|
|
35
|
+
* Scope associated with the currently executing effect.
|
|
36
|
+
*
|
|
37
|
+
* Best-effort safety:
|
|
38
|
+
* - effects run inside an owning scope
|
|
39
|
+
* - signals only subscribe the active effect if the caller is in the same scope
|
|
40
|
+
*/
|
|
41
|
+
let activeScope = null;
|
|
42
|
+
/**
|
|
43
|
+
* Per-tick dedupe for async effects.
|
|
44
|
+
* Multiple writes in the same tick schedule an effect only once.
|
|
45
|
+
*/
|
|
46
|
+
const pendingEffects = new Set();
|
|
47
|
+
/**
|
|
48
|
+
* Stable runner per effect (function identity).
|
|
49
|
+
*
|
|
50
|
+
* Why:
|
|
51
|
+
* - when batching, we enqueue a function into the batch queue
|
|
52
|
+
* - dedupe uses function identity
|
|
53
|
+
* - each effect needs a stable runner function across schedules
|
|
54
|
+
*/
|
|
55
|
+
const effectRunners = new WeakMap();
|
|
56
|
+
/**
|
|
57
|
+
* Schedule an effect with correct semantics:
|
|
58
|
+
* - computed invalidations run synchronously (mark dirty immediately)
|
|
59
|
+
* - normal effects run async (microtask), coalesced/deduped per tick
|
|
60
|
+
* - inside batch(): queue into the batch queue to flush once
|
|
61
|
+
*/
|
|
62
|
+
function scheduleEffect(eff) {
|
|
63
|
+
if (eff.disposed)
|
|
64
|
+
return;
|
|
65
|
+
// Computed invalidation: run immediately so computed becomes dirty synchronously.
|
|
66
|
+
if (eff.sync) {
|
|
67
|
+
try {
|
|
68
|
+
eff();
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
reportEffectError(error, 'computed');
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Dedup before scheduling.
|
|
76
|
+
if (pendingEffects.has(eff))
|
|
77
|
+
return;
|
|
78
|
+
pendingEffects.add(eff);
|
|
79
|
+
// Create / reuse stable runner (so batch dedupe works correctly).
|
|
80
|
+
let runEffect = effectRunners.get(eff);
|
|
81
|
+
if (!runEffect) {
|
|
82
|
+
runEffect = () => {
|
|
83
|
+
pendingEffects.delete(eff);
|
|
84
|
+
if (eff.disposed)
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
eff();
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
reportEffectError(error, 'effect');
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
effectRunners.set(eff, runEffect);
|
|
94
|
+
}
|
|
95
|
+
// During batch: defer scheduling into the batch queue (no microtask overhead).
|
|
96
|
+
// Outside batch: schedule in a microtask (coalescing across multiple writes).
|
|
97
|
+
if (isBatching())
|
|
98
|
+
queueInBatch(runEffect);
|
|
99
|
+
else
|
|
100
|
+
scheduleMicrotask(runEffect);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a signal: a mutable value with automatic dependency tracking.
|
|
104
|
+
*
|
|
105
|
+
* Reads:
|
|
106
|
+
* - if there is an active effect, subscribe it (dynamic deps supported)
|
|
107
|
+
*
|
|
108
|
+
* Writes:
|
|
109
|
+
* - update the value immediately
|
|
110
|
+
* - notify subscribers (immediately, or deferred via batch queue)
|
|
111
|
+
*
|
|
112
|
+
* Lifecycle:
|
|
113
|
+
* - effects remove themselves from subscriber sets on re-run and on dispose
|
|
114
|
+
* - signals do not "own" subscriber lifetimes; they only maintain the set
|
|
115
|
+
*/
|
|
116
|
+
export function signal(initialValue) {
|
|
117
|
+
let value = initialValue;
|
|
118
|
+
const subscribers = new Set();
|
|
119
|
+
const read = () => {
|
|
120
|
+
if (activeEffect && !activeEffect.disposed) {
|
|
121
|
+
// Scope-aware subscription guard (best effort):
|
|
122
|
+
// - if the active effect is not scoped, allow subscription
|
|
123
|
+
// - if scoped, only subscribe when currently executing inside that scope
|
|
124
|
+
if (!activeScope) {
|
|
125
|
+
if (!subscribers.has(activeEffect)) {
|
|
126
|
+
subscribers.add(activeEffect);
|
|
127
|
+
(activeEffect.deps ?? (activeEffect.deps = new Set())).add(subscribers);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const current = getCurrentScope();
|
|
132
|
+
if (activeScope === current) {
|
|
133
|
+
if (!subscribers.has(activeEffect)) {
|
|
134
|
+
subscribers.add(activeEffect);
|
|
135
|
+
(activeEffect.deps ?? (activeEffect.deps = new Set())).add(subscribers);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
};
|
|
142
|
+
const notify = () => {
|
|
143
|
+
for (const eff of subscribers)
|
|
144
|
+
scheduleEffect(eff);
|
|
145
|
+
};
|
|
146
|
+
read.set = (nextValue) => {
|
|
147
|
+
// No-op on identical values.
|
|
148
|
+
if (Object.is(value, nextValue))
|
|
149
|
+
return;
|
|
150
|
+
// State updates are immediate even inside `batch()`.
|
|
151
|
+
value = nextValue;
|
|
152
|
+
// Notify now, or defer into the batch queue.
|
|
153
|
+
if (isBatching())
|
|
154
|
+
queueInBatch(notify);
|
|
155
|
+
else
|
|
156
|
+
notify();
|
|
157
|
+
};
|
|
158
|
+
read.update = (fn) => {
|
|
159
|
+
read.set(fn(value));
|
|
160
|
+
};
|
|
161
|
+
read.peek = () => value;
|
|
162
|
+
read.on = (callback) => {
|
|
163
|
+
// Create a lightweight effect-like subscriber for manual subscriptions
|
|
164
|
+
const subscriber = (() => {
|
|
165
|
+
if (subscriber.disposed)
|
|
166
|
+
return;
|
|
167
|
+
callback(value);
|
|
168
|
+
});
|
|
169
|
+
subscriber.disposed = false;
|
|
170
|
+
subscribers.add(subscriber);
|
|
171
|
+
(subscriber.deps ?? (subscriber.deps = new Set())).add(subscribers);
|
|
172
|
+
// Return unsubscribe function
|
|
173
|
+
return () => {
|
|
174
|
+
if (subscriber.disposed)
|
|
175
|
+
return;
|
|
176
|
+
subscriber.disposed = true;
|
|
177
|
+
subscribers.delete(subscriber);
|
|
178
|
+
subscriber.deps?.delete(subscribers);
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
return read;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Create an effect: reruns `fn` whenever any tracked signal changes.
|
|
185
|
+
*
|
|
186
|
+
* Scheduling:
|
|
187
|
+
* - the initial run is scheduled (microtask) to coalesce multiple writes
|
|
188
|
+
*
|
|
189
|
+
* Dependency tracking:
|
|
190
|
+
* - before each run, the effect unsubscribes from previous dependencies
|
|
191
|
+
* - during the run, reads resubscribe to the new dependencies (dynamic deps)
|
|
192
|
+
*
|
|
193
|
+
* Scope:
|
|
194
|
+
* - if created inside a scope, the effect runs inside that scope
|
|
195
|
+
* - the effect is disposed automatically when the scope disposes
|
|
196
|
+
*/
|
|
197
|
+
export function effect(fn) {
|
|
198
|
+
const owningScope = getCurrentScope();
|
|
199
|
+
const cleanupDeps = () => {
|
|
200
|
+
if (!run.deps)
|
|
201
|
+
return;
|
|
202
|
+
for (const depSet of run.deps)
|
|
203
|
+
depSet.delete(run);
|
|
204
|
+
run.deps.clear();
|
|
205
|
+
};
|
|
206
|
+
const dispose = () => {
|
|
207
|
+
if (run.disposed)
|
|
208
|
+
return;
|
|
209
|
+
run.disposed = true;
|
|
210
|
+
cleanupDeps();
|
|
211
|
+
pendingEffects.delete(run);
|
|
212
|
+
};
|
|
213
|
+
const run = (() => {
|
|
214
|
+
if (run.disposed)
|
|
215
|
+
return;
|
|
216
|
+
// Dynamic deps: unsubscribe from previous reads.
|
|
217
|
+
cleanupDeps();
|
|
218
|
+
const prevEffect = activeEffect;
|
|
219
|
+
const prevScope = activeScope;
|
|
220
|
+
activeEffect = run;
|
|
221
|
+
activeScope = owningScope ?? null;
|
|
222
|
+
try {
|
|
223
|
+
// Run inside owning scope so resources created by the effect are scoped.
|
|
224
|
+
if (owningScope)
|
|
225
|
+
withScope(owningScope, fn);
|
|
226
|
+
else
|
|
227
|
+
fn();
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
activeEffect = prevEffect;
|
|
231
|
+
activeScope = prevScope;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
scheduleEffect(run);
|
|
235
|
+
if (owningScope)
|
|
236
|
+
owningScope.onCleanup(dispose);
|
|
237
|
+
return dispose;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Create a computed signal (derived, cached, read-only).
|
|
241
|
+
*
|
|
242
|
+
* Semantics:
|
|
243
|
+
* - lazy: computes on first read
|
|
244
|
+
* - cached: returns the cached value until invalidated
|
|
245
|
+
* - synchronous invalidation: dependencies mark it dirty immediately
|
|
246
|
+
*
|
|
247
|
+
* Dependency tracking:
|
|
248
|
+
* - while computing, we collect dependencies into an internal "markDirty" effect
|
|
249
|
+
* - those dependencies will synchronously mark this computed as dirty on change
|
|
250
|
+
*
|
|
251
|
+
* Subscription:
|
|
252
|
+
* - other effects can subscribe to the computed like a normal signal
|
|
253
|
+
*/
|
|
254
|
+
export function computed(fn) {
|
|
255
|
+
let value;
|
|
256
|
+
let dirty = true;
|
|
257
|
+
const subscribers = new Set();
|
|
258
|
+
// Dep sets that `markDirty` is currently registered in (so we can unsubscribe on recompute).
|
|
259
|
+
let trackedDeps = new Set();
|
|
260
|
+
/**
|
|
261
|
+
* Internal invalidator.
|
|
262
|
+
* Runs synchronously when any dependency changes.
|
|
263
|
+
*/
|
|
264
|
+
const markDirty = (() => {
|
|
265
|
+
if (dirty)
|
|
266
|
+
return;
|
|
267
|
+
dirty = true;
|
|
268
|
+
for (const eff of subscribers)
|
|
269
|
+
scheduleEffect(eff);
|
|
270
|
+
});
|
|
271
|
+
markDirty.disposed = false;
|
|
272
|
+
markDirty.sync = true;
|
|
273
|
+
const cleanupDeps = () => {
|
|
274
|
+
for (const depSet of trackedDeps)
|
|
275
|
+
depSet.delete(markDirty);
|
|
276
|
+
trackedDeps.clear();
|
|
277
|
+
if (markDirty.deps)
|
|
278
|
+
markDirty.deps.clear();
|
|
279
|
+
};
|
|
280
|
+
const read = () => {
|
|
281
|
+
// Allow effects to subscribe to this computed (same rules as signal()).
|
|
282
|
+
if (activeEffect && !activeEffect.disposed) {
|
|
283
|
+
if (!activeScope) {
|
|
284
|
+
if (!subscribers.has(activeEffect)) {
|
|
285
|
+
subscribers.add(activeEffect);
|
|
286
|
+
(activeEffect.deps ?? (activeEffect.deps = new Set())).add(subscribers);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const current = getCurrentScope();
|
|
291
|
+
if (activeScope === current) {
|
|
292
|
+
if (!subscribers.has(activeEffect)) {
|
|
293
|
+
subscribers.add(activeEffect);
|
|
294
|
+
(activeEffect.deps ?? (activeEffect.deps = new Set())).add(subscribers);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (dirty) {
|
|
300
|
+
cleanupDeps();
|
|
301
|
+
const prevEffect = activeEffect;
|
|
302
|
+
const prevScope = activeScope;
|
|
303
|
+
// Collect deps into markDirty.
|
|
304
|
+
activeEffect = markDirty;
|
|
305
|
+
// During dependency collection for computed:
|
|
306
|
+
// - we want to subscribe to its dependencies regardless of the caller scope.
|
|
307
|
+
// - the computed's deps belong to the computed itself, not to whoever read it.
|
|
308
|
+
activeScope = null;
|
|
309
|
+
try {
|
|
310
|
+
value = fn();
|
|
311
|
+
dirty = false;
|
|
312
|
+
// Snapshot the current dep sets for later unsubscription.
|
|
313
|
+
if (markDirty.deps)
|
|
314
|
+
trackedDeps = new Set(markDirty.deps);
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
activeEffect = prevEffect;
|
|
318
|
+
activeScope = prevScope;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return value;
|
|
322
|
+
};
|
|
323
|
+
read.set = () => {
|
|
324
|
+
throw new Error('Cannot set a computed signal directly. Computed signals are derived from other signals.');
|
|
325
|
+
};
|
|
326
|
+
read.update = () => {
|
|
327
|
+
throw new Error('Cannot update a computed signal directly. Computed signals are derived from other signals.');
|
|
328
|
+
};
|
|
329
|
+
read.peek = () => {
|
|
330
|
+
// For computed, peek still needs to compute if dirty, but without tracking
|
|
331
|
+
if (dirty) {
|
|
332
|
+
cleanupDeps();
|
|
333
|
+
const prevEffect = activeEffect;
|
|
334
|
+
const prevScope = activeScope;
|
|
335
|
+
activeEffect = markDirty;
|
|
336
|
+
activeScope = null;
|
|
337
|
+
try {
|
|
338
|
+
value = fn();
|
|
339
|
+
dirty = false;
|
|
340
|
+
if (markDirty.deps)
|
|
341
|
+
trackedDeps = new Set(markDirty.deps);
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
activeEffect = prevEffect;
|
|
345
|
+
activeScope = prevScope;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return value;
|
|
349
|
+
};
|
|
350
|
+
read.on = (callback) => {
|
|
351
|
+
const subscriber = (() => {
|
|
352
|
+
if (subscriber.disposed)
|
|
353
|
+
return;
|
|
354
|
+
// For computed, we need to get the latest value
|
|
355
|
+
callback(read.peek());
|
|
356
|
+
});
|
|
357
|
+
subscriber.disposed = false;
|
|
358
|
+
subscribers.add(subscriber);
|
|
359
|
+
(subscriber.deps ?? (subscriber.deps = new Set())).add(subscribers);
|
|
360
|
+
return () => {
|
|
361
|
+
if (subscriber.disposed)
|
|
362
|
+
return;
|
|
363
|
+
subscriber.disposed = true;
|
|
364
|
+
subscribers.delete(subscriber);
|
|
365
|
+
subscriber.deps?.delete(subscribers);
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
return read;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Async effect with cancellation.
|
|
372
|
+
*
|
|
373
|
+
* Semantics:
|
|
374
|
+
* - provides an AbortSignal to the callback
|
|
375
|
+
* - on re-run, aborts the previous run before starting the next
|
|
376
|
+
* - when disposed, aborts the current run and stops future scheduling
|
|
377
|
+
*/
|
|
378
|
+
export function effectAsync(fn) {
|
|
379
|
+
const owningScope = getCurrentScope();
|
|
380
|
+
let controller = null;
|
|
381
|
+
const cleanupDeps = () => {
|
|
382
|
+
if (!run.deps)
|
|
383
|
+
return;
|
|
384
|
+
for (const depSet of run.deps)
|
|
385
|
+
depSet.delete(run);
|
|
386
|
+
run.deps.clear();
|
|
387
|
+
};
|
|
388
|
+
const dispose = () => {
|
|
389
|
+
if (run.disposed)
|
|
390
|
+
return;
|
|
391
|
+
run.disposed = true;
|
|
392
|
+
controller?.abort();
|
|
393
|
+
controller = null;
|
|
394
|
+
cleanupDeps();
|
|
395
|
+
pendingEffects.delete(run);
|
|
396
|
+
};
|
|
397
|
+
const run = (() => {
|
|
398
|
+
if (run.disposed)
|
|
399
|
+
return;
|
|
400
|
+
// Abort previous run (if any), then create a new controller for this run.
|
|
401
|
+
controller?.abort();
|
|
402
|
+
controller = new AbortController();
|
|
403
|
+
cleanupDeps();
|
|
404
|
+
const prevEffect = activeEffect;
|
|
405
|
+
const prevScope = activeScope;
|
|
406
|
+
activeEffect = run;
|
|
407
|
+
activeScope = owningScope ?? null;
|
|
408
|
+
try {
|
|
409
|
+
const exec = () => fn(controller.signal);
|
|
410
|
+
if (owningScope)
|
|
411
|
+
withScope(owningScope, exec);
|
|
412
|
+
else
|
|
413
|
+
exec();
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
activeEffect = prevEffect;
|
|
417
|
+
activeScope = prevScope;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
scheduleEffect(run);
|
|
421
|
+
if (owningScope)
|
|
422
|
+
owningScope.onCleanup(dispose);
|
|
423
|
+
return dispose;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Run a function without tracking any signal reads as dependencies.
|
|
427
|
+
*
|
|
428
|
+
* Use this inside an effect when you want to read a signal's value
|
|
429
|
+
* without creating a dependency on it.
|
|
430
|
+
*
|
|
431
|
+
* Example:
|
|
432
|
+
* ```ts
|
|
433
|
+
* effect(() => {
|
|
434
|
+
* const tracked = count(); // This read is tracked
|
|
435
|
+
* const untracked = untrack(() => other()); // This read is NOT tracked
|
|
436
|
+
* });
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
export function untrack(fn) {
|
|
440
|
+
const prevEffect = activeEffect;
|
|
441
|
+
const prevScope = activeScope;
|
|
442
|
+
activeEffect = null;
|
|
443
|
+
activeScope = null;
|
|
444
|
+
try {
|
|
445
|
+
return fn();
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
activeEffect = prevEffect;
|
|
449
|
+
activeScope = prevScope;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Store - Matar necessidade de Zustand
|
|
3
|
+
*
|
|
4
|
+
* createStore() cria um objeto reativo global que:
|
|
5
|
+
* - Não precisa de scope
|
|
6
|
+
* - É singleton por padrão
|
|
7
|
+
* - Suporta computed properties
|
|
8
|
+
* - Suporta actions
|
|
9
|
+
* - Totalmente type-safe
|
|
10
|
+
*/
|
|
11
|
+
type AnyFunction = (...args: any[]) => any;
|
|
12
|
+
type StoreActions<T> = {
|
|
13
|
+
[K in keyof T as T[K] extends AnyFunction ? K : never]: T[K];
|
|
14
|
+
};
|
|
15
|
+
type StoreGetters<T> = {
|
|
16
|
+
[K in keyof T as T[K] extends AnyFunction ? never : K]: () => T[K];
|
|
17
|
+
};
|
|
18
|
+
type Store<T> = StoreGetters<T> & StoreActions<T> & {
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to any state change (like Zustand)
|
|
21
|
+
*/
|
|
22
|
+
subscribe(fn: (state: T) => void): () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Get snapshot of current state (for devtools)
|
|
25
|
+
*/
|
|
26
|
+
getState(): T;
|
|
27
|
+
/**
|
|
28
|
+
* Set multiple values at once (batched)
|
|
29
|
+
*/
|
|
30
|
+
setState(partial: Partial<T> | ((state: T) => Partial<T>)): void;
|
|
31
|
+
/**
|
|
32
|
+
* Reset store to initial state
|
|
33
|
+
*/
|
|
34
|
+
reset(): void;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Create a global reactive store (Zustand-like but better)
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const useCounter = createStore({
|
|
42
|
+
* count: 0,
|
|
43
|
+
* increment() {
|
|
44
|
+
* this.count++;
|
|
45
|
+
* },
|
|
46
|
+
* decrement() {
|
|
47
|
+
* this.count--;
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // Use anywhere (no scope needed!)
|
|
52
|
+
* console.log(useCounter.count()); // 0
|
|
53
|
+
* useCounter.increment();
|
|
54
|
+
* console.log(useCounter.count()); // 1
|
|
55
|
+
*
|
|
56
|
+
* // Reactive
|
|
57
|
+
* effect(() => {
|
|
58
|
+
* console.log('Count:', useCounter.count());
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function createStore<T extends Record<string, any>>(initialState: T): Store<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Create a computed store (derived from other stores)
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const todos = createStore({
|
|
69
|
+
* items: [{ done: false }, { done: true }]
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* const stats = createComputedStore(() => ({
|
|
73
|
+
* total: todos.items().length,
|
|
74
|
+
* completed: todos.items().filter(t => t.done).length
|
|
75
|
+
* }));
|
|
76
|
+
*
|
|
77
|
+
* console.log(stats.total()); // 2
|
|
78
|
+
* console.log(stats.completed()); // 1
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createComputedStore<T extends Record<string, any>>(compute: () => T): StoreGetters<T>;
|
|
82
|
+
/**
|
|
83
|
+
* Persist store to localStorage
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const settings = createStore({
|
|
88
|
+
* theme: 'dark',
|
|
89
|
+
* language: 'en'
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* persistStore(settings, 'app-settings');
|
|
93
|
+
* // Auto-saves on change, auto-loads on init
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare function persistStore<T extends Record<string, any>>(store: Store<T>, key: string, options?: {
|
|
97
|
+
storage?: Storage;
|
|
98
|
+
serialize?: (state: T) => string;
|
|
99
|
+
deserialize?: (str: string) => Partial<T>;
|
|
100
|
+
}): () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Create a store slice (like Redux Toolkit)
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const auth = createStoreSlice('auth', {
|
|
107
|
+
* user: null,
|
|
108
|
+
* login(user) {
|
|
109
|
+
* this.user = user;
|
|
110
|
+
* },
|
|
111
|
+
* logout() {
|
|
112
|
+
* this.user = null;
|
|
113
|
+
* }
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* const app = combineStores({ auth, theme, router });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function createStoreSlice<T extends Record<string, any>>(name: string, initialState: T): Store<T> & {
|
|
120
|
+
_name: string;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Combine multiple store slices into one
|
|
124
|
+
*/
|
|
125
|
+
export declare function combineStores<T extends Record<string, Store<any> & {
|
|
126
|
+
_name: string;
|
|
127
|
+
}>>(slices: T): {
|
|
128
|
+
[K in keyof T]: T[K];
|
|
129
|
+
};
|
|
130
|
+
export {};
|