@getforma/core 0.1.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.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/chunk-27QSSN4E.cjs +1773 -0
- package/dist/chunk-27QSSN4E.cjs.map +1 -0
- package/dist/chunk-DB2ULMOM.js +1750 -0
- package/dist/chunk-DB2ULMOM.js.map +1 -0
- package/dist/chunk-FPSLC62A.cjs +31 -0
- package/dist/chunk-FPSLC62A.cjs.map +1 -0
- package/dist/chunk-KX5WRZH7.js +27 -0
- package/dist/chunk-KX5WRZH7.js.map +1 -0
- package/dist/formajs-runtime-hardened.global.js +2 -0
- package/dist/formajs-runtime-hardened.global.js.map +1 -0
- package/dist/formajs-runtime.global.js +2 -0
- package/dist/formajs-runtime.global.js.map +1 -0
- package/dist/formajs.global.js +2 -0
- package/dist/formajs.global.js.map +1 -0
- package/dist/index.cjs +1626 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1382 -0
- package/dist/index.d.ts +1382 -0
- package/dist/index.js +1482 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-hardened.cjs +2805 -0
- package/dist/runtime-hardened.cjs.map +1 -0
- package/dist/runtime-hardened.js +2786 -0
- package/dist/runtime-hardened.js.map +1 -0
- package/dist/runtime.cjs +2531 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +86 -0
- package/dist/runtime.d.ts +86 -0
- package/dist/runtime.js +2512 -0
- package/dist/runtime.js.map +1 -0
- package/dist/signal-CfLDwMyg.d.cts +29 -0
- package/dist/signal-CfLDwMyg.d.ts +29 -0
- package/dist/ssr/index.cjs +381 -0
- package/dist/ssr/index.cjs.map +1 -0
- package/dist/ssr/index.d.cts +154 -0
- package/dist/ssr/index.d.ts +154 -0
- package/dist/ssr/index.js +371 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/tc39-compat.cjs +45 -0
- package/dist/tc39-compat.cjs.map +1 -0
- package/dist/tc39-compat.d.cts +50 -0
- package/dist/tc39-compat.d.ts +50 -0
- package/dist/tc39-compat.js +42 -0
- package/dist/tc39-compat.js.map +1 -0
- package/package.json +122 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1382 @@
|
|
|
1
|
+
import { S as SignalGetter } from './signal-CfLDwMyg.cjs';
|
|
2
|
+
export { c as createSignal } from './signal-CfLDwMyg.cjs';
|
|
3
|
+
|
|
4
|
+
declare function createEffect(fn: () => void | (() => void)): () => void;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Forma Reactive - Computed
|
|
8
|
+
*
|
|
9
|
+
* Lazy, cached derived value that participates in the reactive graph.
|
|
10
|
+
* Backed by alien-signals for automatic dependency tracking
|
|
11
|
+
* and cache invalidation.
|
|
12
|
+
*
|
|
13
|
+
* TC39 Signals equivalent: Signal.Computed
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Create a lazy, cached computed value.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const [count, setCount] = createSignal(0);
|
|
20
|
+
* const doubled = createComputed(() => count() * 2);
|
|
21
|
+
* console.log(doubled()); // 0
|
|
22
|
+
* setCount(5);
|
|
23
|
+
* console.log(doubled()); // 10
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
declare function createComputed<T>(fn: () => T): () => T;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Forma Reactive - Memo
|
|
30
|
+
*
|
|
31
|
+
* Alias for createComputed with SolidJS/React-familiar naming.
|
|
32
|
+
* A memoized derived value that only recomputes when its dependencies change.
|
|
33
|
+
*
|
|
34
|
+
* TC39 Signals equivalent: Signal.Computed
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a memoized computed value.
|
|
39
|
+
* Identical to `createComputed` — provided for React/SolidJS familiarity.
|
|
40
|
+
*
|
|
41
|
+
* The computation runs lazily and caches the result. It only recomputes
|
|
42
|
+
* when a signal it reads during computation changes.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const [count, setCount] = createSignal(0);
|
|
46
|
+
* const doubled = createMemo(() => count() * 2);
|
|
47
|
+
* console.log(doubled()); // 0
|
|
48
|
+
* setCount(5);
|
|
49
|
+
* console.log(doubled()); // 10
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare const createMemo: typeof createComputed;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Forma Reactive - Batch
|
|
56
|
+
*
|
|
57
|
+
* Groups multiple signal updates and defers effect execution until the
|
|
58
|
+
* outermost batch completes. Prevents intermediate re-renders.
|
|
59
|
+
* Backed by alien-signals.
|
|
60
|
+
*
|
|
61
|
+
* TC39 Signals: no built-in batch — this is a userland optimization.
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Group multiple signal updates so that effects only run once after all
|
|
65
|
+
* updates have been applied.
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* batch(() => {
|
|
69
|
+
* setA(1);
|
|
70
|
+
* setB(2);
|
|
71
|
+
* // effects that depend on A and B won't run yet
|
|
72
|
+
* });
|
|
73
|
+
* // effects run here, once
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Batches are nestable — only the outermost batch triggers the flush.
|
|
77
|
+
*/
|
|
78
|
+
declare function batch(fn: () => void): void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Forma Reactive - Untrack
|
|
82
|
+
*
|
|
83
|
+
* Read signals without subscribing to them in the reactive graph.
|
|
84
|
+
* Essential for reading values inside effects without creating dependencies.
|
|
85
|
+
*
|
|
86
|
+
* TC39 Signals equivalent: Signal.subtle.untrack()
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Execute a function without tracking signal reads.
|
|
90
|
+
* Any signals read inside `fn` will NOT become dependencies of the
|
|
91
|
+
* surrounding effect or computed.
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* createEffect(() => {
|
|
95
|
+
* const a = count(); // tracked — effect re-runs when count changes
|
|
96
|
+
* const b = untrack(() => other()); // NOT tracked — effect ignores other changes
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare function untrack<T>(fn: () => T): T;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Forma Reactive - Root
|
|
104
|
+
*
|
|
105
|
+
* Explicit reactive ownership scope. All effects created inside a root
|
|
106
|
+
* are automatically disposed when the root is torn down.
|
|
107
|
+
*
|
|
108
|
+
* Replaces the module-level disposalBag pattern in mount.ts.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* Create a reactive root scope.
|
|
112
|
+
*
|
|
113
|
+
* All effects created (via `createEffect`) inside the callback are tracked.
|
|
114
|
+
* The returned `dispose` function tears them all down.
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* const dispose = createRoot(() => {
|
|
118
|
+
* createEffect(() => console.log(count()));
|
|
119
|
+
* createEffect(() => console.log(name()));
|
|
120
|
+
* });
|
|
121
|
+
* // later: dispose() stops both effects
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare function createRoot<T>(fn: (dispose: () => void) => T): T;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Register a cleanup function in the current reactive scope.
|
|
128
|
+
* The cleanup runs before the effect re-executes and on disposal.
|
|
129
|
+
*
|
|
130
|
+
* More composable than returning a cleanup from the effect function,
|
|
131
|
+
* since it can be called from helper functions.
|
|
132
|
+
*
|
|
133
|
+
* ```ts
|
|
134
|
+
* createEffect(() => {
|
|
135
|
+
* const timer = setInterval(tick, 1000);
|
|
136
|
+
* onCleanup(() => clearInterval(timer));
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
declare function onCleanup(fn: () => void): void;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Forma Reactive - On
|
|
144
|
+
*
|
|
145
|
+
* Explicit dependency tracking for effects.
|
|
146
|
+
* Only re-runs when the specified signals change, ignoring all other reads.
|
|
147
|
+
*
|
|
148
|
+
* SolidJS equivalent: on()
|
|
149
|
+
* Vue equivalent: watch() with explicit deps
|
|
150
|
+
* React equivalent: useEffect dependency array
|
|
151
|
+
*/
|
|
152
|
+
/**
|
|
153
|
+
* Create a tracked effect body that only fires when specific dependencies change.
|
|
154
|
+
*
|
|
155
|
+
* Wraps a function so that only the `deps` signals are tracked.
|
|
156
|
+
* All signal reads inside `fn` are untracked (won't cause re-runs).
|
|
157
|
+
*
|
|
158
|
+
* Use with `createEffect`:
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* const [a, setA] = createSignal(1);
|
|
162
|
+
* const [b, setB] = createSignal(2);
|
|
163
|
+
*
|
|
164
|
+
* // Only re-runs when `a` changes, NOT when `b` changes:
|
|
165
|
+
* createEffect(on(a, (value, prev) => {
|
|
166
|
+
* console.log(`a changed: ${prev} → ${value}, b is ${b()}`);
|
|
167
|
+
* }));
|
|
168
|
+
*
|
|
169
|
+
* setA(10); // fires: "a changed: 1 → 10, b is 2"
|
|
170
|
+
* setB(20); // does NOT fire
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* Multiple dependencies:
|
|
174
|
+
* ```ts
|
|
175
|
+
* createEffect(on(
|
|
176
|
+
* () => [a(), b()] as const,
|
|
177
|
+
* ([aVal, bVal], prev) => { ... }
|
|
178
|
+
* ));
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function on<T, U>(deps: () => T, fn: (value: T, prev: T | undefined) => U, options?: {
|
|
182
|
+
defer?: boolean;
|
|
183
|
+
}): () => U | undefined;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Forma Reactive - Ref
|
|
187
|
+
*
|
|
188
|
+
* Mutable container that does NOT trigger reactivity.
|
|
189
|
+
* Use for DOM references, previous values, instance variables —
|
|
190
|
+
* anything that needs to persist across effect re-runs without
|
|
191
|
+
* causing re-execution.
|
|
192
|
+
*
|
|
193
|
+
* React equivalent: useRef
|
|
194
|
+
* SolidJS equivalent: (none — uses plain variables in setup)
|
|
195
|
+
*/
|
|
196
|
+
interface Ref<T> {
|
|
197
|
+
current: T;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Create a mutable ref container.
|
|
201
|
+
*
|
|
202
|
+
* Unlike signals, writing to `.current` does NOT trigger effects.
|
|
203
|
+
* Use when you need a stable reference across reactive scopes.
|
|
204
|
+
*
|
|
205
|
+
* ```ts
|
|
206
|
+
* const timerRef = createRef<number | null>(null);
|
|
207
|
+
*
|
|
208
|
+
* createEffect(() => {
|
|
209
|
+
* timerRef.current = setInterval(tick, 1000);
|
|
210
|
+
* onCleanup(() => clearInterval(timerRef.current!));
|
|
211
|
+
* });
|
|
212
|
+
*
|
|
213
|
+
* // DOM ref pattern:
|
|
214
|
+
* const elRef = createRef<HTMLElement | null>(null);
|
|
215
|
+
* h('div', { ref: (el) => { elRef.current = el; } });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
declare function createRef<T>(initialValue: T): Ref<T>;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Forma Reactive - Reducer
|
|
222
|
+
*
|
|
223
|
+
* State machine pattern — dispatch actions to a pure reducer function.
|
|
224
|
+
* Fine-grained: only the resulting state signal is reactive.
|
|
225
|
+
*
|
|
226
|
+
* React equivalent: useReducer
|
|
227
|
+
* SolidJS equivalent: (none — uses createSignal + helpers)
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
type Dispatch<A> = (action: A) => void;
|
|
231
|
+
/**
|
|
232
|
+
* Create a reducer — predictable state updates via dispatched actions.
|
|
233
|
+
*
|
|
234
|
+
* The reducer function must be pure: `(state, action) => newState`.
|
|
235
|
+
* Returns a [state, dispatch] tuple.
|
|
236
|
+
*
|
|
237
|
+
* ```ts
|
|
238
|
+
* type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };
|
|
239
|
+
*
|
|
240
|
+
* const [count, dispatch] = createReducer(
|
|
241
|
+
* (state: number, action: Action) => {
|
|
242
|
+
* switch (action.type) {
|
|
243
|
+
* case 'increment': return state + 1;
|
|
244
|
+
* case 'decrement': return state - 1;
|
|
245
|
+
* case 'reset': return 0;
|
|
246
|
+
* }
|
|
247
|
+
* },
|
|
248
|
+
* 0,
|
|
249
|
+
* );
|
|
250
|
+
*
|
|
251
|
+
* dispatch({ type: 'increment' }); // count() === 1
|
|
252
|
+
* dispatch({ type: 'increment' }); // count() === 2
|
|
253
|
+
* dispatch({ type: 'reset' }); // count() === 0
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
declare function createReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S): [state: SignalGetter<S>, dispatch: Dispatch<A>];
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Forma Reactive - Resource
|
|
260
|
+
*
|
|
261
|
+
* Async data fetching primitive with reactive loading/error state.
|
|
262
|
+
* Tracks a source signal and refetches when it changes.
|
|
263
|
+
*
|
|
264
|
+
* SolidJS equivalent: createResource
|
|
265
|
+
* React equivalent: use() + Suspense (React 19), or useSWR/react-query
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
interface Resource<T> {
|
|
269
|
+
/** The resolved data (or undefined while loading). */
|
|
270
|
+
(): T | undefined;
|
|
271
|
+
/** True while the fetcher is running. */
|
|
272
|
+
loading: SignalGetter<boolean>;
|
|
273
|
+
/** The error if the fetcher rejected (or undefined). */
|
|
274
|
+
error: SignalGetter<unknown>;
|
|
275
|
+
/** Manually refetch with the current source value. */
|
|
276
|
+
refetch: () => void;
|
|
277
|
+
/** Manually set the data (overrides fetcher result). */
|
|
278
|
+
mutate: (value: T | undefined) => void;
|
|
279
|
+
}
|
|
280
|
+
interface ResourceOptions<T> {
|
|
281
|
+
/** Initial value before first fetch resolves. */
|
|
282
|
+
initialValue?: T;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Create an async resource that fetches data reactively.
|
|
286
|
+
*
|
|
287
|
+
* When `source` changes, the fetcher re-runs automatically.
|
|
288
|
+
* Provides reactive `loading` and `error` signals.
|
|
289
|
+
*
|
|
290
|
+
* ```ts
|
|
291
|
+
* const [userId, setUserId] = createSignal(1);
|
|
292
|
+
*
|
|
293
|
+
* const user = createResource(
|
|
294
|
+
* userId, // source signal
|
|
295
|
+
* (id) => fetch(`/api/users/${id}`).then(r => r.json()), // fetcher
|
|
296
|
+
* );
|
|
297
|
+
*
|
|
298
|
+
* internalEffect(() => {
|
|
299
|
+
* if (user.loading()) console.log('Loading...');
|
|
300
|
+
* else if (user.error()) console.log('Error:', user.error());
|
|
301
|
+
* else console.log('User:', user());
|
|
302
|
+
* });
|
|
303
|
+
*
|
|
304
|
+
* setUserId(2); // automatically refetches
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* Without a source signal (static fetch):
|
|
308
|
+
* ```ts
|
|
309
|
+
* const posts = createResource(
|
|
310
|
+
* () => true, // constant source — fetches once
|
|
311
|
+
* () => fetch('/api/posts').then(r => r.json()),
|
|
312
|
+
* );
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
declare function createResource<T, S = true>(source: SignalGetter<S>, fetcher: (source: S) => Promise<T>, options?: ResourceOptions<T>): Resource<T>;
|
|
316
|
+
|
|
317
|
+
type ErrorHandler = (error: unknown, info?: {
|
|
318
|
+
source?: string;
|
|
319
|
+
}) => void;
|
|
320
|
+
/**
|
|
321
|
+
* Install a global error handler for FormaJS reactive errors.
|
|
322
|
+
* Called when effects, computeds, or event handlers throw.
|
|
323
|
+
*
|
|
324
|
+
* ```ts
|
|
325
|
+
* onError((err, info) => {
|
|
326
|
+
* console.error(`[${info?.source}]`, err);
|
|
327
|
+
* Sentry.captureException(err);
|
|
328
|
+
* });
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
declare function onError(handler: ErrorHandler): void;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Forma DOM - Element
|
|
335
|
+
*
|
|
336
|
+
* Hyperscript-style element factory (`h`) and Fragment helper.
|
|
337
|
+
* Backed by alien-signals via forma/reactive.
|
|
338
|
+
*
|
|
339
|
+
* Supports both HTML and SVG elements with automatic namespace detection.
|
|
340
|
+
* Provides event listener cleanup via AbortController.
|
|
341
|
+
*/
|
|
342
|
+
/**
|
|
343
|
+
* Remove all event listeners previously attached via `h()` on the given element.
|
|
344
|
+
*
|
|
345
|
+
* Calls `AbortController.abort()` for the element, which automatically removes
|
|
346
|
+
* every listener that was registered with its signal. The controller is then
|
|
347
|
+
* deleted so a fresh one is created if the element is reused.
|
|
348
|
+
*/
|
|
349
|
+
declare function cleanup(el: Element): void;
|
|
350
|
+
/**
|
|
351
|
+
* Create a real DOM element with optional props and children.
|
|
352
|
+
*
|
|
353
|
+
* Supports both HTML and SVG elements. SVG tags are detected automatically
|
|
354
|
+
* and created with the correct SVG namespace. Inside a `foreignObject`,
|
|
355
|
+
* children switch back to the HTML namespace.
|
|
356
|
+
*
|
|
357
|
+
* Event listeners are attached with an AbortController signal so they can
|
|
358
|
+
* be removed in bulk via `cleanup(el)`.
|
|
359
|
+
*
|
|
360
|
+
* Hyperscript-style API:
|
|
361
|
+
* ```ts
|
|
362
|
+
* h('div', { class: 'container', onClick: handleClick },
|
|
363
|
+
* h('span', null, 'Hello'),
|
|
364
|
+
* h('span', null, name), // name is a signal getter
|
|
365
|
+
* )
|
|
366
|
+
*
|
|
367
|
+
* h('svg', { viewBox: '0 0 24 24', fill: 'none' },
|
|
368
|
+
* h('path', { d: 'M12 2L2 22h20L12 2z', stroke: 'currentColor' }),
|
|
369
|
+
* )
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
declare function h(tag: string, props?: Record<string, unknown> | null, ...children: unknown[]): HTMLElement;
|
|
373
|
+
/**
|
|
374
|
+
* Create a DocumentFragment from children.
|
|
375
|
+
*
|
|
376
|
+
* ```ts
|
|
377
|
+
* fragment(
|
|
378
|
+
* h('li', null, 'one'),
|
|
379
|
+
* h('li', null, 'two'),
|
|
380
|
+
* )
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
declare function fragment(...children: unknown[]): DocumentFragment;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Forma DOM - Text
|
|
387
|
+
*
|
|
388
|
+
* Creates static or reactive Text nodes.
|
|
389
|
+
* Zero dependencies -- native browser APIs only.
|
|
390
|
+
*/
|
|
391
|
+
/**
|
|
392
|
+
* Create a Text node that is either static or reactively bound.
|
|
393
|
+
*
|
|
394
|
+
* - If `value` is a string, creates a plain Text node.
|
|
395
|
+
* - If `value` is a function (signal getter), creates a Text node and sets up
|
|
396
|
+
* an effect that auto-updates `textContent` whenever the signal changes.
|
|
397
|
+
*
|
|
398
|
+
* @returns The Text node.
|
|
399
|
+
*/
|
|
400
|
+
declare function createText(value: string | (() => string)): Text;
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Forma DOM - Mount
|
|
404
|
+
*
|
|
405
|
+
* Mounts a component function into a DOM container, returning an unmount handle.
|
|
406
|
+
* Uses createRoot() for automatic effect disposal — no global state.
|
|
407
|
+
*
|
|
408
|
+
* When the container has `data-forma-ssr`, uses hydrateIsland() for zero-flash
|
|
409
|
+
* descriptor-based hydration instead of tearing down and rebuilding the DOM.
|
|
410
|
+
*/
|
|
411
|
+
/**
|
|
412
|
+
* Mount a component into a DOM container.
|
|
413
|
+
*
|
|
414
|
+
* All effects created during the component's render are tracked via
|
|
415
|
+
* `createRoot()` and automatically disposed when unmount is called.
|
|
416
|
+
*
|
|
417
|
+
* When `data-forma-ssr` is present on the container, the component runs
|
|
418
|
+
* inside `createRoot` in hydration mode — h() returns descriptors that are
|
|
419
|
+
* walked against SSR DOM to attach handlers and reactive bindings.
|
|
420
|
+
*
|
|
421
|
+
* @param component A function returning an HTMLElement or DocumentFragment.
|
|
422
|
+
* @param container An HTMLElement or a CSS selector string.
|
|
423
|
+
* @returns An unmount function that removes the DOM and disposes all effects.
|
|
424
|
+
*
|
|
425
|
+
* ```ts
|
|
426
|
+
* const unmount = mount(() => h('h1', null, 'Hello'), '#app');
|
|
427
|
+
* // later…
|
|
428
|
+
* unmount();
|
|
429
|
+
* ```
|
|
430
|
+
*/
|
|
431
|
+
declare function mount(component: () => HTMLElement | DocumentFragment, container: HTMLElement | string): () => void;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Forma DOM - List
|
|
435
|
+
*
|
|
436
|
+
* Keyed list reconciliation with Longest Increasing Subsequence (LIS).
|
|
437
|
+
* The LIS tells us the maximum set of DOM nodes that can stay in place;
|
|
438
|
+
* only the remaining nodes need to be moved. This minimises DOM operations
|
|
439
|
+
* to exactly `n - LIS_length` moves — provably optimal.
|
|
440
|
+
*
|
|
441
|
+
* Algorithm used by ivi, Inferno, and (with variations) Solid, Vue 3, Svelte.
|
|
442
|
+
*
|
|
443
|
+
* Uses comment-node markers instead of a wrapper <div>, so the list can be
|
|
444
|
+
* placed inside <table>, <ul>, <select>, etc. without breaking semantics.
|
|
445
|
+
*
|
|
446
|
+
* Total module budget: <2KB minified.
|
|
447
|
+
*/
|
|
448
|
+
interface ReconcileResult<T> {
|
|
449
|
+
nodes: Node[];
|
|
450
|
+
items: T[];
|
|
451
|
+
}
|
|
452
|
+
interface ListTransitionHooks {
|
|
453
|
+
onInsert?: (node: Node) => void;
|
|
454
|
+
onBeforeRemove?: (node: Node, done: () => void) => void;
|
|
455
|
+
}
|
|
456
|
+
interface CreateListOptions {
|
|
457
|
+
/**
|
|
458
|
+
* How to handle same-key items whose object identity changed.
|
|
459
|
+
* - 'none' (default): keep the existing row node for maximum throughput.
|
|
460
|
+
* - 'rerender': re-render changed rows and patch static row DOM in place.
|
|
461
|
+
*/
|
|
462
|
+
updateOnItemChange?: 'none' | 'rerender';
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Find the longest increasing subsequence.
|
|
466
|
+
* Returns indices into the input array.
|
|
467
|
+
* O(n log n) time, O(n) space.
|
|
468
|
+
*/
|
|
469
|
+
declare function longestIncreasingSubsequence(arr: number[]): number[];
|
|
470
|
+
/**
|
|
471
|
+
* Reconcile a DOM parent's children to match a new array of items.
|
|
472
|
+
* Uses keyed reconciliation with LIS for minimum DOM operations.
|
|
473
|
+
*
|
|
474
|
+
* For small lists (< 32 items), uses a flat array scan path that avoids
|
|
475
|
+
* Map/Set hash overhead and provides better cache locality.
|
|
476
|
+
*
|
|
477
|
+
* @param parent - The container DOM element
|
|
478
|
+
* @param oldItems - Previous array (from last reconciliation)
|
|
479
|
+
* @param newItems - New array to render
|
|
480
|
+
* @param oldNodes - Previous DOM nodes array
|
|
481
|
+
* @param keyFn - Extracts a unique key from each item
|
|
482
|
+
* @param createFn - Creates a new DOM node for an item
|
|
483
|
+
* @param updateFn - Updates an existing DOM node with new item data
|
|
484
|
+
* @param beforeNode - Optional boundary marker. When provided, new nodes are
|
|
485
|
+
* inserted before this node instead of appended to parent.
|
|
486
|
+
* This allows the reconciler to operate within a range
|
|
487
|
+
* delimited by comment markers.
|
|
488
|
+
* @returns Object with new nodes array and items array for next call
|
|
489
|
+
*/
|
|
490
|
+
declare function reconcileList<T>(parent: Node, oldItems: T[], newItems: T[], oldNodes: Node[], keyFn: (item: T) => string | number, createFn: (item: T) => Node, updateFn: (node: Node, item: T) => void, beforeNode?: Node | null, hooks?: ListTransitionHooks): ReconcileResult<T>;
|
|
491
|
+
/**
|
|
492
|
+
* Create a reactively-bound list of DOM elements with keyed reconciliation.
|
|
493
|
+
*
|
|
494
|
+
* Returns a `DocumentFragment` containing two comment markers
|
|
495
|
+
* (`<!--forma-list-start-->` and `<!--forma-list-end-->`) that delimit the
|
|
496
|
+
* list's range in the DOM. All managed elements live between these markers.
|
|
497
|
+
*
|
|
498
|
+
* This avoids a wrapper `<div>` which would break `<table>`, `<ul>`,
|
|
499
|
+
* `<select>` semantics and pollute the DOM.
|
|
500
|
+
*
|
|
501
|
+
* When the `items` signal changes, only the minimal set of DOM mutations is
|
|
502
|
+
* performed using the LIS algorithm:
|
|
503
|
+
* - New keys: create elements via `renderFn`
|
|
504
|
+
* - Removed keys: remove elements from DOM
|
|
505
|
+
* - Moved keys: reorder elements using minimum moves (n - LIS)
|
|
506
|
+
* - Same keys: keep row nodes and update index signals
|
|
507
|
+
* (or re-render row content when `updateOnItemChange: 'rerender'`)
|
|
508
|
+
*
|
|
509
|
+
* @param items Signal getter returning the current array of items.
|
|
510
|
+
* @param keyFn Extracts a unique key from each item.
|
|
511
|
+
* @param renderFn Renders a single item. Receives the item and a reactive index getter.
|
|
512
|
+
* @returns A DocumentFragment to insert into the DOM. The fragment includes
|
|
513
|
+
* start/end comment markers and any initial list items.
|
|
514
|
+
*
|
|
515
|
+
* ```ts
|
|
516
|
+
* const frag = createList(
|
|
517
|
+
* todos,
|
|
518
|
+
* (t) => t.id,
|
|
519
|
+
* (todo, index) => h('li', null, () => `${index() + 1}. ${todo.text}`),
|
|
520
|
+
* );
|
|
521
|
+
* container.appendChild(frag);
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
declare function createList<T>(items: () => T[], keyFn: (item: T) => string | number, renderFn: (item: T, index: () => number) => HTMLElement, options?: CreateListOptions): DocumentFragment;
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Forma DOM - Show (Conditional Rendering)
|
|
528
|
+
*
|
|
529
|
+
* Surgically swaps a single DOM node based on a boolean signal.
|
|
530
|
+
* Uses comment markers (like createList) for zero-wrapper rendering.
|
|
531
|
+
* When condition changes, only ONE node is removed and ONE inserted.
|
|
532
|
+
*
|
|
533
|
+
* Each branch is rendered inside createRoot + untrack so that:
|
|
534
|
+
* 1. Inner effects survive when the show effect re-runs (alien-signals
|
|
535
|
+
* would otherwise dispose child effects linked to the parent).
|
|
536
|
+
* 2. Inner effects are explicitly disposed when the branch changes.
|
|
537
|
+
*
|
|
538
|
+
* SolidJS equivalent: <Show when={} fallback={}>
|
|
539
|
+
*/
|
|
540
|
+
/**
|
|
541
|
+
* Conditionally render content based on a reactive boolean.
|
|
542
|
+
*
|
|
543
|
+
* ```ts
|
|
544
|
+
* const [show, setShow] = createSignal(true);
|
|
545
|
+
* const fragment = createShow(
|
|
546
|
+
* show,
|
|
547
|
+
* () => h('div', null, 'Visible!'),
|
|
548
|
+
* () => h('div', null, 'Hidden fallback'),
|
|
549
|
+
* );
|
|
550
|
+
* container.appendChild(fragment);
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* Returns a DocumentFragment with comment markers. The content between
|
|
554
|
+
* markers is swapped reactively when `when()` changes.
|
|
555
|
+
*/
|
|
556
|
+
declare function createShow(when: () => unknown, thenFn: () => Node, elseFn?: () => Node): DocumentFragment;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Forma DOM - Switch (Multi-branch Conditional)
|
|
560
|
+
*
|
|
561
|
+
* Maps a reactive value to one of N render branches.
|
|
562
|
+
* Caches previously rendered nodes for instant swap-back.
|
|
563
|
+
*
|
|
564
|
+
* Each cached branch gets its own reactive root (ownership scope) so that:
|
|
565
|
+
* 1. Inner effects survive when the switch effect re-runs (alien-signals
|
|
566
|
+
* would otherwise dispose child effects linked to the parent).
|
|
567
|
+
* 2. Inner effects are explicitly disposed when the branch is evicted
|
|
568
|
+
* from the cache or the switch itself is torn down.
|
|
569
|
+
*
|
|
570
|
+
* SolidJS equivalent: <Switch><Match when={}>
|
|
571
|
+
*/
|
|
572
|
+
interface SwitchCase<T> {
|
|
573
|
+
match: T;
|
|
574
|
+
render: () => Node;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Multi-branch conditional rendering with node caching.
|
|
578
|
+
*
|
|
579
|
+
* ```ts
|
|
580
|
+
* const [tab, setTab] = createSignal('home');
|
|
581
|
+
* const fragment = createSwitch(tab, [
|
|
582
|
+
* { match: 'home', render: () => h('div', null, 'Home page') },
|
|
583
|
+
* { match: 'about', render: () => h('div', null, 'About page') },
|
|
584
|
+
* { match: 'contact', render: () => h('div', null, 'Contact page') },
|
|
585
|
+
* ], () => h('div', null, '404'));
|
|
586
|
+
* ```
|
|
587
|
+
*
|
|
588
|
+
* Previously rendered branches are cached — switching back to a tab
|
|
589
|
+
* re-inserts the cached node without re-rendering.
|
|
590
|
+
*/
|
|
591
|
+
declare function createSwitch<T>(value: () => T, cases: SwitchCase<T>[], fallback?: () => Node): DocumentFragment;
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Forma DOM - Portal
|
|
595
|
+
*
|
|
596
|
+
* Renders children into a different DOM container than the parent.
|
|
597
|
+
* Useful for modals, tooltips, dropdowns that need to escape overflow.
|
|
598
|
+
*
|
|
599
|
+
* SolidJS equivalent: <Portal mount={}>
|
|
600
|
+
*/
|
|
601
|
+
/**
|
|
602
|
+
* Render content into an external DOM container.
|
|
603
|
+
*
|
|
604
|
+
* ```ts
|
|
605
|
+
* const modal = createPortal(
|
|
606
|
+
* () => h('div', { class: 'modal' }, 'Modal content'),
|
|
607
|
+
* document.body,
|
|
608
|
+
* );
|
|
609
|
+
* ```
|
|
610
|
+
*
|
|
611
|
+
* Returns a comment node placeholder. The actual content is rendered
|
|
612
|
+
* into the target container. Cleanup removes content from target.
|
|
613
|
+
*/
|
|
614
|
+
declare function createPortal(children: () => Node, target?: Element | string): Comment;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Forma DOM - Error Boundary
|
|
618
|
+
*
|
|
619
|
+
* Catches errors during rendering and shows a fallback UI.
|
|
620
|
+
* Provides a retry function to re-attempt the original render.
|
|
621
|
+
*
|
|
622
|
+
* SolidJS equivalent: <ErrorBoundary fallback={}>
|
|
623
|
+
*/
|
|
624
|
+
/**
|
|
625
|
+
* Wrap a render function with error recovery.
|
|
626
|
+
*
|
|
627
|
+
* ```ts
|
|
628
|
+
* const fragment = createErrorBoundary(
|
|
629
|
+
* () => h('div', null, riskyComponent()),
|
|
630
|
+
* (error, retry) => h('div', { class: 'error' },
|
|
631
|
+
* h('p', null, `Error: ${error.message}`),
|
|
632
|
+
* h('button', { onClick: retry }, 'Retry'),
|
|
633
|
+
* ),
|
|
634
|
+
* );
|
|
635
|
+
* ```
|
|
636
|
+
*
|
|
637
|
+
* If `tryFn` throws, `catchFn` is called with the error and a retry function.
|
|
638
|
+
* Calling `retry()` re-runs `tryFn`.
|
|
639
|
+
*/
|
|
640
|
+
declare function createErrorBoundary(tryFn: () => Node, catchFn: (error: Error, retry: () => void) => Node): DocumentFragment;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Forma DOM - Suspense
|
|
644
|
+
*
|
|
645
|
+
* A Suspense boundary that shows fallback content while async resources
|
|
646
|
+
* inside the children function are loading. Uses comment markers
|
|
647
|
+
* (like createShow, createList) for zero-wrapper rendering.
|
|
648
|
+
*
|
|
649
|
+
* When all resources resolve, the fallback is swapped for the real content.
|
|
650
|
+
* If a resource errors, the fallback remains (pair with createErrorBoundary
|
|
651
|
+
* for error handling).
|
|
652
|
+
*
|
|
653
|
+
* SolidJS equivalent: <Suspense fallback={}>
|
|
654
|
+
*/
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Create a Suspense boundary that shows fallback content while async resources
|
|
658
|
+
* inside the children function are loading.
|
|
659
|
+
*
|
|
660
|
+
* Uses comment markers: `<!--forma-suspense-->` / `<!--/forma-suspense-->`
|
|
661
|
+
*
|
|
662
|
+
* When all resources resolve, the fallback is swapped for the real content.
|
|
663
|
+
* If a resource errors, the fallback remains (pair with createErrorBoundary
|
|
664
|
+
* for errors).
|
|
665
|
+
*
|
|
666
|
+
* ```ts
|
|
667
|
+
* const frag = createSuspense(
|
|
668
|
+
* () => h('div', null, 'Loading...'),
|
|
669
|
+
* () => {
|
|
670
|
+
* const data = createResource(source, fetcher);
|
|
671
|
+
* return h('div', null, () => data()?.name ?? '');
|
|
672
|
+
* },
|
|
673
|
+
* );
|
|
674
|
+
* container.appendChild(frag);
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
declare function createSuspense(fallback: () => Node, children: () => Node): DocumentFragment;
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Hydrate an SSR island in-place. Runs the component in hydration mode so
|
|
681
|
+
* h() returns descriptors, then walks the descriptor tree against the SSR DOM
|
|
682
|
+
* to attach event handlers and reactive bindings. No DOM elements are created.
|
|
683
|
+
*
|
|
684
|
+
* The component function MUST be called inside a reactive root (createRoot)
|
|
685
|
+
* so that effects created during adoption are properly tracked.
|
|
686
|
+
*
|
|
687
|
+
* Returns the active root element — usually `target`, but may be a replacement
|
|
688
|
+
* element when the CSR fallback fires (empty island shell replaced by the
|
|
689
|
+
* component's own root element).
|
|
690
|
+
*
|
|
691
|
+
* @param component A function that builds the UI (calls h(), createShow, etc.)
|
|
692
|
+
* @param target The container element with `data-forma-ssr` attribute
|
|
693
|
+
*/
|
|
694
|
+
declare function hydrateIsland(component: () => unknown, target: Element): Element;
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Forma DOM - Island Activation
|
|
698
|
+
*
|
|
699
|
+
* Discovers SSR-rendered islands via [data-forma-island] attributes,
|
|
700
|
+
* loads props (inline or script_tag), and hydrates each island inside
|
|
701
|
+
* an independent createRoot scope with try/catch error isolation.
|
|
702
|
+
*/
|
|
703
|
+
/** Function that creates a component's DOM tree (same for CSR and hydration). */
|
|
704
|
+
type IslandHydrateFn = (props: Record<string, unknown> | null) => unknown;
|
|
705
|
+
/**
|
|
706
|
+
* Discover and activate all SSR-rendered islands on the page.
|
|
707
|
+
*
|
|
708
|
+
* Each island is activated inside its own createRoot scope with try/catch
|
|
709
|
+
* isolation — a broken island never takes down its siblings.
|
|
710
|
+
*
|
|
711
|
+
* @param registry Map of component names to hydration functions.
|
|
712
|
+
*/
|
|
713
|
+
declare function activateIslands(registry: Record<string, IslandHydrateFn>): void;
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Forma Component - Define
|
|
717
|
+
*
|
|
718
|
+
* Component definition system where the setup function runs ONCE.
|
|
719
|
+
* Reactivity comes from signals, not re-rendering.
|
|
720
|
+
* Backed by alien-signals via forma/reactive.
|
|
721
|
+
*/
|
|
722
|
+
type CleanupFn = () => void;
|
|
723
|
+
type SetupFn = () => HTMLElement | DocumentFragment;
|
|
724
|
+
interface ComponentDef {
|
|
725
|
+
setup: SetupFn;
|
|
726
|
+
name?: string;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Register a callback that runs after the component's setup completes.
|
|
730
|
+
* If the callback returns a function, that function is called on unmount.
|
|
731
|
+
* Must be called inside a setup function.
|
|
732
|
+
*/
|
|
733
|
+
declare function onMount(fn: () => void | CleanupFn): void;
|
|
734
|
+
/**
|
|
735
|
+
* Register a callback that runs when the component is disposed.
|
|
736
|
+
* Must be called inside a setup function.
|
|
737
|
+
*/
|
|
738
|
+
declare function onUnmount(fn: () => void): void;
|
|
739
|
+
/**
|
|
740
|
+
* Define a component from a setup function or definition object.
|
|
741
|
+
* Returns a factory function that, when called, produces a DOM element
|
|
742
|
+
* with attached lifecycle and disposal logic.
|
|
743
|
+
*
|
|
744
|
+
* The setup function runs ONCE per factory call. Reactivity is driven
|
|
745
|
+
* by signals, not by re-running setup.
|
|
746
|
+
*/
|
|
747
|
+
declare function defineComponent(setupOrDef: SetupFn | ComponentDef): () => HTMLElement | DocumentFragment;
|
|
748
|
+
/**
|
|
749
|
+
* Dispose a component that was created via defineComponent.
|
|
750
|
+
* This runs all onUnmount callbacks and disposes all tracked effects.
|
|
751
|
+
*/
|
|
752
|
+
declare function disposeComponent(dom: HTMLElement | DocumentFragment): void;
|
|
753
|
+
/**
|
|
754
|
+
* Track an effect disposal within the current component's lifecycle.
|
|
755
|
+
* Call this inside a setup function to ensure effects are cleaned up
|
|
756
|
+
* when the component is disposed.
|
|
757
|
+
*/
|
|
758
|
+
declare function trackDisposer(dispose: () => void): void;
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Forma Component - Context
|
|
762
|
+
*
|
|
763
|
+
* Dependency injection via stack-based context.
|
|
764
|
+
* Simpler than React's Provider component tree: provide() pushes a value,
|
|
765
|
+
* inject() reads the top, component teardown pops automatically.
|
|
766
|
+
* Zero dependencies -- native browser APIs only.
|
|
767
|
+
*/
|
|
768
|
+
interface Context<T> {
|
|
769
|
+
/** Unique identifier for this context. */
|
|
770
|
+
readonly id: symbol;
|
|
771
|
+
/** Value returned when no provider is active. */
|
|
772
|
+
readonly defaultValue: T;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Create a new context with a default value.
|
|
776
|
+
*
|
|
777
|
+
* ```ts
|
|
778
|
+
* const ThemeCtx = createContext('light');
|
|
779
|
+
* ```
|
|
780
|
+
*/
|
|
781
|
+
declare function createContext<T>(defaultValue: T): Context<T>;
|
|
782
|
+
/**
|
|
783
|
+
* Provide a value for a context.
|
|
784
|
+
* The value is pushed onto the context's stack and will be returned by
|
|
785
|
+
* inject() until it is removed via unprovide() or overridden by a nested provide().
|
|
786
|
+
*
|
|
787
|
+
* ```ts
|
|
788
|
+
* provide(ThemeCtx, 'dark');
|
|
789
|
+
* ```
|
|
790
|
+
*/
|
|
791
|
+
declare function provide<T>(ctx: Context<T>, value: T): void;
|
|
792
|
+
/**
|
|
793
|
+
* Read the current value of a context.
|
|
794
|
+
* Returns the most recently provided value, or the default if none was provided.
|
|
795
|
+
*
|
|
796
|
+
* ```ts
|
|
797
|
+
* const theme = inject(ThemeCtx); // 'dark' if provided, else 'light'
|
|
798
|
+
* ```
|
|
799
|
+
*/
|
|
800
|
+
declare function inject<T>(ctx: Context<T>): T;
|
|
801
|
+
/**
|
|
802
|
+
* Remove the most recent provided value for a context.
|
|
803
|
+
* Used during component teardown to restore the previous scope.
|
|
804
|
+
*
|
|
805
|
+
* ```ts
|
|
806
|
+
* unprovide(ThemeCtx);
|
|
807
|
+
* ```
|
|
808
|
+
*/
|
|
809
|
+
declare function unprovide<T>(ctx: Context<T>): void;
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Forma State - Store
|
|
813
|
+
*
|
|
814
|
+
* Deep reactive store with path-based signal granularity.
|
|
815
|
+
* Every property path (e.g. `user.name`, `items.0.done`) gets its own signal,
|
|
816
|
+
* so only effects that read a specific path are notified when it changes.
|
|
817
|
+
*
|
|
818
|
+
* Inspired by SolidJS's createStore and Vue's reactive(), with:
|
|
819
|
+
* - Lazy proxy wrapping (child objects proxied on first access)
|
|
820
|
+
* - Structural sharing (replacing an object invalidates child signals)
|
|
821
|
+
* - Array mutation batching (push/pop/splice/sort etc. batch their signals)
|
|
822
|
+
*
|
|
823
|
+
* Backed by alien-signals via forma/reactive.
|
|
824
|
+
*/
|
|
825
|
+
type StoreSetter<T extends object> = (partial: Partial<T> | ((prev: T) => Partial<T>)) => void;
|
|
826
|
+
/**
|
|
827
|
+
* Create a deep reactive store.
|
|
828
|
+
*
|
|
829
|
+
* Returns a tuple of `[getter proxy, setter function]`.
|
|
830
|
+
* The getter proxy tracks reads at every property path via dedicated signals.
|
|
831
|
+
* Setting a property (either via `setState()` or direct mutation like
|
|
832
|
+
* `state.user.name = 'Bob'`) only notifies effects that actually read that
|
|
833
|
+
* specific path.
|
|
834
|
+
*
|
|
835
|
+
* ```ts
|
|
836
|
+
* const [state, setState] = createStore({
|
|
837
|
+
* count: 0,
|
|
838
|
+
* user: { name: 'Alice', age: 30 },
|
|
839
|
+
* items: [{ text: 'Buy milk', done: false }],
|
|
840
|
+
* });
|
|
841
|
+
*
|
|
842
|
+
* // Fine-grained reads
|
|
843
|
+
* state.user.name; // tracked at path "user.name"
|
|
844
|
+
* state.items[0].done; // tracked at path "items.0.done"
|
|
845
|
+
*
|
|
846
|
+
* // Setter API (batched)
|
|
847
|
+
* setState({ count: 1 });
|
|
848
|
+
* setState(prev => ({ count: prev.count + 1 }));
|
|
849
|
+
*
|
|
850
|
+
* // Direct mutation (via Proxy set trap)
|
|
851
|
+
* state.user.name = 'Bob'; // only "user.name" subscribers notified
|
|
852
|
+
* state.items[0].done = true;
|
|
853
|
+
*
|
|
854
|
+
* // Array mutations
|
|
855
|
+
* state.items.push({ text: 'Walk dog', done: false });
|
|
856
|
+
* ```
|
|
857
|
+
*/
|
|
858
|
+
declare function createStore<T extends object>(initial: T): [get: T, set: StoreSetter<T>];
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Forma State - History
|
|
862
|
+
*
|
|
863
|
+
* Undo/redo for any signal. Tracks changes and maintains undo/redo stacks
|
|
864
|
+
* with reactive canUndo/canRedo signals.
|
|
865
|
+
* Zero dependencies -- native browser APIs only.
|
|
866
|
+
*/
|
|
867
|
+
interface HistoryControls<T> {
|
|
868
|
+
/** Undo the last change, restoring the previous value. */
|
|
869
|
+
undo: () => void;
|
|
870
|
+
/** Redo the last undone change. */
|
|
871
|
+
redo: () => void;
|
|
872
|
+
/** Reactive getter: true if undo is available. */
|
|
873
|
+
canUndo: () => boolean;
|
|
874
|
+
/** Reactive getter: true if redo is available. */
|
|
875
|
+
canRedo: () => boolean;
|
|
876
|
+
/** Reactive getter: the full history stack (past + current + future). */
|
|
877
|
+
history: () => T[];
|
|
878
|
+
/** Reactive getter: current position in the history stack (0-based). */
|
|
879
|
+
cursor: () => number;
|
|
880
|
+
/** Clear all history, keeping only the current value. */
|
|
881
|
+
clear: () => void;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Create undo/redo history tracking for a signal.
|
|
885
|
+
*
|
|
886
|
+
* ```ts
|
|
887
|
+
* const [count, setCount] = createSignal(0);
|
|
888
|
+
* const h = createHistory([count, setCount], { maxLength: 50 });
|
|
889
|
+
*
|
|
890
|
+
* setCount(1);
|
|
891
|
+
* setCount(2);
|
|
892
|
+
* h.undo(); // count() === 1
|
|
893
|
+
* h.redo(); // count() === 2
|
|
894
|
+
* h.canUndo(); // true (reactive)
|
|
895
|
+
* ```
|
|
896
|
+
*/
|
|
897
|
+
declare function createHistory<T>(source: [get: () => T, set: (v: T) => void], options?: {
|
|
898
|
+
maxLength?: number;
|
|
899
|
+
}): HistoryControls<T>;
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Forma State - Persist
|
|
903
|
+
*
|
|
904
|
+
* Auto-persist a signal to localStorage (or any Storage-compatible backend).
|
|
905
|
+
* Reads stored value on creation; writes on every signal change.
|
|
906
|
+
* Zero dependencies -- native browser APIs only.
|
|
907
|
+
*/
|
|
908
|
+
interface PersistOptions<T> {
|
|
909
|
+
/** Storage backend. Defaults to localStorage. */
|
|
910
|
+
storage?: Storage;
|
|
911
|
+
/** Custom serializer. Defaults to JSON.stringify. */
|
|
912
|
+
serialize?: (v: T) => string;
|
|
913
|
+
/** Custom deserializer. Defaults to JSON.parse. */
|
|
914
|
+
deserialize?: (s: string) => T;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Persist a signal's value to storage.
|
|
918
|
+
*
|
|
919
|
+
* On creation, reads the stored value (if any) and hydrates the signal.
|
|
920
|
+
* Then sets up an effect to write to storage whenever the signal changes.
|
|
921
|
+
*
|
|
922
|
+
* ```ts
|
|
923
|
+
* const [theme, setTheme] = createSignal('light');
|
|
924
|
+
* persist([theme, setTheme], 'app:theme');
|
|
925
|
+
*
|
|
926
|
+
* setTheme('dark'); // auto-saved to localStorage under key 'app:theme'
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
declare function persist<T>(source: [get: () => T, set: (v: T) => void], key: string, options?: PersistOptions<T>): void;
|
|
930
|
+
|
|
931
|
+
interface EventBus<T extends Record<string, any>> {
|
|
932
|
+
on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void;
|
|
933
|
+
once<K extends keyof T>(event: K, handler: (payload: T[K]) => void): () => void;
|
|
934
|
+
emit<K extends keyof T>(event: K, payload: T[K]): void;
|
|
935
|
+
off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void;
|
|
936
|
+
clear(): void;
|
|
937
|
+
}
|
|
938
|
+
declare function createBus<T extends Record<string, any> = Record<string, any>>(): EventBus<T>;
|
|
939
|
+
|
|
940
|
+
declare function delegate<K extends keyof HTMLElementEventMap>(container: HTMLElement | Document, selector: string, event: K, handler: (e: HTMLElementEventMap[K], matchedEl: HTMLElement) => void, options?: AddEventListenerOptions): () => void;
|
|
941
|
+
|
|
942
|
+
type KeyCombo = string;
|
|
943
|
+
interface KeyOptions {
|
|
944
|
+
target?: EventTarget;
|
|
945
|
+
preventDefault?: boolean;
|
|
946
|
+
}
|
|
947
|
+
declare function onKey(combo: KeyCombo, handler: (e: KeyboardEvent) => void, options?: KeyOptions): () => void;
|
|
948
|
+
|
|
949
|
+
declare function $<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T | null;
|
|
950
|
+
declare function $$<T extends HTMLElement = HTMLElement>(selector: string, parent?: ParentNode): T[];
|
|
951
|
+
|
|
952
|
+
declare function addClass(el: HTMLElement, ...classes: string[]): void;
|
|
953
|
+
declare function removeClass(el: HTMLElement, ...classes: string[]): void;
|
|
954
|
+
declare function toggleClass(el: HTMLElement, className: string, force?: boolean): boolean;
|
|
955
|
+
declare function setStyle(el: HTMLElement, styles: Partial<CSSStyleDeclaration>): void;
|
|
956
|
+
declare function setAttr(el: HTMLElement, attrs: Record<string, string | boolean | null>): void;
|
|
957
|
+
declare function setText(el: HTMLElement, text: string): void;
|
|
958
|
+
declare function setHTML(el: HTMLElement, html: string): void;
|
|
959
|
+
|
|
960
|
+
declare function closest<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector: string): T | null;
|
|
961
|
+
declare function children<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[];
|
|
962
|
+
declare function siblings<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T[];
|
|
963
|
+
declare function parent<T extends HTMLElement = HTMLElement>(el: HTMLElement): T | null;
|
|
964
|
+
declare function nextSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null;
|
|
965
|
+
declare function prevSibling<T extends HTMLElement = HTMLElement>(el: HTMLElement, selector?: string): T | null;
|
|
966
|
+
|
|
967
|
+
declare function onResize(el: HTMLElement, handler: (entry: ResizeObserverEntry) => void): () => void;
|
|
968
|
+
declare function onIntersect(el: HTMLElement, handler: (entry: IntersectionObserverEntry) => void, options?: IntersectionObserverInit): () => void;
|
|
969
|
+
declare function onMutation(el: HTMLElement, handler: (mutations: MutationRecord[]) => void, options?: MutationObserverInit): () => void;
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Forma Storage - Local
|
|
973
|
+
*
|
|
974
|
+
* Typed localStorage wrapper with graceful error handling.
|
|
975
|
+
* Zero dependencies — native browser APIs only.
|
|
976
|
+
*/
|
|
977
|
+
interface TypedStorage$1<T> {
|
|
978
|
+
get(): T | null;
|
|
979
|
+
set(value: T): void;
|
|
980
|
+
remove(): void;
|
|
981
|
+
key: string;
|
|
982
|
+
}
|
|
983
|
+
interface StorageOptions$1<T> {
|
|
984
|
+
serialize?: (v: T) => string;
|
|
985
|
+
deserialize?: (s: string) => T;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Create a typed localStorage wrapper for the given key.
|
|
989
|
+
*
|
|
990
|
+
* ```ts
|
|
991
|
+
* const store = createLocalStorage<{ name: string }>('user');
|
|
992
|
+
* store.set({ name: 'Alice' });
|
|
993
|
+
* store.get(); // { name: 'Alice' }
|
|
994
|
+
* store.remove();
|
|
995
|
+
* ```
|
|
996
|
+
*/
|
|
997
|
+
declare function createLocalStorage<T>(key: string, options?: StorageOptions$1<T>): TypedStorage$1<T>;
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Forma Storage - Session
|
|
1001
|
+
*
|
|
1002
|
+
* Typed sessionStorage wrapper with graceful error handling.
|
|
1003
|
+
* Zero dependencies — native browser APIs only.
|
|
1004
|
+
*/
|
|
1005
|
+
interface TypedStorage<T> {
|
|
1006
|
+
get(): T | null;
|
|
1007
|
+
set(value: T): void;
|
|
1008
|
+
remove(): void;
|
|
1009
|
+
key: string;
|
|
1010
|
+
}
|
|
1011
|
+
interface StorageOptions<T> {
|
|
1012
|
+
serialize?: (v: T) => string;
|
|
1013
|
+
deserialize?: (s: string) => T;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Create a typed sessionStorage wrapper for the given key.
|
|
1017
|
+
*
|
|
1018
|
+
* ```ts
|
|
1019
|
+
* const store = createSessionStorage<{ token: string }>('auth');
|
|
1020
|
+
* store.set({ token: 'abc123' });
|
|
1021
|
+
* store.get(); // { token: 'abc123' }
|
|
1022
|
+
* store.remove();
|
|
1023
|
+
* ```
|
|
1024
|
+
*/
|
|
1025
|
+
declare function createSessionStorage<T>(key: string, options?: StorageOptions<T>): TypedStorage<T>;
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Forma Storage - IndexedDB
|
|
1029
|
+
*
|
|
1030
|
+
* Simplified IndexedDB wrapper with lazy connection and promise-based API.
|
|
1031
|
+
* Zero dependencies — native browser APIs only.
|
|
1032
|
+
*/
|
|
1033
|
+
interface IDBStore<T> {
|
|
1034
|
+
get(key: string): Promise<T | undefined>;
|
|
1035
|
+
set(key: string, value: T): Promise<void>;
|
|
1036
|
+
delete(key: string): Promise<void>;
|
|
1037
|
+
getAll(): Promise<T[]>;
|
|
1038
|
+
keys(): Promise<string[]>;
|
|
1039
|
+
clear(): Promise<void>;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Create a simplified IndexedDB store.
|
|
1043
|
+
*
|
|
1044
|
+
* ```ts
|
|
1045
|
+
* const store = createIndexedDB<User>('myApp', 'users');
|
|
1046
|
+
* await store.set('u1', { name: 'Alice' });
|
|
1047
|
+
* const user = await store.get('u1'); // { name: 'Alice' }
|
|
1048
|
+
* ```
|
|
1049
|
+
*/
|
|
1050
|
+
declare function createIndexedDB<T>(dbName: string, storeName?: string): IDBStore<T>;
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Forma HTTP - Fetch
|
|
1054
|
+
*
|
|
1055
|
+
* Typed fetch wrapper with reactive signal integration.
|
|
1056
|
+
* Zero dependencies — native browser APIs only.
|
|
1057
|
+
*/
|
|
1058
|
+
interface FetchOptions<T> extends Omit<RequestInit, 'signal'> {
|
|
1059
|
+
base?: string;
|
|
1060
|
+
params?: Record<string, string>;
|
|
1061
|
+
timeout?: number;
|
|
1062
|
+
transform?: (data: unknown) => T;
|
|
1063
|
+
}
|
|
1064
|
+
interface FetchResult<T> {
|
|
1065
|
+
data: () => T | null;
|
|
1066
|
+
error: () => Error | null;
|
|
1067
|
+
loading: () => boolean;
|
|
1068
|
+
refetch: () => Promise<void>;
|
|
1069
|
+
abort: () => void;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Create a reactive fetch that exposes data/error/loading as signals.
|
|
1073
|
+
*
|
|
1074
|
+
* If `url` is a signal getter (function), an effect auto-refetches when it
|
|
1075
|
+
* changes.
|
|
1076
|
+
*
|
|
1077
|
+
* ```ts
|
|
1078
|
+
* const { data, loading, error, refetch, abort } = createFetch<User[]>('/api/users');
|
|
1079
|
+
* ```
|
|
1080
|
+
*/
|
|
1081
|
+
declare function createFetch<T>(url: string | (() => string), options?: FetchOptions<T>): FetchResult<T>;
|
|
1082
|
+
/**
|
|
1083
|
+
* One-shot fetch that returns parsed JSON.
|
|
1084
|
+
*
|
|
1085
|
+
* ```ts
|
|
1086
|
+
* const users = await fetchJSON<User[]>('/api/users');
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
declare function fetchJSON<T>(url: string, options?: RequestInit): Promise<T>;
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Forma HTTP - Server-Sent Events
|
|
1093
|
+
*
|
|
1094
|
+
* Reactive SSE wrapper with signal integration.
|
|
1095
|
+
* Zero dependencies — native browser APIs only.
|
|
1096
|
+
*/
|
|
1097
|
+
interface SSEOptions {
|
|
1098
|
+
withCredentials?: boolean;
|
|
1099
|
+
headers?: Record<string, string>;
|
|
1100
|
+
}
|
|
1101
|
+
interface SSEConnection<T = unknown> {
|
|
1102
|
+
data: () => T | null;
|
|
1103
|
+
error: () => Event | null;
|
|
1104
|
+
connected: () => boolean;
|
|
1105
|
+
close: () => void;
|
|
1106
|
+
on(event: string, handler: (data: unknown) => void): () => void;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Create a reactive Server-Sent Events connection.
|
|
1110
|
+
*
|
|
1111
|
+
* ```ts
|
|
1112
|
+
* const sse = createSSE<{ message: string }>('/api/events');
|
|
1113
|
+
* createEffect(() => {
|
|
1114
|
+
* const msg = sse.data();
|
|
1115
|
+
* if (msg) console.log(msg.message);
|
|
1116
|
+
* });
|
|
1117
|
+
* ```
|
|
1118
|
+
*/
|
|
1119
|
+
declare function createSSE<T = unknown>(url: string, options?: SSEOptions): SSEConnection<T>;
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Forma HTTP - WebSocket
|
|
1123
|
+
*
|
|
1124
|
+
* Reactive WebSocket wrapper with auto-reconnect and signal integration.
|
|
1125
|
+
* Zero dependencies — native browser APIs only.
|
|
1126
|
+
*/
|
|
1127
|
+
type WSStatus = 'connecting' | 'open' | 'closed' | 'error';
|
|
1128
|
+
interface WSOptions {
|
|
1129
|
+
protocols?: string | string[];
|
|
1130
|
+
reconnect?: boolean;
|
|
1131
|
+
reconnectInterval?: number;
|
|
1132
|
+
maxReconnects?: number;
|
|
1133
|
+
}
|
|
1134
|
+
interface WSConnection<TSend = unknown, TReceive = unknown> {
|
|
1135
|
+
data: () => TReceive | null;
|
|
1136
|
+
status: () => WSStatus;
|
|
1137
|
+
send(data: TSend): void;
|
|
1138
|
+
close(): void;
|
|
1139
|
+
on(handler: (data: TReceive) => void): () => void;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Create a reactive WebSocket connection with auto-reconnect.
|
|
1143
|
+
*
|
|
1144
|
+
* ```ts
|
|
1145
|
+
* const ws = createWebSocket<string, ChatMessage>('wss://chat.example.com');
|
|
1146
|
+
* ws.send('hello');
|
|
1147
|
+
* createEffect(() => {
|
|
1148
|
+
* const msg = ws.data();
|
|
1149
|
+
* if (msg) console.log(msg);
|
|
1150
|
+
* });
|
|
1151
|
+
* ```
|
|
1152
|
+
*/
|
|
1153
|
+
declare function createWebSocket<TSend = unknown, TReceive = unknown>(url: string, options?: WSOptions): WSConnection<TSend, TReceive>;
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* FormaJS Server - Action
|
|
1157
|
+
*
|
|
1158
|
+
* Creates an action that wraps a server function with optimistic UI support.
|
|
1159
|
+
* The action immediately applies an optimistic update, then reconciles when
|
|
1160
|
+
* the server responds (or rolls back on error).
|
|
1161
|
+
*/
|
|
1162
|
+
|
|
1163
|
+
interface ActionOptions<Args extends unknown[], Result> {
|
|
1164
|
+
/**
|
|
1165
|
+
* Apply an optimistic update immediately when the action is called.
|
|
1166
|
+
* This runs BEFORE the server function.
|
|
1167
|
+
* Store whatever you need to roll back in the return value or via closures.
|
|
1168
|
+
*/
|
|
1169
|
+
optimistic?: (...args: Args) => void;
|
|
1170
|
+
/**
|
|
1171
|
+
* Called when the server function resolves successfully.
|
|
1172
|
+
* Use this to apply the server result to local state.
|
|
1173
|
+
*/
|
|
1174
|
+
onSuccess?: (result: Result, ...args: Args) => void;
|
|
1175
|
+
/**
|
|
1176
|
+
* Called when the server function rejects.
|
|
1177
|
+
* Use this to roll back the optimistic update.
|
|
1178
|
+
*/
|
|
1179
|
+
onError?: (error: unknown, ...args: Args) => void;
|
|
1180
|
+
/**
|
|
1181
|
+
* Resources to refetch after the action completes successfully.
|
|
1182
|
+
* Used with single-flight mutations: if the server response includes
|
|
1183
|
+
* revalidation data, these resources are updated without a refetch.
|
|
1184
|
+
*/
|
|
1185
|
+
invalidates?: Resource<unknown>[];
|
|
1186
|
+
}
|
|
1187
|
+
interface Action<Args extends unknown[], Result> {
|
|
1188
|
+
/** Execute the action. */
|
|
1189
|
+
(...args: Args): Promise<Result>;
|
|
1190
|
+
/** Whether the action is currently in-flight. */
|
|
1191
|
+
pending: () => boolean;
|
|
1192
|
+
/** The last error from the action (or undefined). */
|
|
1193
|
+
error: () => unknown;
|
|
1194
|
+
/** Clear the error state. */
|
|
1195
|
+
clearError: () => void;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Create an action that wraps a server function with optimistic UI.
|
|
1199
|
+
*
|
|
1200
|
+
* ```ts
|
|
1201
|
+
* const addTodo = createAction(
|
|
1202
|
+
* serverCreateTodo,
|
|
1203
|
+
* {
|
|
1204
|
+
* optimistic: (text) => {
|
|
1205
|
+
* // Immediately add to local list
|
|
1206
|
+
* setTodos(prev => [...prev, { text, done: false, id: 'temp' }]);
|
|
1207
|
+
* },
|
|
1208
|
+
* onSuccess: (result) => {
|
|
1209
|
+
* // Replace temp item with server result
|
|
1210
|
+
* setTodos(prev => prev.map(t => t.id === 'temp' ? result : t));
|
|
1211
|
+
* },
|
|
1212
|
+
* onError: (err, text) => {
|
|
1213
|
+
* // Remove the optimistic item
|
|
1214
|
+
* setTodos(prev => prev.filter(t => t.id !== 'temp'));
|
|
1215
|
+
* },
|
|
1216
|
+
* invalidates: [todosResource],
|
|
1217
|
+
* },
|
|
1218
|
+
* );
|
|
1219
|
+
*
|
|
1220
|
+
* // Use:
|
|
1221
|
+
* await addTodo('Buy milk');
|
|
1222
|
+
* ```
|
|
1223
|
+
*/
|
|
1224
|
+
declare function createAction<Args extends unknown[], Result>(serverFn: (...args: Args) => Promise<Result>, options?: ActionOptions<Args, Result>): Action<Args, Result>;
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* FormaJS Server - Mutation
|
|
1228
|
+
*
|
|
1229
|
+
* Single-flight mutation pattern: the server response carries both the
|
|
1230
|
+
* mutation result AND fresh data for dependent resources in one round trip.
|
|
1231
|
+
*
|
|
1232
|
+
* Without single-flight:
|
|
1233
|
+
* Client -> Server: createTodo("Buy milk")
|
|
1234
|
+
* Server -> Client: { id: 1, text: "Buy milk" }
|
|
1235
|
+
* Client -> Server: GET /api/todos (refetch to update list)
|
|
1236
|
+
* Server -> Client: [all todos]
|
|
1237
|
+
*
|
|
1238
|
+
* With single-flight:
|
|
1239
|
+
* Client -> Server: createTodo("Buy milk")
|
|
1240
|
+
* Server -> Client: { data: {...}, __revalidate: { "/api/todos": [all todos] } }
|
|
1241
|
+
* (No second request needed!)
|
|
1242
|
+
*/
|
|
1243
|
+
|
|
1244
|
+
interface MutationResponse<T> {
|
|
1245
|
+
/** The mutation result. */
|
|
1246
|
+
data: T;
|
|
1247
|
+
/**
|
|
1248
|
+
* Fresh data for dependent resources, keyed by resource identifier.
|
|
1249
|
+
* When present, the client updates these resources directly instead of refetching.
|
|
1250
|
+
*/
|
|
1251
|
+
__revalidate?: Record<string, unknown>;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Register a resource with a key so it can be revalidated by single-flight mutations.
|
|
1255
|
+
*
|
|
1256
|
+
* ```ts
|
|
1257
|
+
* const todos = createResource(
|
|
1258
|
+
* () => true,
|
|
1259
|
+
* () => fetch('/api/todos').then(r => r.json()),
|
|
1260
|
+
* );
|
|
1261
|
+
* registerResource('/api/todos', todos);
|
|
1262
|
+
* ```
|
|
1263
|
+
*/
|
|
1264
|
+
declare function registerResource(key: string, resource: Resource<unknown>): void;
|
|
1265
|
+
/**
|
|
1266
|
+
* Unregister a resource (call on cleanup/unmount).
|
|
1267
|
+
*/
|
|
1268
|
+
declare function unregisterResource(key: string): void;
|
|
1269
|
+
/**
|
|
1270
|
+
* Apply revalidation data from a single-flight mutation response.
|
|
1271
|
+
* For each key in the revalidate map, find the matching resource
|
|
1272
|
+
* and mutate it directly with the fresh data (skipping a refetch).
|
|
1273
|
+
*/
|
|
1274
|
+
declare function applyRevalidation(revalidateData: Record<string, unknown>): void;
|
|
1275
|
+
/**
|
|
1276
|
+
* Listen for revalidation events dispatched by $$serverFunction.
|
|
1277
|
+
* Call this once during app initialization to enable automatic
|
|
1278
|
+
* single-flight mutation handling.
|
|
1279
|
+
*
|
|
1280
|
+
* ```ts
|
|
1281
|
+
* // In your app entry:
|
|
1282
|
+
* import { enableAutoRevalidation } from 'forma/server/mutation';
|
|
1283
|
+
* enableAutoRevalidation();
|
|
1284
|
+
* ```
|
|
1285
|
+
*/
|
|
1286
|
+
declare function enableAutoRevalidation(): () => void;
|
|
1287
|
+
/**
|
|
1288
|
+
* Wrap a server function response to include revalidation data.
|
|
1289
|
+
* Use this on the server side to enable single-flight mutations.
|
|
1290
|
+
*
|
|
1291
|
+
* ```ts
|
|
1292
|
+
* // Server-side:
|
|
1293
|
+
* async function createTodo(text: string) {
|
|
1294
|
+
* "use server";
|
|
1295
|
+
* const newTodo = await db.insert('todos', { text, done: false });
|
|
1296
|
+
* const allTodos = await db.query('todos');
|
|
1297
|
+
* return withRevalidation(newTodo, {
|
|
1298
|
+
* '/api/todos': allTodos,
|
|
1299
|
+
* });
|
|
1300
|
+
* }
|
|
1301
|
+
* ```
|
|
1302
|
+
*/
|
|
1303
|
+
declare function withRevalidation<T>(data: T, revalidate: Record<string, unknown>): MutationResponse<T>;
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* FormaJS Server - RPC Client
|
|
1307
|
+
*
|
|
1308
|
+
* Provides the client-side stub function that replaces "use server" function
|
|
1309
|
+
* bodies after compilation. Each call becomes a fetch POST to the server endpoint.
|
|
1310
|
+
*/
|
|
1311
|
+
/**
|
|
1312
|
+
* Create an RPC stub function for a server function.
|
|
1313
|
+
* This replaces the original function body on the client side.
|
|
1314
|
+
*
|
|
1315
|
+
* @param endpoint - The RPC endpoint path (e.g. "/rpc/createTodo_a1b2c3")
|
|
1316
|
+
* @returns An async function that sends args to the server and returns the result
|
|
1317
|
+
*/
|
|
1318
|
+
declare function $$serverFunction<T extends (...args: any[]) => Promise<any>>(endpoint: string): T;
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* FormaJS Server - RPC Handler
|
|
1322
|
+
*
|
|
1323
|
+
* Server-side registry and request handler for "use server" functions.
|
|
1324
|
+
* Framework-agnostic — works with any Node.js HTTP server, Express, Hono, etc.
|
|
1325
|
+
*/
|
|
1326
|
+
type ServerFunction = (...args: unknown[]) => Promise<unknown>;
|
|
1327
|
+
/**
|
|
1328
|
+
* Register a server function at a specific endpoint.
|
|
1329
|
+
* Called by the server-side compiled output.
|
|
1330
|
+
*/
|
|
1331
|
+
declare function registerServerFunction(endpoint: string, fn: ServerFunction): void;
|
|
1332
|
+
/**
|
|
1333
|
+
* Get a registered server function by endpoint.
|
|
1334
|
+
*/
|
|
1335
|
+
declare function getServerFunction(endpoint: string): ServerFunction | undefined;
|
|
1336
|
+
/**
|
|
1337
|
+
* Get all registered server function endpoints.
|
|
1338
|
+
*/
|
|
1339
|
+
declare function getRegisteredEndpoints(): string[];
|
|
1340
|
+
interface RPCRequest {
|
|
1341
|
+
args: unknown[];
|
|
1342
|
+
}
|
|
1343
|
+
interface RPCResponse {
|
|
1344
|
+
data?: unknown;
|
|
1345
|
+
error?: string;
|
|
1346
|
+
__revalidate?: Record<string, unknown>;
|
|
1347
|
+
}
|
|
1348
|
+
declare function handleRPC(endpoint: string, body: RPCRequest, revalidateData?: Record<string, unknown>): Promise<RPCResponse>;
|
|
1349
|
+
/**
|
|
1350
|
+
* Create a middleware-style handler for use with Express-like frameworks.
|
|
1351
|
+
*
|
|
1352
|
+
* ```ts
|
|
1353
|
+
* import { createRPCMiddleware } from 'forma/server/rpc-handler';
|
|
1354
|
+
* app.use('/rpc', createRPCMiddleware());
|
|
1355
|
+
* ```
|
|
1356
|
+
*/
|
|
1357
|
+
declare function createRPCMiddleware(): (req: {
|
|
1358
|
+
url: string;
|
|
1359
|
+
method: string;
|
|
1360
|
+
body?: unknown;
|
|
1361
|
+
}, res: {
|
|
1362
|
+
json: (data: unknown) => void;
|
|
1363
|
+
status: (code: number) => {
|
|
1364
|
+
json: (data: unknown) => void;
|
|
1365
|
+
};
|
|
1366
|
+
}) => Promise<void>;
|
|
1367
|
+
|
|
1368
|
+
declare global {
|
|
1369
|
+
interface Window {
|
|
1370
|
+
__FORMA_WASM__?: {
|
|
1371
|
+
loader: string;
|
|
1372
|
+
binary: string;
|
|
1373
|
+
ir: string;
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/** Full page render via WASM. */
|
|
1378
|
+
declare function renderLocal(slotsJson: string): Promise<string>;
|
|
1379
|
+
/** Fragment render (single island) via WASM. */
|
|
1380
|
+
declare function renderIsland(slotsJson: string, islandId: number): Promise<string>;
|
|
1381
|
+
|
|
1382
|
+
export { $, $$, $$serverFunction, type Action, type ActionOptions, type IslandHydrateFn, type MutationResponse, type RPCRequest, type RPCResponse, activateIslands, addClass, applyRevalidation, batch, children, cleanup, closest, createAction, createBus, createComputed, createContext, createEffect, createErrorBoundary, createFetch, createHistory, createIndexedDB, createList, createLocalStorage, createMemo, createPortal, createRPCMiddleware, createReducer, createRef, createResource, createRoot, createSSE, createSessionStorage, createShow, createStore, createSuspense, createSwitch, createText, createWebSocket, defineComponent, delegate, disposeComponent, enableAutoRevalidation, fetchJSON, fragment, getRegisteredEndpoints, getServerFunction, h, handleRPC, hydrateIsland, inject, longestIncreasingSubsequence, mount, nextSibling, on, onCleanup, onError, onIntersect, onKey, onMount, onMutation, onResize, onUnmount, parent, persist, prevSibling, provide, reconcileList, registerResource, registerServerFunction, removeClass, renderIsland, renderLocal, setAttr, setHTML, setStyle, setText, siblings, toggleClass, trackDisposer, unprovide, unregisterResource, untrack, withRevalidation };
|