@mizchi/luna 0.1.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/index.js ADDED
@@ -0,0 +1,785 @@
1
+ // Re-export from MoonBit build output (api_js)
2
+ // This file wraps MoonBit APIs to provide SolidJS-compatible interface
3
+
4
+ import {
5
+ // Signal API (internal)
6
+ createSignal as _createSignal,
7
+ get as _get,
8
+ set as _set,
9
+ update as _update,
10
+ peek as _peek,
11
+ subscribe as _subscribe,
12
+ map as _map,
13
+ createMemo as _createMemo,
14
+ combine as _combine,
15
+ effect as _effect,
16
+ batchStart,
17
+ batchEnd,
18
+ runUntracked,
19
+ batch,
20
+ onCleanup,
21
+ createRoot,
22
+ getOwner,
23
+ runWithOwner,
24
+ hasOwner,
25
+ onMount,
26
+ // DOM API
27
+ text,
28
+ textDyn,
29
+ render,
30
+ mount,
31
+ show,
32
+ jsx,
33
+ jsxs,
34
+ Fragment,
35
+ createElement,
36
+ events,
37
+ forEach,
38
+ // Timer utilities
39
+ debounced as _debounced,
40
+ // Route definitions
41
+ routePage,
42
+ routePageTitled,
43
+ routePageFull,
44
+ createRouter,
45
+ routerNavigate,
46
+ routerReplace,
47
+ routerGetPath,
48
+ routerGetMatch,
49
+ routerGetBase,
50
+ // Context API
51
+ createContext,
52
+ provide,
53
+ useContext,
54
+ // Resource API
55
+ createResource as _createResource,
56
+ createDeferred as _createDeferred,
57
+ resourceGet,
58
+ resourcePeek,
59
+ resourceRefetch,
60
+ resourceIsPending,
61
+ resourceIsSuccess,
62
+ resourceIsFailure,
63
+ resourceValue,
64
+ resourceError,
65
+ stateIsPending,
66
+ stateIsSuccess,
67
+ stateIsFailure,
68
+ stateValue,
69
+ stateError,
70
+ // Portal API
71
+ portalToBody,
72
+ portalToSelector,
73
+ portalWithShadow,
74
+ portalToElementWithShadow,
75
+ } from "../../target/js/release/build/platform/js/api/api.js";
76
+
77
+ // ============================================================================
78
+ // SolidJS-compatible Signal API
79
+ // ============================================================================
80
+
81
+ /**
82
+ * Creates a reactive signal (SolidJS-style)
83
+ * @template T
84
+ * @param {T} initialValue
85
+ * @returns {[() => T, (value: T | ((prev: T) => T)) => void]}
86
+ */
87
+ export function createSignal(initialValue) {
88
+ const signal = _createSignal(initialValue);
89
+
90
+ const getter = () => _get(signal);
91
+
92
+ const setter = (valueOrUpdater) => {
93
+ if (typeof valueOrUpdater === "function") {
94
+ _update(signal, valueOrUpdater);
95
+ } else {
96
+ _set(signal, valueOrUpdater);
97
+ }
98
+ };
99
+
100
+ return [getter, setter];
101
+ }
102
+
103
+ /**
104
+ * Creates a reactive effect (SolidJS-style alias)
105
+ */
106
+ export function createEffect(fn) {
107
+ return _effect(fn);
108
+ }
109
+
110
+ /**
111
+ * Creates a memoized computed value (SolidJS-style)
112
+ * @template T
113
+ * @param {() => T} fn
114
+ * @returns {() => T}
115
+ */
116
+ export function createMemo(fn) {
117
+ return _createMemo(fn);
118
+ }
119
+
120
+ /**
121
+ * Runs a function without tracking dependencies (SolidJS-style alias)
122
+ */
123
+ export { runUntracked as untrack };
124
+
125
+ /**
126
+ * Explicit dependency tracking helper (SolidJS-style)
127
+ * Wraps a function to explicitly specify which signals to track
128
+ *
129
+ * @template T
130
+ * @template U
131
+ * @param {(() => T) | Array<() => any>} deps - Signal accessor(s) to track
132
+ * @param {(input: T, prevInput?: T, prevValue?: U) => U} fn - Function to run with dependency values
133
+ * @param {{ defer?: boolean }} [options] - Options (defer: don't run on initial)
134
+ * @returns {(prevValue?: U) => U | undefined}
135
+ */
136
+ export function on(deps, fn, options = {}) {
137
+ const { defer = false } = options;
138
+ const isArray = Array.isArray(deps);
139
+
140
+ let prevInput;
141
+ let prevValue;
142
+ let isFirst = true;
143
+
144
+ return (injectedPrevValue) => {
145
+ // Get current dependency values
146
+ const input = isArray ? deps.map((d) => d()) : deps();
147
+
148
+ // Handle deferred execution
149
+ if (defer && isFirst) {
150
+ isFirst = false;
151
+ prevInput = input;
152
+ return undefined;
153
+ }
154
+
155
+ // Run the function with current and previous values
156
+ const result = fn(input, prevInput, injectedPrevValue ?? prevValue);
157
+
158
+ // Store for next run
159
+ prevInput = input;
160
+ prevValue = result;
161
+ isFirst = false;
162
+
163
+ return result;
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Merge multiple props objects, with later objects taking precedence (SolidJS-style)
169
+ * Event handlers and refs are merged, other props are overwritten
170
+ *
171
+ * @template T
172
+ * @param {...T} sources - Props objects to merge
173
+ * @returns {T}
174
+ */
175
+ export function mergeProps(...sources) {
176
+ const result = {};
177
+
178
+ for (const source of sources) {
179
+ if (!source) continue;
180
+
181
+ for (const key of Object.keys(source)) {
182
+ const value = source[key];
183
+
184
+ // Merge event handlers (on* props)
185
+ if (key.startsWith("on") && typeof value === "function") {
186
+ const existing = result[key];
187
+ if (typeof existing === "function") {
188
+ result[key] = (...args) => {
189
+ existing(...args);
190
+ value(...args);
191
+ };
192
+ } else {
193
+ result[key] = value;
194
+ }
195
+ }
196
+ // Merge ref callbacks
197
+ else if (key === "ref" && typeof value === "function") {
198
+ const existing = result[key];
199
+ if (typeof existing === "function") {
200
+ result[key] = (el) => {
201
+ existing(el);
202
+ value(el);
203
+ };
204
+ } else {
205
+ result[key] = value;
206
+ }
207
+ }
208
+ // Merge class/className
209
+ else if (key === "class" || key === "className") {
210
+ const existing = result[key];
211
+ if (existing) {
212
+ result[key] = `${existing} ${value}`;
213
+ } else {
214
+ result[key] = value;
215
+ }
216
+ }
217
+ // Merge style objects
218
+ else if (key === "style" && typeof value === "object" && typeof result[key] === "object") {
219
+ result[key] = { ...result[key], ...value };
220
+ }
221
+ // Default: overwrite
222
+ else {
223
+ result[key] = value;
224
+ }
225
+ }
226
+ }
227
+
228
+ return result;
229
+ }
230
+
231
+ /**
232
+ * Split props into multiple objects based on key lists (SolidJS-style)
233
+ *
234
+ * @template T
235
+ * @template K
236
+ * @param {T} props - Props object to split
237
+ * @param {...K[]} keys - Arrays of keys to extract
238
+ * @returns {[Pick<T, K>, Omit<T, K>]}
239
+ */
240
+ export function splitProps(props, ...keys) {
241
+ const result = [];
242
+ const remaining = { ...props };
243
+
244
+ for (const keyList of keys) {
245
+ const extracted = {};
246
+ for (const key of keyList) {
247
+ if (key in remaining) {
248
+ extracted[key] = remaining[key];
249
+ delete remaining[key];
250
+ }
251
+ }
252
+ result.push(extracted);
253
+ }
254
+
255
+ result.push(remaining);
256
+ return result;
257
+ }
258
+
259
+ /**
260
+ * Creates a resource for async data (SolidJS-style)
261
+ * @template T
262
+ * @param {(resolve: (v: T) => void, reject: (e: string) => void) => void} fetcher
263
+ * @returns {[ResourceAccessor<T>, { refetch: () => void }]}
264
+ */
265
+ export function createResource(fetcher) {
266
+ const resource = _createResource(fetcher);
267
+
268
+ // Use resourceGet for tracking dependencies, stateValue for actual value
269
+ const accessor = () => stateValue(resourceGet(resource));
270
+ Object.defineProperties(accessor, {
271
+ loading: { get: () => resourceIsPending(resource) },
272
+ error: { get: () => resourceError(resource) },
273
+ state: {
274
+ get: () => {
275
+ if (resourceIsPending(resource)) return "pending";
276
+ if (resourceIsSuccess(resource)) return "ready";
277
+ if (resourceIsFailure(resource)) return "errored";
278
+ return "unresolved";
279
+ },
280
+ },
281
+ latest: { get: () => resourcePeek(resource) },
282
+ });
283
+
284
+ return [accessor, { refetch: () => resourceRefetch(resource) }];
285
+ }
286
+
287
+ /**
288
+ * Creates a deferred resource (SolidJS-style)
289
+ */
290
+ export function createDeferred() {
291
+ const result = _createDeferred();
292
+ const resource = result._0;
293
+ const resolve = result._1;
294
+ const reject = result._2;
295
+
296
+ // Use resourceGet for tracking dependencies, stateValue for actual value
297
+ const accessor = () => stateValue(resourceGet(resource));
298
+ Object.defineProperties(accessor, {
299
+ loading: { get: () => resourceIsPending(resource) },
300
+ error: { get: () => resourceError(resource) },
301
+ });
302
+
303
+ return [accessor, resolve, reject];
304
+ }
305
+
306
+ /**
307
+ * Debounces a signal (returns SolidJS-style signal)
308
+ */
309
+ export function debounced(signal, delayMs) {
310
+ const [getter] = signal;
311
+ const innerSignal = _createSignal(getter());
312
+ const debouncedInner = _debounced(innerSignal, delayMs);
313
+ return [() => _get(debouncedInner), (v) => _set(innerSignal, v)];
314
+ }
315
+
316
+ // ============================================================================
317
+ // SolidJS-compatible Component API
318
+ // ============================================================================
319
+
320
+ /**
321
+ * For component for list rendering (SolidJS-style)
322
+ * @template T
323
+ * @param {{ each: () => T[], fallback?: any, children: (item: T, index: () => number) => any }} props
324
+ * @returns {any}
325
+ */
326
+ export function For(props) {
327
+ const { each, fallback, children } = props;
328
+
329
+ // If each is not provided or is falsy, show fallback
330
+ if (!each) {
331
+ return fallback ?? null;
332
+ }
333
+
334
+ // each should be a getter function
335
+ const getter = typeof each === "function" ? each : () => each;
336
+
337
+ return forEach(getter, (item, index) => {
338
+ // Wrap index in a getter for SolidJS compatibility
339
+ return children(item, () => index);
340
+ });
341
+ }
342
+
343
+ /**
344
+ * Show component for conditional rendering (SolidJS-style)
345
+ * Note: fallback prop is not yet supported (Luna limitation)
346
+ * @template T
347
+ * @param {{ when: T | (() => T), fallback?: any, children: any | ((item: T) => any) }} props
348
+ * @returns {any}
349
+ */
350
+ export function Show(props) {
351
+ const { when, children } = props;
352
+ // TODO: fallback support requires MoonBit-side changes
353
+
354
+ // Convert when to a getter if it's not already
355
+ const condition = typeof when === "function" ? when : () => when;
356
+
357
+ // If children is a function, we need to call it with the truthy value
358
+ const renderChildren =
359
+ typeof children === "function" ? () => children(condition()) : () => children;
360
+
361
+ return show(() => Boolean(condition()), renderChildren);
362
+ }
363
+
364
+ /**
365
+ * Index component for index-based list rendering (SolidJS-style)
366
+ * Unlike For which tracks items by reference, Index tracks by index position
367
+ * Item signals update in place when values change at the same index
368
+ *
369
+ * @template T
370
+ * @param {{ each: () => T[], fallback?: any, children: (item: () => T, index: number) => any }} props
371
+ * @returns {any}
372
+ */
373
+ export function Index(props) {
374
+ const { each, fallback, children } = props;
375
+
376
+ if (!each) {
377
+ return fallback ?? null;
378
+ }
379
+
380
+ const getter = typeof each === "function" ? each : () => each;
381
+ const items = getter();
382
+
383
+ if (items.length === 0 && fallback) {
384
+ return fallback;
385
+ }
386
+
387
+ // Use index_each from MoonBit if available, otherwise simulate with forEach
388
+ // For now, we'll use forEach with index-based tracking
389
+ return forEach(getter, (_item, index) => {
390
+ // Provide item as a getter for reactivity at that index
391
+ const itemGetter = () => getter()[index];
392
+ return children(itemGetter, index);
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Provider component for Context (SolidJS-style)
398
+ * Provides a context value to all descendants
399
+ *
400
+ * @template T
401
+ * @param {{ context: Context<T>, value: T, children: any | (() => any) }} props
402
+ * @returns {any}
403
+ */
404
+ export function Provider(props) {
405
+ const { context, value, children } = props;
406
+
407
+ return provide(context, value, () => {
408
+ return typeof children === "function" ? children() : children;
409
+ });
410
+ }
411
+
412
+ /**
413
+ * Switch component for conditional rendering with multiple branches (SolidJS-style)
414
+ * Renders the first Match that evaluates to true
415
+ *
416
+ * @param {{ fallback?: any, children: any[] }} props
417
+ * @returns {any}
418
+ */
419
+ export function Switch(props) {
420
+ const { fallback, children } = props;
421
+
422
+ // children should be Match components, each with { when, children }
423
+ // Since we don't have compile-time JSX, children is an array of Match results
424
+
425
+ if (!Array.isArray(children)) {
426
+ return fallback ?? null;
427
+ }
428
+
429
+ // Find first truthy match
430
+ for (const child of children) {
431
+ if (child && child.__isMatch && child.when()) {
432
+ return typeof child.children === "function" ? child.children() : child.children;
433
+ }
434
+ }
435
+
436
+ return fallback ?? null;
437
+ }
438
+
439
+ /**
440
+ * Match component for use inside Switch (SolidJS-style)
441
+ *
442
+ * @template T
443
+ * @param {{ when: T | (() => T), children: any | ((item: T) => any) }} props
444
+ * @returns {{ __isMatch: true, when: () => boolean, children: any }}
445
+ */
446
+ export function Match(props) {
447
+ const { when, children } = props;
448
+ const condition = typeof when === "function" ? when : () => when;
449
+
450
+ return {
451
+ __isMatch: true,
452
+ when: () => Boolean(condition()),
453
+ children:
454
+ typeof children === "function"
455
+ ? () => children(condition())
456
+ : children,
457
+ };
458
+ }
459
+
460
+ /**
461
+ * Portal component for rendering outside the component tree (SolidJS-style)
462
+ * Teleports children to a different DOM location
463
+ *
464
+ * @param {{ mount?: Element | string, useShadow?: boolean, children: any | (() => any) }} props
465
+ * @returns {any}
466
+ */
467
+ export function Portal(props) {
468
+ const { mount, useShadow = false, children } = props;
469
+
470
+ // Resolve children
471
+ const resolvedChildren = typeof children === "function" ? [children()] : Array.isArray(children) ? children : [children];
472
+
473
+ // Handle different mount targets
474
+ if (useShadow) {
475
+ if (typeof mount === "string") {
476
+ const target = document.querySelector(mount);
477
+ if (target) {
478
+ return portalToElementWithShadow(target, resolvedChildren);
479
+ }
480
+ } else if (mount) {
481
+ return portalToElementWithShadow(mount, resolvedChildren);
482
+ }
483
+ return portalWithShadow(resolvedChildren);
484
+ }
485
+
486
+ if (typeof mount === "string") {
487
+ return portalToSelector(mount, resolvedChildren);
488
+ }
489
+
490
+ if (mount) {
491
+ // For custom element mount, use selector approach
492
+ return portalToBody(resolvedChildren);
493
+ }
494
+
495
+ return portalToBody(resolvedChildren);
496
+ }
497
+
498
+ // ============================================================================
499
+ // Store API (SolidJS-style)
500
+ // ============================================================================
501
+
502
+ /**
503
+ * Creates a reactive store with nested property tracking (SolidJS-style)
504
+ * @template T
505
+ * @param {T} initialValue - Initial store state
506
+ * @returns {[T, SetStoreFunction<T>]} - [state proxy, setState function]
507
+ *
508
+ * @example
509
+ * const [state, setState] = createStore({ count: 0, user: { name: "John" } });
510
+ *
511
+ * // Read (reactive - tracks dependencies)
512
+ * state.count
513
+ * state.user.name
514
+ *
515
+ * // Update by path
516
+ * setState("count", 1);
517
+ * setState("user", "name", "Jane");
518
+ *
519
+ * // Functional update
520
+ * setState("count", c => c + 1);
521
+ *
522
+ * // Object merge at path
523
+ * setState("user", { name: "Jane", age: 30 });
524
+ */
525
+ export function createStore(initialValue) {
526
+ // Store signals for each path
527
+ const signals = new Map();
528
+ // Deep clone the initial value to avoid mutation issues
529
+ const store = structuredClone(initialValue);
530
+
531
+ // Get or create a signal for a path
532
+ function getSignal(path) {
533
+ const key = path.join(".");
534
+ if (!signals.has(key)) {
535
+ const value = getValueAtPath(store, path);
536
+ signals.set(key, _createSignal(value));
537
+ }
538
+ return signals.get(key);
539
+ }
540
+
541
+ // Get value at a path in an object
542
+ function getValueAtPath(obj, path) {
543
+ let current = obj;
544
+ for (const key of path) {
545
+ if (current == null) return undefined;
546
+ current = current[key];
547
+ }
548
+ return current;
549
+ }
550
+
551
+ // Set value at a path in an object
552
+ function setValueAtPath(obj, path, value) {
553
+ if (path.length === 0) return;
554
+ let current = obj;
555
+ for (let i = 0; i < path.length - 1; i++) {
556
+ const key = path[i];
557
+ if (current[key] == null) {
558
+ // Preserve array vs object based on next key
559
+ const nextKey = path[i + 1];
560
+ current[key] = typeof nextKey === "number" || /^\d+$/.test(nextKey) ? [] : {};
561
+ }
562
+ current = current[key];
563
+ }
564
+ current[path[path.length - 1]] = value;
565
+ }
566
+
567
+ // Notify all signals that might be affected by a path change
568
+ // Uses batch to ensure effects only run once even if multiple signals are updated
569
+ function notifyPath(path) {
570
+ const pathStr = path.join(".");
571
+
572
+ batchStart();
573
+ try {
574
+ for (const [key, signal] of signals.entries()) {
575
+ // Only notify:
576
+ // 1. The exact path that changed
577
+ // 2. Child paths (paths that start with the changed path)
578
+ // Do NOT notify parent paths - they didn't change (object reference is same)
579
+ if (key === pathStr || key.startsWith(pathStr + ".")) {
580
+ const signalPath = key.split(".");
581
+ const newValue = getValueAtPath(store, signalPath);
582
+ _set(signal, newValue);
583
+ }
584
+ }
585
+ } finally {
586
+ batchEnd();
587
+ }
588
+ }
589
+
590
+ // Create a proxy for reactive access
591
+ function createProxy(target, path = []) {
592
+ if (target === null || typeof target !== "object") {
593
+ return target;
594
+ }
595
+
596
+ return new Proxy(target, {
597
+ get(obj, prop) {
598
+ if (typeof prop === "symbol") {
599
+ return obj[prop];
600
+ }
601
+
602
+ const currentPath = [...path, prop];
603
+ const signal = getSignal(currentPath);
604
+ // Track dependency by reading the signal
605
+ _get(signal);
606
+
607
+ const value = obj[prop];
608
+ if (value !== null && typeof value === "object") {
609
+ return createProxy(value, currentPath);
610
+ }
611
+ return value;
612
+ },
613
+
614
+ set(obj, prop, value) {
615
+ // Direct assignment on proxy - update store and notify
616
+ const currentPath = [...path, prop];
617
+ obj[prop] = value;
618
+ notifyPath(currentPath);
619
+ return true;
620
+ },
621
+ });
622
+ }
623
+
624
+ // setState function supporting path-based updates
625
+ function setState(...args) {
626
+ if (args.length === 0) return;
627
+
628
+ // Collect path segments and final value/updater
629
+ const path = [];
630
+ let i = 0;
631
+
632
+ // Collect string path segments
633
+ while (i < args.length - 1 && typeof args[i] === "string") {
634
+ path.push(args[i]);
635
+ i++;
636
+ }
637
+
638
+ const valueOrUpdater = args[i];
639
+
640
+ // If no path, treat as root update
641
+ if (path.length === 0 && typeof valueOrUpdater === "object" && valueOrUpdater !== null) {
642
+ // Merge at root
643
+ Object.assign(store, valueOrUpdater);
644
+ // Notify all signals
645
+ for (const [key, signal] of signals.entries()) {
646
+ const signalPath = key.split(".");
647
+ const newValue = getValueAtPath(store, signalPath);
648
+ _set(signal, newValue);
649
+ }
650
+ return;
651
+ }
652
+
653
+ // Get current value at path
654
+ const currentValue = getValueAtPath(store, path);
655
+
656
+ // Determine new value
657
+ let newValue;
658
+ if (typeof valueOrUpdater === "function") {
659
+ newValue = valueOrUpdater(currentValue);
660
+ } else if (
661
+ Array.isArray(valueOrUpdater)
662
+ ) {
663
+ // Arrays are replaced, not merged
664
+ newValue = valueOrUpdater;
665
+ } else if (
666
+ typeof valueOrUpdater === "object" &&
667
+ valueOrUpdater !== null &&
668
+ typeof currentValue === "object" &&
669
+ currentValue !== null &&
670
+ !Array.isArray(currentValue)
671
+ ) {
672
+ // Merge objects (but not arrays)
673
+ newValue = { ...currentValue, ...valueOrUpdater };
674
+ } else {
675
+ newValue = valueOrUpdater;
676
+ }
677
+
678
+ // Set value in store
679
+ setValueAtPath(store, path, newValue);
680
+
681
+ // Notify affected signals
682
+ notifyPath(path);
683
+ }
684
+
685
+ const proxy = createProxy(store);
686
+ return [proxy, setState];
687
+ }
688
+
689
+ /**
690
+ * Produce helper for immer-style mutations (SolidJS-style)
691
+ * @template T
692
+ * @param {(draft: T) => void} fn - Mutation function
693
+ * @returns {(state: T) => T} - Function that applies mutations to a copy
694
+ */
695
+ export function produce(fn) {
696
+ return (state) => {
697
+ const draft = structuredClone(state);
698
+ fn(draft);
699
+ return draft;
700
+ };
701
+ }
702
+
703
+ /**
704
+ * Reconcile helper for efficient array/object updates (SolidJS-style)
705
+ * @template T
706
+ * @param {T} value - New value to reconcile
707
+ * @returns {(state: T) => T} - Function that returns the new value
708
+ */
709
+ export function reconcile(value) {
710
+ return () => value;
711
+ }
712
+
713
+ // Re-export unchanged APIs
714
+ export {
715
+ // Batch control
716
+ batchStart,
717
+ batchEnd,
718
+ batch,
719
+ // Cleanup
720
+ onCleanup,
721
+ // Owner/Root
722
+ createRoot,
723
+ getOwner,
724
+ runWithOwner,
725
+ hasOwner,
726
+ onMount,
727
+ // DOM API
728
+ text,
729
+ textDyn,
730
+ render,
731
+ mount,
732
+ show,
733
+ jsx,
734
+ jsxs,
735
+ Fragment,
736
+ createElement,
737
+ events,
738
+ forEach,
739
+ // Route definitions
740
+ routePage,
741
+ routePageTitled,
742
+ routePageFull,
743
+ createRouter,
744
+ routerNavigate,
745
+ routerReplace,
746
+ routerGetPath,
747
+ routerGetMatch,
748
+ routerGetBase,
749
+ // Context API
750
+ createContext,
751
+ provide,
752
+ useContext,
753
+ // Resource helpers (for direct access)
754
+ resourceGet,
755
+ resourcePeek,
756
+ resourceRefetch,
757
+ resourceIsPending,
758
+ resourceIsSuccess,
759
+ resourceIsFailure,
760
+ resourceValue,
761
+ resourceError,
762
+ stateIsPending,
763
+ stateIsSuccess,
764
+ stateIsFailure,
765
+ stateValue,
766
+ stateError,
767
+ // Portal API (low-level)
768
+ portalToBody,
769
+ portalToSelector,
770
+ portalWithShadow,
771
+ portalToElementWithShadow,
772
+ };
773
+
774
+ // Legacy API exports (for backwards compatibility during migration)
775
+ export {
776
+ _get as get,
777
+ _set as set,
778
+ _update as update,
779
+ _peek as peek,
780
+ _subscribe as subscribe,
781
+ _map as map,
782
+ _combine as combine,
783
+ _effect as effect,
784
+ runUntracked,
785
+ };