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.
Files changed (59) hide show
  1. package/dist/context/auto-scope.d.ts +167 -0
  2. package/dist/context/auto-scope.js +381 -0
  3. package/dist/context/context.d.ts +111 -0
  4. package/dist/context/context.js +283 -0
  5. package/dist/context/index.d.ts +2 -0
  6. package/dist/context/index.js +2 -0
  7. package/dist/context/raw.d.ts +2 -0
  8. package/dist/context/raw.js +2 -0
  9. package/dist/core/dev.d.ts +7 -0
  10. package/dist/core/dev.js +14 -0
  11. package/dist/core/for.d.ts +42 -0
  12. package/dist/core/for.js +311 -0
  13. package/dist/core/index.d.ts +14 -0
  14. package/dist/core/index.js +14 -0
  15. package/dist/core/key.d.ts +33 -0
  16. package/dist/core/key.js +83 -0
  17. package/dist/core/match.d.ts +22 -0
  18. package/dist/core/match.js +175 -0
  19. package/dist/core/mutation.d.ts +55 -0
  20. package/dist/core/mutation.js +128 -0
  21. package/dist/core/persist.d.ts +63 -0
  22. package/dist/core/persist.js +371 -0
  23. package/dist/core/query.d.ts +72 -0
  24. package/dist/core/query.js +184 -0
  25. package/dist/core/resource.d.ts +299 -0
  26. package/dist/core/resource.js +924 -0
  27. package/dist/core/scheduler.d.ts +111 -0
  28. package/dist/core/scheduler.js +243 -0
  29. package/dist/core/scope.d.ts +74 -0
  30. package/dist/core/scope.js +171 -0
  31. package/dist/core/signal.d.ts +88 -0
  32. package/dist/core/signal.js +451 -0
  33. package/dist/core/store.d.ts +130 -0
  34. package/dist/core/store.js +234 -0
  35. package/dist/core/virtual.d.ts +26 -0
  36. package/dist/core/virtual.js +277 -0
  37. package/dist/core/watch-testing.d.ts +13 -0
  38. package/dist/core/watch-testing.js +16 -0
  39. package/dist/core/watch.d.ts +81 -0
  40. package/dist/core/watch.js +353 -0
  41. package/dist/core/when.d.ts +23 -0
  42. package/dist/core/when.js +124 -0
  43. package/dist/index.d.ts +4 -0
  44. package/dist/index.js +4 -0
  45. package/dist/internal/watch-testing.d.ts +1 -0
  46. package/dist/internal/watch-testing.js +8 -0
  47. package/dist/router/index.d.ts +1 -0
  48. package/dist/router/index.js +1 -0
  49. package/dist/router/route.d.ts +23 -0
  50. package/dist/router/route.js +48 -0
  51. package/dist/router/router.d.ts +23 -0
  52. package/dist/router/router.js +169 -0
  53. package/dist/runtime/bind.d.ts +65 -0
  54. package/dist/runtime/bind.js +616 -0
  55. package/dist/runtime/index.d.ts +10 -0
  56. package/dist/runtime/index.js +9 -0
  57. package/dist/simple.d.ts +11 -0
  58. package/dist/simple.js +11 -0
  59. 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 {};