@nexus_js/runtime 0.6.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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Nexus SPA Navigation — Server-Driven DOM Morphing.
3
+ *
4
+ * The core question: "How do you handle state rehydration when the user
5
+ * navigates between routes without a page refresh?"
6
+ *
7
+ * ─── THE STRATEGY: Server-Driven Morphing ─────────────────────────────────
8
+ *
9
+ * We DON'T do client-side routing with a VDOM (React Router style).
10
+ * We DON'T do full page reloads (MPA style).
11
+ * We DO: fetch the new page HTML from the server, morph the current DOM
12
+ * into the new DOM surgically, and preserve island state across the transition.
13
+ *
14
+ * Why morphing instead of innerHTML replacement?
15
+ * innerHTML = destroy all islands + re-create = flicker + lost state
16
+ * morphing = surgical diff + update only what changed = smooth + preserved
17
+ *
18
+ * ─── THE ALGORITHM ────────────────────────────────────────────────────────
19
+ *
20
+ * 1. User clicks <a href="/new-route"> (or calls navigate('/new-route'))
21
+ * 2. Intercept: preventDefault(), push to history
22
+ * 3. Fetch: GET /_nexus/navigate?path=/new-route
23
+ * Server returns: { html, head, islands, props }
24
+ * 4. Diff <head>: update title, meta, canonical (via @nexus_js/head)
25
+ * 5. Morph <body>: walk the DOM tree
26
+ * a. Same node type + same [data-nx-key] → update attributes + children
27
+ * b. Island node ([data-nexus-island]) with same component path:
28
+ * → PRESERVE the island (skip re-hydration, keep its $state)
29
+ * c. New island in new page → mount fresh
30
+ * d. Removed island → destroy() cleanly
31
+ * 6. Update URL, fire 'nexus:navigate' event, restore scroll
32
+ *
33
+ * ─── STATE PRESERVATION RULES ─────────────────────────────────────────────
34
+ *
35
+ * Island state is preserved when ALL of these match:
36
+ * - Same [data-nexus-component] path (same component file)
37
+ * - Same [data-nx-key] if provided (explicit identity)
38
+ * - OR same position in the layout tree (implicit identity)
39
+ *
40
+ * State is reset when:
41
+ * - The component file changes
42
+ * - The user explicitly passes key={Math.random()} (force reset)
43
+ * - The island is in a part of the layout that changed
44
+ *
45
+ * ─── LAYOUT PERSISTENCE ───────────────────────────────────────────────────
46
+ *
47
+ * Shared layouts (+layout.nx) are identified by [data-nx-layout="path"].
48
+ * The morphing algorithm skips islands inside unchanged layouts,
49
+ * achieving the SvelteKit-style "layout persistence" where the
50
+ * sidebar counter doesn't reset when navigating between pages.
51
+ *
52
+ * ─── PREFETCHING ──────────────────────────────────────────────────────────
53
+ *
54
+ * Links get automatic prefetching based on:
55
+ * - data-nx-prefetch="hover" → prefetch on mouseenter (default)
56
+ * - data-nx-prefetch="load" → prefetch on page load
57
+ * - data-nx-prefetch="visible" → prefetch when link enters viewport
58
+ * - data-nx-prefetch="false" → disable prefetch for this link
59
+ */
60
+ export interface NavigateOptions {
61
+ /** Replace current history entry instead of pushing */
62
+ replace?: boolean;
63
+ /** Scroll to top after navigation (default: true) */
64
+ scroll?: boolean;
65
+ /** Override prefetch cache */
66
+ noCache?: boolean;
67
+ }
68
+ export interface NavigationState {
69
+ url: string;
70
+ pending: boolean;
71
+ error: string | null;
72
+ }
73
+ /** Reactive navigation state — use in islands to show loading indicators */
74
+ export declare const navigation: {
75
+ url: {
76
+ value: string;
77
+ };
78
+ pending: {
79
+ value: boolean;
80
+ };
81
+ error: {
82
+ value: string | null;
83
+ };
84
+ };
85
+ /**
86
+ * Programmatic SPA navigation.
87
+ * Equivalent to `<a href="/path">` but callable from island code.
88
+ */
89
+ export declare function navigate(path: string, opts?: NavigateOptions): Promise<void>;
90
+ /**
91
+ * Prefetches a route in the background.
92
+ * Call this in `$effect` or on mouse hover for instant navigation.
93
+ */
94
+ export declare function prefetch(path: string): void;
95
+ /** Bootstrap: call once when the page loads */
96
+ export declare function initNavigation(): void;
97
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAQH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qDAAqD;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,4EAA4E;AAC5E,eAAO,MAAM,UAAU;;;;;;;;;;CAItB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgB3C;AAID,+CAA+C;AAC/C,wBAAgB,cAAc,IAAI,IAAI,CAcrC"}
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Nexus SPA Navigation — Server-Driven DOM Morphing.
3
+ *
4
+ * The core question: "How do you handle state rehydration when the user
5
+ * navigates between routes without a page refresh?"
6
+ *
7
+ * ─── THE STRATEGY: Server-Driven Morphing ─────────────────────────────────
8
+ *
9
+ * We DON'T do client-side routing with a VDOM (React Router style).
10
+ * We DON'T do full page reloads (MPA style).
11
+ * We DO: fetch the new page HTML from the server, morph the current DOM
12
+ * into the new DOM surgically, and preserve island state across the transition.
13
+ *
14
+ * Why morphing instead of innerHTML replacement?
15
+ * innerHTML = destroy all islands + re-create = flicker + lost state
16
+ * morphing = surgical diff + update only what changed = smooth + preserved
17
+ *
18
+ * ─── THE ALGORITHM ────────────────────────────────────────────────────────
19
+ *
20
+ * 1. User clicks <a href="/new-route"> (or calls navigate('/new-route'))
21
+ * 2. Intercept: preventDefault(), push to history
22
+ * 3. Fetch: GET /_nexus/navigate?path=/new-route
23
+ * Server returns: { html, head, islands, props }
24
+ * 4. Diff <head>: update title, meta, canonical (via @nexus_js/head)
25
+ * 5. Morph <body>: walk the DOM tree
26
+ * a. Same node type + same [data-nx-key] → update attributes + children
27
+ * b. Island node ([data-nexus-island]) with same component path:
28
+ * → PRESERVE the island (skip re-hydration, keep its $state)
29
+ * c. New island in new page → mount fresh
30
+ * d. Removed island → destroy() cleanly
31
+ * 6. Update URL, fire 'nexus:navigate' event, restore scroll
32
+ *
33
+ * ─── STATE PRESERVATION RULES ─────────────────────────────────────────────
34
+ *
35
+ * Island state is preserved when ALL of these match:
36
+ * - Same [data-nexus-component] path (same component file)
37
+ * - Same [data-nx-key] if provided (explicit identity)
38
+ * - OR same position in the layout tree (implicit identity)
39
+ *
40
+ * State is reset when:
41
+ * - The component file changes
42
+ * - The user explicitly passes key={Math.random()} (force reset)
43
+ * - The island is in a part of the layout that changed
44
+ *
45
+ * ─── LAYOUT PERSISTENCE ───────────────────────────────────────────────────
46
+ *
47
+ * Shared layouts (+layout.nx) are identified by [data-nx-layout="path"].
48
+ * The morphing algorithm skips islands inside unchanged layouts,
49
+ * achieving the SvelteKit-style "layout persistence" where the
50
+ * sidebar counter doesn't reset when navigating between pages.
51
+ *
52
+ * ─── PREFETCHING ──────────────────────────────────────────────────────────
53
+ *
54
+ * Links get automatic prefetching based on:
55
+ * - data-nx-prefetch="hover" → prefetch on mouseenter (default)
56
+ * - data-nx-prefetch="load" → prefetch on page load
57
+ * - data-nx-prefetch="visible" → prefetch when link enters viewport
58
+ * - data-nx-prefetch="false" → disable prefetch for this link
59
+ */
60
+ import { hydrateAll } from './island.js';
61
+ import { $state } from './runes.js';
62
+ import { snapshotStore, importStore } from './store.js';
63
+ /** Reactive navigation state — use in islands to show loading indicators */
64
+ export const navigation = {
65
+ url: $state(typeof location !== 'undefined' ? location.href : ''),
66
+ pending: $state(false),
67
+ error: $state(null),
68
+ };
69
+ /**
70
+ * Programmatic SPA navigation.
71
+ * Equivalent to `<a href="/path">` but callable from island code.
72
+ */
73
+ export async function navigate(path, opts = {}) {
74
+ await performNavigation(path, opts);
75
+ }
76
+ /**
77
+ * Prefetches a route in the background.
78
+ * Call this in `$effect` or on mouse hover for instant navigation.
79
+ */
80
+ export function prefetch(path) {
81
+ if (typeof document === 'undefined')
82
+ return;
83
+ if (prefetchCache.has(path))
84
+ return;
85
+ // Use <link rel="prefetch"> for network-level prefetch
86
+ const link = document.createElement('link');
87
+ link.rel = 'prefetch';
88
+ link.href = navigateEndpoint(path);
89
+ link.as = 'fetch';
90
+ link.crossOrigin = 'same-origin';
91
+ document.head.appendChild(link);
92
+ // Also start the JSON fetch to warm up our cache
93
+ fetchRoute(path).then((data) => {
94
+ if (data)
95
+ prefetchCache.set(path, data);
96
+ }).catch(() => { });
97
+ }
98
+ // ── Initialization ────────────────────────────────────────────────────────────
99
+ /** Bootstrap: call once when the page loads */
100
+ export function initNavigation() {
101
+ if (typeof document === 'undefined')
102
+ return;
103
+ // Intercept all link clicks
104
+ document.addEventListener('click', handleLinkClick, { passive: false });
105
+ // Handle browser back/forward
106
+ window.addEventListener('popstate', handlePopState);
107
+ // Setup prefetch observers
108
+ setupPrefetchObservers();
109
+ // Store initial page state for back navigation
110
+ history.replaceState({ nx: true, path: location.pathname }, '', location.href);
111
+ }
112
+ // ── Internal navigation engine ────────────────────────────────────────────────
113
+ const NAVIGATE_ENDPOINT = '/_nexus/navigate';
114
+ const prefetchCache = new Map();
115
+ function navigateEndpoint(path) {
116
+ return `${NAVIGATE_ENDPOINT}?path=${encodeURIComponent(path)}`;
117
+ }
118
+ async function performNavigation(path, opts) {
119
+ if (navigation.pending.value)
120
+ return;
121
+ navigation.pending.value = true;
122
+ navigation.error.value = null;
123
+ try {
124
+ // Checkpoint all persisted island state before leaving the current page
125
+ snapshotStore();
126
+ // Check prefetch cache first
127
+ const cached = !opts.noCache ? prefetchCache.get(path) : null;
128
+ const payload = cached ?? (await fetchRoute(path));
129
+ if (!payload) {
130
+ throw new Error(`Failed to fetch route: ${path}`);
131
+ }
132
+ // Update history
133
+ if (opts.replace) {
134
+ history.replaceState({ nx: true, path }, '', path);
135
+ }
136
+ else {
137
+ history.pushState({ nx: true, path }, '', path);
138
+ }
139
+ // Apply the navigation
140
+ await applyNavigation(payload, opts);
141
+ navigation.url.value = location.href;
142
+ // Fire navigation event
143
+ document.dispatchEvent(new CustomEvent('nexus:navigate', { detail: { path, payload } }));
144
+ }
145
+ catch (err) {
146
+ navigation.error.value = err instanceof Error ? err.message : String(err);
147
+ console.error('[Nexus Navigation]', err);
148
+ }
149
+ finally {
150
+ navigation.pending.value = false;
151
+ }
152
+ }
153
+ async function fetchRoute(path) {
154
+ try {
155
+ const res = await fetch(navigateEndpoint(path), {
156
+ headers: {
157
+ 'x-nexus-navigate': '1',
158
+ 'accept': 'application/json',
159
+ },
160
+ });
161
+ if (!res.ok)
162
+ return null;
163
+ return await res.json();
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ }
169
+ async function applyNavigation(payload, opts) {
170
+ // 0. Rehydrate global store from server snapshot (Hydration Miss = 0)
171
+ if (payload.storeSnapshot) {
172
+ importStore(payload.storeSnapshot);
173
+ }
174
+ // 1. Take a snapshot of current islands to preserve state
175
+ const preserved = snapshotIslands();
176
+ // 2. Update <head> (title, meta, etc.)
177
+ applyHeadUpdate(payload.headHTML);
178
+ // 3. Morph <body> — surgical DOM update
179
+ morphBody(payload.html, preserved);
180
+ // 4. Hydrate new islands (skip preserved ones)
181
+ hydrateAll();
182
+ // 5. Restore scroll position
183
+ if (opts.scroll !== false) {
184
+ const hash = location.hash;
185
+ if (hash) {
186
+ const target = document.querySelector(hash);
187
+ if (target) {
188
+ target.scrollIntoView({ behavior: 'smooth' });
189
+ }
190
+ }
191
+ else {
192
+ window.scrollTo({ top: 0, behavior: 'instant' });
193
+ }
194
+ }
195
+ }
196
+ function snapshotIslands() {
197
+ const snapshots = new Map();
198
+ document.querySelectorAll('[data-nexus-island]').forEach((el) => {
199
+ const id = el.getAttribute('data-nexus-island') ?? '';
200
+ const componentPath = el.getAttribute('data-nexus-component') ?? '';
201
+ const key = el.getAttribute('data-nx-key') ?? componentPath;
202
+ snapshots.set(key, { id, componentPath, key, element: el });
203
+ });
204
+ return snapshots;
205
+ }
206
+ // ── DOM Morphing ───────────────────────────────────────────────────────────────
207
+ /**
208
+ * Morphs the current <body> into the new HTML.
209
+ * Preserves islands that survive the navigation.
210
+ *
211
+ * Algorithm: Walk both trees simultaneously.
212
+ * - Same tag + same key → patch attributes, recurse into children
213
+ * - Island with same component → skip (keep existing DOM element)
214
+ * - New node → insert
215
+ * - Removed node → remove (call island.destroy() if it's an island)
216
+ */
217
+ function morphBody(newHTML, preserved) {
218
+ const parser = new DOMParser();
219
+ const newDoc = parser.parseFromString(newHTML, 'text/html');
220
+ const newBody = newDoc.body;
221
+ const oldBody = document.body;
222
+ morphNode(oldBody, newBody, preserved);
223
+ }
224
+ function morphNode(oldNode, newNode, preserved) {
225
+ // Update attributes
226
+ patchAttributes(oldNode, newNode);
227
+ const oldChildren = [...oldNode.childNodes];
228
+ const newChildren = [...newNode.childNodes];
229
+ let oldIdx = 0;
230
+ let newIdx = 0;
231
+ while (newIdx < newChildren.length) {
232
+ const newChild = newChildren[newIdx];
233
+ const oldChild = oldChildren[oldIdx];
234
+ if (!newChild)
235
+ break;
236
+ // Check if this new child is a preserved island
237
+ if (newChild instanceof Element) {
238
+ const newIslandId = newChild.getAttribute('data-nexus-island');
239
+ const newComponentPath = newChild.getAttribute('data-nexus-component');
240
+ const newKey = newChild.getAttribute('data-nx-key') ?? newComponentPath ?? '';
241
+ const snap = preserved.get(newKey);
242
+ if (snap && newComponentPath === snap.componentPath) {
243
+ // PRESERVE: replace new placeholder with existing island element
244
+ if (oldChild !== snap.element) {
245
+ oldNode.insertBefore(snap.element, oldChild ?? null);
246
+ }
247
+ oldIdx++;
248
+ newIdx++;
249
+ continue;
250
+ }
251
+ }
252
+ if (!oldChild) {
253
+ // New node has more children — append
254
+ oldNode.appendChild(newChild.cloneNode(true));
255
+ newIdx++;
256
+ continue;
257
+ }
258
+ if (oldChild.nodeType !== newChild.nodeType) {
259
+ // Different type — replace
260
+ oldNode.replaceChild(newChild.cloneNode(true), oldChild);
261
+ oldIdx++;
262
+ newIdx++;
263
+ continue;
264
+ }
265
+ if (oldChild instanceof Element && newChild instanceof Element) {
266
+ if (oldChild.tagName === newChild.tagName) {
267
+ // Same element — recurse
268
+ morphNode(oldChild, newChild, preserved);
269
+ }
270
+ else {
271
+ // Different tags — replace
272
+ oldNode.replaceChild(newChild.cloneNode(true), oldChild);
273
+ }
274
+ }
275
+ else if (oldChild.nodeType === Node.TEXT_NODE) {
276
+ // Text node — update content
277
+ if (oldChild.textContent !== newChild.textContent) {
278
+ oldChild.textContent = newChild.textContent;
279
+ }
280
+ }
281
+ oldIdx++;
282
+ newIdx++;
283
+ }
284
+ // Remove extra old children
285
+ while (oldIdx < oldChildren.length) {
286
+ const toRemove = oldChildren[oldIdx];
287
+ if (toRemove) {
288
+ // Destroy island if applicable
289
+ const islandId = toRemove instanceof Element
290
+ ? toRemove.getAttribute('data-nexus-island')
291
+ : null;
292
+ if (islandId) {
293
+ toRemove.dispatchEvent(new Event('nexus:destroy'));
294
+ }
295
+ oldNode.removeChild(toRemove);
296
+ }
297
+ oldIdx++;
298
+ }
299
+ }
300
+ function patchAttributes(oldEl, newEl) {
301
+ // Add/update new attributes
302
+ for (const attr of newEl.attributes) {
303
+ if (oldEl.getAttribute(attr.name) !== attr.value) {
304
+ oldEl.setAttribute(attr.name, attr.value);
305
+ }
306
+ }
307
+ // Remove old attributes not in new element
308
+ for (const attr of [...oldEl.attributes]) {
309
+ if (!newEl.hasAttribute(attr.name)) {
310
+ oldEl.removeAttribute(attr.name);
311
+ }
312
+ }
313
+ }
314
+ function applyHeadUpdate(headHTML) {
315
+ if (!headHTML)
316
+ return;
317
+ // Update title
318
+ const titleMatch = /<title>([^<]*)<\/title>/.exec(headHTML);
319
+ if (titleMatch?.[1])
320
+ document.title = titleMatch[1];
321
+ // Remove previous navigation-injected metas (marked with data-nx-nav)
322
+ document.querySelectorAll('[data-nx-nav]').forEach((el) => el.remove());
323
+ // Inject new metas
324
+ const parser = new DOMParser();
325
+ const doc = parser.parseFromString(`<head>${headHTML}</head>`, 'text/html');
326
+ for (const el of doc.head.children) {
327
+ if (el.tagName === 'TITLE')
328
+ continue;
329
+ el.setAttribute('data-nx-nav', '');
330
+ document.head.appendChild(el.cloneNode(true));
331
+ }
332
+ }
333
+ // ── Event handlers ─────────────────────────────────────────────────────────────
334
+ function handleLinkClick(e) {
335
+ const target = e.target.closest('a');
336
+ if (!target)
337
+ return;
338
+ const href = target.getAttribute('href');
339
+ if (!href)
340
+ return;
341
+ // Skip: external, hash-only, download, target="_blank", data-nx-prefetch="false"
342
+ if (href.startsWith('http') ||
343
+ href.startsWith('mailto:') ||
344
+ href.startsWith('tel:') ||
345
+ href === '#' ||
346
+ target.hasAttribute('download') ||
347
+ target.getAttribute('target') === '_blank' ||
348
+ target.getAttribute('data-nx-prefetch') === 'false' ||
349
+ target.getAttribute('data-nx-external') !== null) {
350
+ return;
351
+ }
352
+ e.preventDefault();
353
+ navigate(href).catch(console.error);
354
+ }
355
+ function handlePopState(e) {
356
+ if (e.state?.nx) {
357
+ navigate(location.pathname, { replace: true, noCache: true }).catch(console.error);
358
+ }
359
+ }
360
+ function setupPrefetchObservers() {
361
+ // Hover prefetch (default)
362
+ document.addEventListener('mouseover', (e) => {
363
+ const target = e.target.closest('a[href]');
364
+ if (!target)
365
+ return;
366
+ const href = target.getAttribute('href');
367
+ if (!href || href.startsWith('http') || href.startsWith('#'))
368
+ return;
369
+ const prefetchMode = target.getAttribute('data-nx-prefetch') ?? 'hover';
370
+ if (prefetchMode === 'hover' || prefetchMode === '') {
371
+ prefetch(href);
372
+ }
373
+ }, { passive: true });
374
+ // Viewport prefetch
375
+ const visibleObserver = new IntersectionObserver((entries) => {
376
+ for (const entry of entries) {
377
+ if (!entry.isIntersecting)
378
+ continue;
379
+ const target = entry.target;
380
+ const href = target.getAttribute('href') ?? '';
381
+ if (href && !href.startsWith('http'))
382
+ prefetch(href);
383
+ visibleObserver.unobserve(target);
384
+ }
385
+ }, { rootMargin: '100px' });
386
+ document.querySelectorAll('a[data-nx-prefetch="visible"]').forEach((el) => {
387
+ visibleObserver.observe(el);
388
+ });
389
+ }
390
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAmBxD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;IACtB,KAAK,EAAE,MAAM,CAAgB,IAAI,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,OAAwB,EAAE;IAE1B,MAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAC5C,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO;IAEpC,uDAAuD;IACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC;IACtB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC;IAClB,IAAI,CAAC,WAAW,GAAG,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAEhC,iDAAiD;IACjD,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7B,IAAI,IAAI;YAAE,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,iFAAiF;AAEjF,+CAA+C;AAC/C,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAE5C,4BAA4B;IAC5B,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAExE,8BAA8B;IAC9B,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IAEpD,2BAA2B;IAC3B,sBAAsB,EAAE,CAAC;IAEzB,+CAA+C;IAC/C,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;AACjF,CAAC;AAED,iFAAiF;AAEjF,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAC7C,MAAM,aAAa,GAAG,IAAI,GAAG,EAA6B,CAAC;AAS3D,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,GAAG,iBAAiB,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;AACjE,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,IAAqB;IAClE,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK;QAAE,OAAO;IAErC,UAAU,CAAC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;IAE9B,IAAI,CAAC;QACH,wEAAwE;QACxE,aAAa,EAAE,CAAC;QAEhB,6BAA6B;QAC7B,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAEnD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,iBAAiB;QACjB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAClD,CAAC;QAED,uBAAuB;QACvB,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAErC,UAAU,CAAC,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC;QAErC,wBAAwB;QACxB,QAAQ,CAAC,aAAa,CACpB,IAAI,WAAW,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CACjE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1E,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;IAC3C,CAAC;YAAS,CAAC;QACT,UAAU,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACnC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE;YAC9C,OAAO,EAAE;gBACP,kBAAkB,EAAE,GAAG;gBACvB,QAAQ,EAAE,kBAAkB;aAC7B;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,OAAO,MAAM,GAAG,CAAC,IAAI,EAAuB,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,OAA0B,EAC1B,IAAqB;IAErB,sEAAsE;IACtE,IAAK,OAA0D,CAAC,aAAa,EAAE,CAAC;QAC9E,WAAW,CAAE,OAAyD,CAAC,aAAa,CAAC,CAAC;IACxF,CAAC;IAED,0DAA0D;IAC1D,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IAEpC,uCAAuC;IACvC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAElC,wCAAwC;IACxC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAEnC,+CAA+C;IAC/C,UAAU,EAAE,CAAC;IAEb,6BAA6B;IAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC3B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC;AAWD,SAAS,eAAe;IACtB,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IAEpD,QAAQ,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QAC9D,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,aAAa,GAAG,EAAE,CAAC,YAAY,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC;QACpE,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC;QAE5D,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kFAAkF;AAElF;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,OAAe,EAAE,SAAsC;IACxE,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;IAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE9B,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,SAAS,CAChB,OAAgB,EAChB,OAAgB,EAChB,SAAsC;IAEtC,oBAAoB;IACpB,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAElC,MAAM,WAAW,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,WAAW,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE5C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAErC,IAAI,CAAC,QAAQ;YAAE,MAAM;QAErB,gDAAgD;QAChD,IAAI,QAAQ,YAAY,OAAO,EAAE,CAAC;YAChC,MAAM,WAAW,GAAG,QAAQ,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;YAC/D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;YACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,gBAAgB,IAAI,EAAE,CAAC;YAE9E,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,IAAI,IAAI,gBAAgB,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;gBACpD,iEAAiE;gBACjE,IAAI,QAAQ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;oBAC9B,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;gBACvD,CAAC;gBACD,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,CAAC;gBACT,SAAS;YACX,CAAC;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,sCAAsC;YACtC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,EAAE,CAAC;YACT,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC5C,2BAA2B;YAC3B,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;YACzD,MAAM,EAAE,CAAC;YACT,MAAM,EAAE,CAAC;YACT,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,YAAY,OAAO,IAAI,QAAQ,YAAY,OAAO,EAAE,CAAC;YAC/D,IAAI,QAAQ,CAAC,OAAO,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;gBAC1C,yBAAyB;gBACzB,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC3C,CAAC;iBAAM,CAAC;gBACN,2BAA2B;gBAC3B,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;aAAM,IAAI,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YAChD,6BAA6B;YAC7B,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAClD,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,CAAC;IACX,CAAC;IAED,4BAA4B;IAC5B,OAAO,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,QAAQ,EAAE,CAAC;YACb,+BAA+B;YAC/B,MAAM,QAAQ,GAAG,QAAQ,YAAY,OAAO;gBAC1C,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,mBAAmB,CAAC;gBAC5C,CAAC,CAAC,IAAI,CAAC;YACT,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,EAAE,CAAC;IACX,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAc,EAAE,KAAc;IACrD,4BAA4B;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YACjD,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,2CAA2C;IAC3C,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB;IACvC,IAAI,CAAC,QAAQ;QAAE,OAAO;IAEtB,eAAe;IACf,MAAM,UAAU,GAAG,yBAAyB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC;QAAE,QAAQ,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAEpD,sEAAsE;IACtE,QAAQ,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAExE,mBAAmB;IACnB,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,eAAe,CAAC,SAAS,QAAQ,SAAS,EAAE,WAAW,CAAC,CAAC;IAC5E,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,OAAO,KAAK,OAAO;YAAE,SAAS;QACrC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,kFAAkF;AAElF,SAAS,eAAe,CAAC,CAAa;IACpC,MAAM,MAAM,GAAI,CAAC,CAAC,MAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,iFAAiF;IACjF,IACE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QACvB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QACvB,IAAI,KAAK,GAAG;QACZ,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QAC/B,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,QAAQ;QAC1C,MAAM,CAAC,YAAY,CAAC,kBAAkB,CAAC,KAAK,OAAO;QACnD,MAAM,CAAC,YAAY,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAChD,CAAC;QACD,OAAO;IACT,CAAC;IAED,CAAC,CAAC,cAAc,EAAE,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,cAAc,CAAC,CAAgB;IACtC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC;QAChB,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACrF,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB;IAC7B,2BAA2B;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;QAC3C,MAAM,MAAM,GAAI,CAAC,CAAC,MAAkB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QACrE,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC;QACxE,IAAI,YAAY,KAAK,OAAO,IAAI,YAAY,KAAK,EAAE,EAAE,CAAC;YACpD,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;IACH,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtB,oBAAoB;IACpB,MAAM,eAAe,GAAG,IAAI,oBAAoB,CAAC,CAAC,OAAO,EAAE,EAAE;QAC3D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,cAAc;gBAAE,SAAS;YACpC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAiB,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YACrD,eAAe,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IAE5B,QAAQ,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACxE,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Nexus Optimistic UI — `$optimistic` rune.
3
+ *
4
+ * Updates the UI instantly before the server responds.
5
+ * Rolls back automatically if the server action fails.
6
+ *
7
+ * Usage:
8
+ * const likes = $state(post.likes);
9
+ *
10
+ * function handleLike() {
11
+ * $optimistic(
12
+ * likes,
13
+ * () => likePost(post.id), // Server Action
14
+ * post.likes // rollback value
15
+ * );
16
+ * }
17
+ *
18
+ * What happens:
19
+ * 1. `likes.value` jumps to `likes.value + 1` immediately (UI updates)
20
+ * 2. Server Action runs in background
21
+ * 3a. Success → server value is confirmed (or replaced with server result)
22
+ * 3b. Failure → `likes.value` rolls back to `post.likes`
23
+ */
24
+ export type OptimisticUpdate<T> = {
25
+ /** Current displayed value (may be optimistic) */
26
+ value: T;
27
+ /** True while the server action is in flight */
28
+ pending: boolean;
29
+ /** Error message if the action failed */
30
+ error: string | null;
31
+ };
32
+ /**
33
+ * Wraps a server action with optimistic UI.
34
+ * The signal is updated immediately; rolls back on error.
35
+ *
36
+ * @param signal - A $state signal to update optimistically
37
+ * @param action - Async function that calls the server (returns new value or void)
38
+ * @param rollback - Value to restore if the action fails (defaults to current value)
39
+ */
40
+ export declare function $optimistic<T>(signal: {
41
+ value: T;
42
+ }, action: () => Promise<T | void>, rollback?: T): Promise<void>;
43
+ /**
44
+ * Creates a full optimistic action controller with pending/error state.
45
+ * More powerful than bare $optimistic — gives you loading indicators too.
46
+ *
47
+ * @example
48
+ * const likeAction = createOptimistic(likes, () => likePost(post.id));
49
+ * <button onclick={likeAction.execute} disabled={likeAction.pending}>
50
+ * {likeAction.pending ? '...' : likes.value} likes
51
+ * </button>
52
+ */
53
+ export declare function createOptimistic<T>(signal: {
54
+ value: T;
55
+ }, action: (current: T) => Promise<T | void>, opts?: {
56
+ optimisticValue?: (current: T) => T;
57
+ onError?: (err: unknown) => void;
58
+ }): {
59
+ execute: () => Promise<void>;
60
+ pending: {
61
+ value: boolean;
62
+ };
63
+ error: {
64
+ value: string | null;
65
+ };
66
+ };
67
+ //# sourceMappingURL=optimistic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimistic.d.ts","sourceRoot":"","sources":["../src/optimistic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,kDAAkD;IAClD,KAAK,EAAE,CAAC,CAAC;IACT,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EACjC,MAAM,EAAE;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,EACpB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAC/B,QAAQ,CAAC,EAAE,CAAC,GACX,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,MAAM,EAAE;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,EACpB,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EACzC,IAAI,GAAE;IACJ,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7B,GACL;IACD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACjC,CAgCA"}