sibujs 1.3.0 → 1.5.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.
Files changed (87) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +53 -14
  3. package/dist/browser.d.cts +14 -9
  4. package/dist/browser.d.ts +14 -9
  5. package/dist/browser.js +4 -4
  6. package/dist/build.cjs +125 -135
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +11 -91
  10. package/dist/cdn.global.js +6 -6
  11. package/dist/chunk-5ZYQ6KDD.js +154 -0
  12. package/dist/chunk-6BMPXPUW.js +26 -0
  13. package/dist/chunk-7GRNSCFT.js +1097 -0
  14. package/dist/chunk-BGTHZHJ5.js +1016 -0
  15. package/dist/chunk-BMPL52BF.js +654 -0
  16. package/dist/chunk-CNZ35WI2.js +178 -0
  17. package/dist/chunk-GJPXRJ45.js +37 -0
  18. package/dist/chunk-JCDUJN2F.js +2779 -0
  19. package/dist/chunk-K4G4ZQNR.js +286 -0
  20. package/dist/chunk-M4NLBH4I.js +725 -0
  21. package/dist/chunk-MB6QFH3I.js +2776 -0
  22. package/dist/chunk-MYRV7VDM.js +742 -0
  23. package/dist/chunk-NZIIMDWI.js +84 -0
  24. package/dist/chunk-P3XWXJZU.js +282 -0
  25. package/dist/chunk-PDZQY43A.js +616 -0
  26. package/dist/chunk-RJ46C3CS.js +1293 -0
  27. package/dist/chunk-SFKNRVCU.js +292 -0
  28. package/dist/chunk-TDGZL5CU.js +365 -0
  29. package/dist/chunk-UHNL42EF.js +2730 -0
  30. package/dist/chunk-VAPYJN4X.js +368 -0
  31. package/dist/chunk-VQDZK23A.js +1023 -0
  32. package/dist/chunk-VQNQZCWJ.js +61 -0
  33. package/dist/chunk-XHK6BDAJ.js +76 -0
  34. package/dist/chunk-XUEEGU5O.js +409 -0
  35. package/dist/chunk-ZWKZCBO6.js +317 -0
  36. package/dist/contracts-ey_Qh8ef.d.cts +239 -0
  37. package/dist/contracts-ey_Qh8ef.d.ts +239 -0
  38. package/dist/contracts-xo5ckdRP.d.cts +240 -0
  39. package/dist/contracts-xo5ckdRP.d.ts +240 -0
  40. package/dist/customElement-BL3Uo8dL.d.cts +318 -0
  41. package/dist/customElement-BL3Uo8dL.d.ts +318 -0
  42. package/dist/data.cjs +52 -11
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +22 -24
  45. package/dist/devtools.d.cts +1 -1
  46. package/dist/devtools.d.ts +1 -1
  47. package/dist/devtools.js +26 -28
  48. package/dist/ecosystem.cjs +31 -6
  49. package/dist/ecosystem.d.cts +4 -4
  50. package/dist/ecosystem.d.ts +4 -4
  51. package/dist/ecosystem.js +7 -7
  52. package/dist/extras.cjs +305 -131
  53. package/dist/extras.d.cts +3 -3
  54. package/dist/extras.d.ts +3 -3
  55. package/dist/extras.js +21 -29
  56. package/dist/index.cjs +124 -56
  57. package/dist/index.d.cts +60 -72
  58. package/dist/index.d.ts +60 -72
  59. package/dist/index.js +10 -14
  60. package/dist/motion.cjs +13 -2
  61. package/dist/motion.d.cts +1 -1
  62. package/dist/motion.d.ts +1 -1
  63. package/dist/motion.js +3 -3
  64. package/dist/patterns.cjs +91 -46
  65. package/dist/patterns.d.cts +46 -60
  66. package/dist/patterns.d.ts +46 -60
  67. package/dist/patterns.js +6 -14
  68. package/dist/performance.cjs +97 -12
  69. package/dist/performance.d.cts +6 -1
  70. package/dist/performance.d.ts +6 -1
  71. package/dist/performance.js +5 -3
  72. package/dist/plugins.cjs +20 -14
  73. package/dist/plugins.d.cts +3 -3
  74. package/dist/plugins.d.ts +3 -3
  75. package/dist/plugins.js +17 -19
  76. package/dist/ssr.cjs +9 -0
  77. package/dist/ssr.d.cts +1 -1
  78. package/dist/ssr.d.ts +1 -1
  79. package/dist/ssr.js +7 -7
  80. package/dist/testing.js +2 -2
  81. package/dist/ui.cjs +130 -53
  82. package/dist/ui.d.cts +13 -16
  83. package/dist/ui.d.ts +13 -16
  84. package/dist/ui.js +7 -9
  85. package/dist/widgets.cjs +31 -6
  86. package/dist/widgets.js +5 -5
  87. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24,8 +24,6 @@ import {
24
24
  lazy,
25
25
  longPress,
26
26
  match,
27
- memo,
28
- memoFn,
29
27
  mount,
30
28
  nextTick,
31
29
  onCleanup,
@@ -45,7 +43,7 @@ import {
45
43
  unregisterComponent,
46
44
  when,
47
45
  writable
48
- } from "./chunk-QWZG56ET.js";
46
+ } from "./chunk-JCDUJN2F.js";
49
47
  import {
50
48
  __resetIdCounter,
51
49
  createId
@@ -187,31 +185,31 @@ import {
187
185
  use,
188
186
  var_,
189
187
  video
190
- } from "./chunk-32DY64NT.js";
188
+ } from "./chunk-P3XWXJZU.js";
191
189
  import {
192
190
  watch
193
- } from "./chunk-NYVAC6P5.js";
191
+ } from "./chunk-GJPXRJ45.js";
194
192
  import {
195
193
  context
196
- } from "./chunk-BGN5ZMP4.js";
194
+ } from "./chunk-6BMPXPUW.js";
197
195
  import {
198
196
  SVG_NS,
199
197
  tagFactory
200
- } from "./chunk-F3FA4F32.js";
198
+ } from "./chunk-SFKNRVCU.js";
201
199
  import {
202
200
  bindDynamic,
203
201
  checkLeaks,
204
202
  dispose,
205
203
  registerDisposer
206
- } from "./chunk-PTQJDMRT.js";
204
+ } from "./chunk-5ZYQ6KDD.js";
207
205
  import {
208
206
  derived
209
- } from "./chunk-NEKUBFPT.js";
207
+ } from "./chunk-XHK6BDAJ.js";
210
208
  import "./chunk-CMBFNA7L.js";
211
209
  import {
212
210
  effect,
213
211
  on
214
- } from "./chunk-CHF5OHIA.js";
212
+ } from "./chunk-VQNQZCWJ.js";
215
213
  import {
216
214
  disableSSR,
217
215
  enableSSR,
@@ -223,10 +221,10 @@ import {
223
221
  enqueueBatchedSignal,
224
222
  isBatching,
225
223
  signal
226
- } from "./chunk-WZSPOOER.js";
224
+ } from "./chunk-NZIIMDWI.js";
227
225
  import {
228
226
  untracked
229
- } from "./chunk-ZD6OAMTH.js";
227
+ } from "./chunk-K4G4ZQNR.js";
230
228
  import "./chunk-5X6PP2UK.js";
231
229
  export {
232
230
  DynamicComponent,
@@ -343,8 +341,6 @@ export {
343
341
  mask,
344
342
  match,
345
343
  math,
346
- memo,
347
- memoFn,
348
344
  menu,
349
345
  meta,
350
346
  meter,
package/dist/motion.cjs CHANGED
@@ -57,20 +57,29 @@ function transition(element, options = {}) {
57
57
  onLeaveDone
58
58
  } = options;
59
59
  const transitionValue = `${property} ${duration}ms ${easing} ${delay}ms`;
60
+ let activeTimer = null;
61
+ function cancelPending() {
62
+ if (activeTimer !== null) {
63
+ clearTimeout(activeTimer);
64
+ activeTimer = null;
65
+ }
66
+ }
60
67
  function enter() {
61
68
  return new Promise((resolve) => {
69
+ cancelPending();
62
70
  element.style.transition = transitionValue;
63
71
  if (enterClass) element.classList.add(enterClass);
64
72
  if (leaveClass) element.classList.remove(leaveClass);
65
73
  void element.offsetHeight;
66
74
  if (activeClass) element.classList.add(activeClass);
67
75
  const done = () => {
76
+ activeTimer = null;
68
77
  if (enterClass) element.classList.remove(enterClass);
69
78
  onEnterDone?.();
70
79
  resolve();
71
80
  };
72
81
  if (duration > 0) {
73
- setTimeout(done, duration + delay);
82
+ activeTimer = setTimeout(done, duration + delay);
74
83
  } else {
75
84
  done();
76
85
  }
@@ -78,17 +87,19 @@ function transition(element, options = {}) {
78
87
  }
79
88
  function leave() {
80
89
  return new Promise((resolve) => {
90
+ cancelPending();
81
91
  element.style.transition = transitionValue;
82
92
  if (activeClass) element.classList.remove(activeClass);
83
93
  if (leaveClass) element.classList.add(leaveClass);
84
94
  if (enterClass) element.classList.remove(enterClass);
85
95
  const done = () => {
96
+ activeTimer = null;
86
97
  if (leaveClass) element.classList.remove(leaveClass);
87
98
  onLeaveDone?.();
88
99
  resolve();
89
100
  };
90
101
  if (duration > 0) {
91
- setTimeout(done, duration + delay);
102
+ activeTimer = setTimeout(done, duration + delay);
92
103
  } else {
93
104
  done();
94
105
  }
package/dist/motion.d.cts CHANGED
@@ -32,7 +32,7 @@ interface TransitionOptions {
32
32
  *
33
33
  * @example
34
34
  * ```ts
35
- * const box = div({ class: "box", nodes: "Hello" });
35
+ * const box = div("box", "Hello");
36
36
  * const { enter, leave } = transition(box, {
37
37
  * duration: 300,
38
38
  * enterClass: "fade-in",
package/dist/motion.d.ts CHANGED
@@ -32,7 +32,7 @@ interface TransitionOptions {
32
32
  *
33
33
  * @example
34
34
  * ```ts
35
- * const box = div({ class: "box", nodes: "Hello" });
35
+ * const box = div("box", "Hello");
36
36
  * const { enter, leave } = transition(box, {
37
37
  * duration: 300,
38
38
  * enterClass: "fade-in",
package/dist/motion.js CHANGED
@@ -19,9 +19,9 @@ import {
19
19
  stagger,
20
20
  transition,
21
21
  viewTransition
22
- } from "./chunk-KQPDEVVS.js";
23
- import "./chunk-WZSPOOER.js";
24
- import "./chunk-ZD6OAMTH.js";
22
+ } from "./chunk-XUEEGU5O.js";
23
+ import "./chunk-NZIIMDWI.js";
24
+ import "./chunk-K4G4ZQNR.js";
25
25
  import "./chunk-5X6PP2UK.js";
26
26
  export {
27
27
  TransitionGroup,
package/dist/patterns.cjs CHANGED
@@ -22,12 +22,8 @@ var patterns_exports = {};
22
22
  __export(patterns_exports, {
23
23
  RenderProp: () => RenderProp,
24
24
  assertType: () => assertType,
25
- composable: () => composable,
26
25
  compose: () => compose,
27
- createEffect: () => createEffect,
28
26
  createGuard: () => createGuard,
29
- createMemo: () => createMemo,
30
- createSignal: () => createSignal,
31
27
  createSlots: () => createSlots,
32
28
  defineComponent: () => defineComponent,
33
29
  defineSlottedComponent: () => defineSlottedComponent,
@@ -434,7 +430,7 @@ function persisted(key, initial, options = {}) {
434
430
  }
435
431
  const [value, setValue] = signal(restored);
436
432
  let applyingFromStorage = false;
437
- effect(() => {
433
+ const stopPersistEffect = effect(() => {
438
434
  const current = value();
439
435
  if (applyingFromStorage) return;
440
436
  try {
@@ -474,6 +470,7 @@ function persisted(key, initial, options = {}) {
474
470
  window.addEventListener("storage", storageListener);
475
471
  }
476
472
  const dispose = () => {
473
+ stopPersistEffect();
477
474
  if (storageListener && typeof window !== "undefined") {
478
475
  window.removeEventListener("storage", storageListener);
479
476
  storageListener = null;
@@ -491,27 +488,53 @@ function persisted(key, initial, options = {}) {
491
488
  // src/patterns/optimistic.ts
492
489
  function optimistic(initialValue) {
493
490
  const [value, setValue] = signal(initialValue);
494
- const [_pending, setPending] = signal(false);
495
- async function addOptimistic(optimisticValue, asyncAction) {
491
+ const [pending, setPending] = signal(false);
492
+ let inflightCount = 0;
493
+ let version = 0;
494
+ async function update(optimisticValue, asyncAction) {
495
+ const myVersion = ++version;
496
496
  const previousValue = value();
497
497
  setValue(optimisticValue);
498
+ inflightCount++;
498
499
  setPending(true);
499
500
  try {
500
501
  const result = await asyncAction();
501
- setValue(result);
502
+ if (version === myVersion) {
503
+ setValue(result);
504
+ }
502
505
  } catch {
503
- setValue(previousValue);
506
+ if (version === myVersion) {
507
+ setValue(previousValue);
508
+ }
504
509
  } finally {
505
- setPending(false);
510
+ inflightCount--;
511
+ if (inflightCount === 0) setPending(false);
506
512
  }
507
513
  }
508
- return [value, addOptimistic];
514
+ return { value, pending, update };
509
515
  }
510
516
  function optimisticList(initialValue) {
511
517
  const [items, setItems] = signal([...initialValue]);
512
- async function addOptimistic(item, asyncAction) {
518
+ const [pending, setPending] = signal(false);
519
+ let inflightCount = 0;
520
+ let version = 0;
521
+ function begin() {
522
+ const v = ++version;
523
+ inflightCount++;
524
+ setPending(true);
525
+ return v;
526
+ }
527
+ function end(myVersion, revertFn) {
528
+ if (revertFn && version === myVersion) {
529
+ revertFn();
530
+ }
531
+ inflightCount--;
532
+ if (inflightCount === 0) setPending(false);
533
+ }
534
+ async function add(item, asyncAction) {
513
535
  const prev = items();
514
536
  setItems([...prev, item]);
537
+ const myVersion = begin();
515
538
  try {
516
539
  const result = await asyncAction();
517
540
  setItems((current) => {
@@ -521,32 +544,56 @@ function optimisticList(initialValue) {
521
544
  next[idx] = result;
522
545
  return next;
523
546
  }
524
- return [...current.filter((i) => i !== item), result];
547
+ return [...current, result];
525
548
  });
549
+ end(myVersion);
526
550
  } catch {
527
- setItems(prev);
551
+ end(myVersion, () => setItems(prev));
528
552
  }
529
553
  }
530
- async function removeOptimistic(predicate, asyncAction) {
554
+ async function remove(predicate, asyncAction) {
531
555
  const prev = items();
532
556
  setItems(prev.filter((item) => !predicate(item)));
557
+ const myVersion = begin();
533
558
  try {
534
559
  await asyncAction();
560
+ end(myVersion);
535
561
  } catch {
536
- setItems(prev);
562
+ end(myVersion, () => setItems(prev));
537
563
  }
538
564
  }
539
- async function updateOptimistic(predicate, update, asyncAction) {
565
+ async function updateItem(predicate, patch, asyncAction) {
540
566
  const prev = items();
541
- setItems(prev.map((item) => predicate(item) ? { ...item, ...update } : item));
567
+ const patchedRefs = [];
568
+ setItems(
569
+ prev.map((item) => {
570
+ if (predicate(item)) {
571
+ const patched = { ...item, ...patch };
572
+ patchedRefs.push(patched);
573
+ return patched;
574
+ }
575
+ return item;
576
+ })
577
+ );
578
+ const myVersion = begin();
542
579
  try {
543
580
  const result = await asyncAction();
544
- setItems((current) => current.map((item) => predicate(item) ? result : item));
581
+ setItems((current) => current.map((item) => patchedRefs.includes(item) ? result : item));
582
+ end(myVersion);
545
583
  } catch {
546
- setItems(prev);
584
+ end(myVersion, () => setItems(prev));
547
585
  }
548
586
  }
549
- return { items, addOptimistic, removeOptimistic, updateOptimistic };
587
+ return {
588
+ items,
589
+ pending,
590
+ add,
591
+ remove,
592
+ update: updateItem,
593
+ addOptimistic: add,
594
+ removeOptimistic: remove,
595
+ updateOptimistic: updateItem
596
+ };
550
597
  }
551
598
 
552
599
  // src/core/signals/derived.ts
@@ -567,21 +614,37 @@ function derived(getter, options) {
567
614
  cs._v = getter();
568
615
  }, markDirty);
569
616
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
617
+ let evaluating = false;
570
618
  function computedGetter() {
619
+ if (evaluating) {
620
+ throw new Error(
621
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
622
+ );
623
+ }
571
624
  if (trackingSuspended) {
572
625
  if (cs._d) {
573
- cs._d = false;
574
- cs._v = getter();
626
+ evaluating = true;
627
+ try {
628
+ cs._d = false;
629
+ cs._v = getter();
630
+ } finally {
631
+ evaluating = false;
632
+ }
575
633
  }
576
634
  return cs._v;
577
635
  }
578
636
  recordDependency(cs);
579
637
  if (cs._d) {
580
638
  const oldValue = cs._v;
581
- track(() => {
582
- cs._d = false;
583
- cs._v = getter();
584
- }, markDirty);
639
+ evaluating = true;
640
+ try {
641
+ track(() => {
642
+ cs._d = false;
643
+ cs._v = getter();
644
+ }, markDirty);
645
+ } finally {
646
+ evaluating = false;
647
+ }
585
648
  if (hook && oldValue !== cs._v) {
586
649
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
587
650
  }
@@ -646,7 +709,7 @@ function timeline(initial, maxHistory = 100) {
646
709
 
647
710
  // src/patterns/globalStore.ts
648
711
  function globalStore(config) {
649
- const initialState = { ...config.state };
712
+ const initialState = JSON.parse(JSON.stringify(config.state));
650
713
  const [getState, setState] = signal({ ...initialState });
651
714
  const listeners = /* @__PURE__ */ new Set();
652
715
  const middlewares = config.middleware || [];
@@ -699,17 +762,6 @@ function globalStore(config) {
699
762
  return { getState, select, dispatch, subscribe, reset };
700
763
  }
701
764
 
702
- // src/patterns/primitives.ts
703
- function createSignal(value) {
704
- return signal(value);
705
- }
706
- function createMemo(fn) {
707
- return derived(fn);
708
- }
709
- function createEffect(fn) {
710
- return effect(fn);
711
- }
712
-
713
765
  // src/patterns/hoc.ts
714
766
  function withWrapper(WrappedComponent, wrapper) {
715
767
  return (props) => wrapper(WrappedComponent, props);
@@ -722,9 +774,6 @@ function compose(...wrappers) {
722
774
  }
723
775
 
724
776
  // src/patterns/composable.ts
725
- function composable(setup) {
726
- return setup;
727
- }
728
777
  function RenderProp(props) {
729
778
  return props.render(props.data());
730
779
  }
@@ -878,12 +927,8 @@ function createGuard(validator) {
878
927
  0 && (module.exports = {
879
928
  RenderProp,
880
929
  assertType,
881
- composable,
882
930
  compose,
883
- createEffect,
884
931
  createGuard,
885
- createMemo,
886
- createSignal,
887
932
  createSlots,
888
933
  defineComponent,
889
934
  defineSlottedComponent,
@@ -1,4 +1,4 @@
1
- export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DDrwxvJ-.cjs';
1
+ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as compose, d as createGuard, e as createSlots, f as defineComponent, g as defineSlottedComponent, h as defineStrictComponent, v as validateProps, i as validators, w as withBoundary, j as withDefaults, k as withProps, l as withWrapper } from './contracts-ey_Qh8ef.cjs';
2
2
 
3
3
  interface MachineConfig<S extends string, E extends string, C extends Record<string, unknown> = Record<string, unknown>> {
4
4
  initial: S;
@@ -88,31 +88,65 @@ interface PersistOptions<T = unknown> {
88
88
  }
89
89
  declare function persisted<T>(key: string, initial: T, options?: PersistOptions<T>): [() => T, (next: T | ((prev: T) => T)) => void];
90
90
 
91
- interface OptimisticAction<T> {
92
- optimisticValue: T;
93
- revert: T;
94
- }
95
91
  /**
96
92
  * optimistic provides optimistic UI updates that can be reverted on failure.
97
93
  * The value updates immediately, then reverts if the async operation fails.
98
94
  *
99
- * Note: concurrent optimistic updates on the same value are inherently
100
- * ambiguous. If multiple operations are in flight, a failed revert restores
101
- * the value captured before that specific operation started.
95
+ * Returns a named object with `value`, `pending`, and `update`.
96
+ *
97
+ * Concurrent safety: each operation is tagged with a version counter.
98
+ * A failing operation only reverts if no newer operation has started since,
99
+ * preventing stale snapshots from overwriting fresher optimistic state.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * const likes = optimistic(42);
104
+ *
105
+ * button(
106
+ * { on: { click: () => likes.update(likes.value() + 1, () => api.like()) } },
107
+ * () => `${likes.value()} ${likes.pending() ? "(saving…)" : ""}`,
108
+ * );
109
+ * ```
102
110
  */
103
- declare function optimistic<T>(initialValue: T): [() => T, (optimisticValue: T, asyncAction: () => Promise<T>) => Promise<void>];
111
+ declare function optimistic<T>(initialValue: T): {
112
+ value: () => T;
113
+ pending: () => boolean;
114
+ update: (optimisticValue: T, asyncAction: () => Promise<T>) => Promise<void>;
115
+ };
104
116
  /**
105
117
  * optimisticList provides optimistic updates for array state.
106
118
  *
107
119
  * Uses functional updates (setItems(current => ...)) for the success path
108
120
  * to avoid losing changes made by concurrent operations. The failure path
109
- * reverts to a snapshot taken before the operation started.
121
+ * uses a version guard so stale reverts don't overwrite newer state.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const todos = optimisticList<Todo>([]);
126
+ *
127
+ * todos.add(
128
+ * { id: tempId(), text: "New" },
129
+ * async () => api.createTodo("New"),
130
+ * );
131
+ *
132
+ * div([
133
+ * () => todos.pending() ? span("Saving…") : null,
134
+ * each(() => todos.items(), (t) => div(t().text), { key: (t) => t.id }),
135
+ * ]);
136
+ * ```
110
137
  */
111
138
  declare function optimisticList<T>(initialValue: T[]): {
112
139
  items: () => T[];
140
+ pending: () => boolean;
141
+ add: (item: T, asyncAction: () => Promise<T>) => Promise<void>;
142
+ remove: (predicate: (item: T) => boolean, asyncAction: () => Promise<void>) => Promise<void>;
143
+ update: (predicate: (item: T) => boolean, patch: Partial<T>, asyncAction: () => Promise<T>) => Promise<void>;
144
+ /** @deprecated Use `add` instead */
113
145
  addOptimistic: (item: T, asyncAction: () => Promise<T>) => Promise<void>;
146
+ /** @deprecated Use `remove` instead */
114
147
  removeOptimistic: (predicate: (item: T) => boolean, asyncAction: () => Promise<void>) => Promise<void>;
115
- updateOptimistic: (predicate: (item: T) => boolean, update: Partial<T>, asyncAction: () => Promise<T>) => Promise<void>;
148
+ /** @deprecated Use `update` instead */
149
+ updateOptimistic: (predicate: (item: T) => boolean, patch: Partial<T>, asyncAction: () => Promise<T>) => Promise<void>;
116
150
  };
117
151
 
118
152
  interface TimeTravelReturn<T> {
@@ -151,52 +185,4 @@ declare function globalStore<S extends Record<string, unknown>, A extends Record
151
185
  middleware?: Middleware<S>[];
152
186
  }): GlobalStore<S, A>;
153
187
 
154
- /**
155
- * SolidJS-style reactive primitives — standalone APIs that don't require
156
- * being inside a component. These are thin wrappers around the signal system.
157
- */
158
- /**
159
- * Creates a reactive signal. Equivalent to signal but with SolidJS naming.
160
- *
161
- * @param value Initial value
162
- * @returns Tuple [getter, setter]
163
- *
164
- * @example
165
- * ```ts
166
- * const [count, setCount] = createSignal(0);
167
- * console.log(count()); // 0
168
- * setCount(5);
169
- * ```
170
- */
171
- declare function createSignal<T>(value: T): [() => T, (next: T | ((prev: T) => T)) => void];
172
- /**
173
- * Creates a derived/computed reactive value. Equivalent to derived.
174
- *
175
- * @param fn Computation function that reads other signals
176
- * @returns Getter for the computed value
177
- *
178
- * @example
179
- * ```ts
180
- * const [count] = createSignal(5);
181
- * const doubled = createMemo(() => count() * 2);
182
- * console.log(doubled()); // 10
183
- * ```
184
- */
185
- declare function createMemo<T>(fn: () => T): () => T;
186
- /**
187
- * Creates a reactive side effect. Equivalent to effect.
188
- *
189
- * @param fn Effect function that reads reactive signals
190
- * @returns Cleanup/teardown function
191
- *
192
- * @example
193
- * ```ts
194
- * const [count] = createSignal(0);
195
- * const cleanup = createEffect(() => {
196
- * console.log("Count is:", count());
197
- * });
198
- * ```
199
- */
200
- declare function createEffect(fn: () => void): () => void;
201
-
202
- export { type GlobalStore, type MachineConfig, type MachineReturn, type Middleware, type OptimisticAction, type PersistOptions, type Selector, type TimeTravelReturn, createEffect, createMemo, createSignal, globalStore, machine, optimistic, optimisticList, persisted, timeline };
188
+ export { type GlobalStore, type MachineConfig, type MachineReturn, type Middleware, type PersistOptions, type Selector, type TimeTravelReturn, globalStore, machine, optimistic, optimisticList, persisted, timeline };
@@ -1,4 +1,4 @@
1
- export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as composable, d as compose, e as createGuard, f as createSlots, g as defineComponent, h as defineSlottedComponent, i as defineStrictComponent, v as validateProps, j as validators, w as withBoundary, k as withDefaults, l as withProps, m as withWrapper } from './contracts-DDrwxvJ-.js';
1
+ export { C as ComponentProps, P as PropDef, a as PropSchema, R as RenderProp, V as Validator, b as assertType, c as compose, d as createGuard, e as createSlots, f as defineComponent, g as defineSlottedComponent, h as defineStrictComponent, v as validateProps, i as validators, w as withBoundary, j as withDefaults, k as withProps, l as withWrapper } from './contracts-ey_Qh8ef.js';
2
2
 
3
3
  interface MachineConfig<S extends string, E extends string, C extends Record<string, unknown> = Record<string, unknown>> {
4
4
  initial: S;
@@ -88,31 +88,65 @@ interface PersistOptions<T = unknown> {
88
88
  }
89
89
  declare function persisted<T>(key: string, initial: T, options?: PersistOptions<T>): [() => T, (next: T | ((prev: T) => T)) => void];
90
90
 
91
- interface OptimisticAction<T> {
92
- optimisticValue: T;
93
- revert: T;
94
- }
95
91
  /**
96
92
  * optimistic provides optimistic UI updates that can be reverted on failure.
97
93
  * The value updates immediately, then reverts if the async operation fails.
98
94
  *
99
- * Note: concurrent optimistic updates on the same value are inherently
100
- * ambiguous. If multiple operations are in flight, a failed revert restores
101
- * the value captured before that specific operation started.
95
+ * Returns a named object with `value`, `pending`, and `update`.
96
+ *
97
+ * Concurrent safety: each operation is tagged with a version counter.
98
+ * A failing operation only reverts if no newer operation has started since,
99
+ * preventing stale snapshots from overwriting fresher optimistic state.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * const likes = optimistic(42);
104
+ *
105
+ * button(
106
+ * { on: { click: () => likes.update(likes.value() + 1, () => api.like()) } },
107
+ * () => `${likes.value()} ${likes.pending() ? "(saving…)" : ""}`,
108
+ * );
109
+ * ```
102
110
  */
103
- declare function optimistic<T>(initialValue: T): [() => T, (optimisticValue: T, asyncAction: () => Promise<T>) => Promise<void>];
111
+ declare function optimistic<T>(initialValue: T): {
112
+ value: () => T;
113
+ pending: () => boolean;
114
+ update: (optimisticValue: T, asyncAction: () => Promise<T>) => Promise<void>;
115
+ };
104
116
  /**
105
117
  * optimisticList provides optimistic updates for array state.
106
118
  *
107
119
  * Uses functional updates (setItems(current => ...)) for the success path
108
120
  * to avoid losing changes made by concurrent operations. The failure path
109
- * reverts to a snapshot taken before the operation started.
121
+ * uses a version guard so stale reverts don't overwrite newer state.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const todos = optimisticList<Todo>([]);
126
+ *
127
+ * todos.add(
128
+ * { id: tempId(), text: "New" },
129
+ * async () => api.createTodo("New"),
130
+ * );
131
+ *
132
+ * div([
133
+ * () => todos.pending() ? span("Saving…") : null,
134
+ * each(() => todos.items(), (t) => div(t().text), { key: (t) => t.id }),
135
+ * ]);
136
+ * ```
110
137
  */
111
138
  declare function optimisticList<T>(initialValue: T[]): {
112
139
  items: () => T[];
140
+ pending: () => boolean;
141
+ add: (item: T, asyncAction: () => Promise<T>) => Promise<void>;
142
+ remove: (predicate: (item: T) => boolean, asyncAction: () => Promise<void>) => Promise<void>;
143
+ update: (predicate: (item: T) => boolean, patch: Partial<T>, asyncAction: () => Promise<T>) => Promise<void>;
144
+ /** @deprecated Use `add` instead */
113
145
  addOptimistic: (item: T, asyncAction: () => Promise<T>) => Promise<void>;
146
+ /** @deprecated Use `remove` instead */
114
147
  removeOptimistic: (predicate: (item: T) => boolean, asyncAction: () => Promise<void>) => Promise<void>;
115
- updateOptimistic: (predicate: (item: T) => boolean, update: Partial<T>, asyncAction: () => Promise<T>) => Promise<void>;
148
+ /** @deprecated Use `update` instead */
149
+ updateOptimistic: (predicate: (item: T) => boolean, patch: Partial<T>, asyncAction: () => Promise<T>) => Promise<void>;
116
150
  };
117
151
 
118
152
  interface TimeTravelReturn<T> {
@@ -151,52 +185,4 @@ declare function globalStore<S extends Record<string, unknown>, A extends Record
151
185
  middleware?: Middleware<S>[];
152
186
  }): GlobalStore<S, A>;
153
187
 
154
- /**
155
- * SolidJS-style reactive primitives — standalone APIs that don't require
156
- * being inside a component. These are thin wrappers around the signal system.
157
- */
158
- /**
159
- * Creates a reactive signal. Equivalent to signal but with SolidJS naming.
160
- *
161
- * @param value Initial value
162
- * @returns Tuple [getter, setter]
163
- *
164
- * @example
165
- * ```ts
166
- * const [count, setCount] = createSignal(0);
167
- * console.log(count()); // 0
168
- * setCount(5);
169
- * ```
170
- */
171
- declare function createSignal<T>(value: T): [() => T, (next: T | ((prev: T) => T)) => void];
172
- /**
173
- * Creates a derived/computed reactive value. Equivalent to derived.
174
- *
175
- * @param fn Computation function that reads other signals
176
- * @returns Getter for the computed value
177
- *
178
- * @example
179
- * ```ts
180
- * const [count] = createSignal(5);
181
- * const doubled = createMemo(() => count() * 2);
182
- * console.log(doubled()); // 10
183
- * ```
184
- */
185
- declare function createMemo<T>(fn: () => T): () => T;
186
- /**
187
- * Creates a reactive side effect. Equivalent to effect.
188
- *
189
- * @param fn Effect function that reads reactive signals
190
- * @returns Cleanup/teardown function
191
- *
192
- * @example
193
- * ```ts
194
- * const [count] = createSignal(0);
195
- * const cleanup = createEffect(() => {
196
- * console.log("Count is:", count());
197
- * });
198
- * ```
199
- */
200
- declare function createEffect(fn: () => void): () => void;
201
-
202
- export { type GlobalStore, type MachineConfig, type MachineReturn, type Middleware, type OptimisticAction, type PersistOptions, type Selector, type TimeTravelReturn, createEffect, createMemo, createSignal, globalStore, machine, optimistic, optimisticList, persisted, timeline };
188
+ export { type GlobalStore, type MachineConfig, type MachineReturn, type Middleware, type PersistOptions, type Selector, type TimeTravelReturn, globalStore, machine, optimistic, optimisticList, persisted, timeline };