@lunora/solid 0.0.0 → 1.0.0-alpha.1

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,339 @@
1
+ import { LunoraClient, User, ConnectionStatus, FunctionReference, ArgsOf, MutationCallOptions, ReturnOf, Preloaded } from '@lunora/client';
2
+ export type { ArgsOf, FunctionReference, OptimisticUpdate, Preloaded, ReturnOf, Unsubscribe } from '@lunora/client';
3
+ import { Context, JSX, Accessor } from 'solid-js';
4
+ import { PaginationStatus } from '@lunora/client/pagination';
5
+ import { RateLimitStatus, RateLimitConfig } from '@lunora/ratelimit';
6
+ /**
7
+ * Solid context carrying the framework-neutral {@link LunoraClient}. Every
8
+ * reactive primitive in this adapter (`createQuery`, `createMutation`,
9
+ * `hydratePreloaded`) reads the client from here, so a single
10
+ * `<LunoraProvider client={…}>` at the root of the tree wires the whole app.
11
+ *
12
+ * Defaults to `undefined` so {@link useLunora} can throw a helpful error when a
13
+ * primitive is used outside a provider rather than dereferencing it.
14
+ */
15
+ declare const LunoraContext: Context<LunoraClient | undefined>;
16
+ /**
17
+ * Read the {@link LunoraClient} from the nearest `&lt;LunoraProvider>`.
18
+ *
19
+ * Throws when called outside a provider — the client is required to open the
20
+ * HTTP/WS transport, so there is no sensible fallback. The React adapter's
21
+ * `useLunora` has the same contract.
22
+ */
23
+ declare const useLunora: () => LunoraClient;
24
+ interface UseAuthResult {
25
+ setToken: (token: string | null) => void;
26
+ token: Accessor<string | null>;
27
+ user: Accessor<User | null>;
28
+ }
29
+ /**
30
+ * Token + identity plumbing for Solid. Returns `{ token, user, setToken }`
31
+ * where `token` and `user` are fine-grained signals. `setToken(jwt)` after
32
+ * sign-in updates the shared client token; `user` resolves asynchronously via
33
+ * `client.getCurrentUser()` and updates on every token change.
34
+ */
35
+ declare const createAuth: () => UseAuthResult;
36
+ interface AuthGateProps {
37
+ children: JSX.Element;
38
+ }
39
+ /**
40
+ * Render `children` only after authentication has settled and a token + user
41
+ * are both present.
42
+ */
43
+ declare const Authenticated: (props: AuthGateProps) => JSX.Element;
44
+ /**
45
+ * Render `children` while authentication is still in progress — token is set
46
+ * but the user has not yet resolved.
47
+ */
48
+ declare const AuthLoading: (props: AuthGateProps) => JSX.Element;
49
+ /**
50
+ * Render `children` only when auth has settled and no token is present (the
51
+ * signed-out state).
52
+ */
53
+ declare const Unauthenticated: (props: AuthGateProps) => JSX.Element;
54
+ /**
55
+ * Reactive accessor of the client's aggregate live-socket status across all
56
+ * shard connections. Reads the current status synchronously and updates on
57
+ * every transition (`idle` → `connecting` → `connected` → `offline`) — Solid's
58
+ * fine-grained signals mean only the components that read the accessor
59
+ * re-render. The Solid equivalent of `@lunora/react`'s `useConnectionStatus`.
60
+ *
61
+ * The status listener is torn down via `onCleanup` when the owning reactive
62
+ * scope disposes (component unmount). Call inside a component / reactive root.
63
+ */
64
+ declare const createConnectionStatus: () => Accessor<ConnectionStatus>;
65
+ interface MutationHandle<F extends FunctionReference> {
66
+ /** The latest invocation's resolved value, or `undefined` before the first success. */
67
+ data: Accessor<ReturnOf<F> | undefined>;
68
+ /** The latest invocation's error, or `undefined`. */
69
+ error: Accessor<Error | undefined>;
70
+ /** Invoke the mutation. Resolves with the server result; rejects on failure. */
71
+ mutate: (args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
72
+ /** `true` while any invocation from this handle is in flight (ref-counted, so overlapping calls compose). */
73
+ pending: Accessor<boolean>;
74
+ /** Clear `data`/`error` back to idle. */
75
+ reset: () => void;
76
+ }
77
+ /**
78
+ * The transport surface {@link createMutation} actually needs — just
79
+ * `client.mutation`. Narrowed so the primitive can be exercised against a stub
80
+ * in tests without constructing a full `LunoraClient`.
81
+ */
82
+ interface MutationClient<F extends FunctionReference> {
83
+ mutation: (function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
84
+ }
85
+ /**
86
+ * Build a mutation handle bound to an explicit client. Internal seam used by the
87
+ * provider-bound {@link createMutation}; exported for tests that inject a stub.
88
+ * The ref-counted pending + error-normalize orchestration is the shared
89
+ * `createMutationRunner` from `@lunora/client`; only the reactive sinks (Solid
90
+ * signals) are adapter-specific.
91
+ */
92
+ declare const createMutationForClient: <F extends FunctionReference>(client: MutationClient<F>, function_: F) => MutationHandle<F>;
93
+ /**
94
+ * Returns a reactive handle `{ mutate, pending, data, error, reset }` for the
95
+ * given mutation reference, bound to the `LunoraClient` from the nearest
96
+ * `&lt;LunoraProvider>`.
97
+ *
98
+ * Optimistic updates stay client-owned: the `optimistic` / `optimisticUpdate`
99
+ * call options pass straight through to `client.mutation`, which applies and
100
+ * rolls them back against the live Lunora subscription cache — the same
101
+ * machinery `createQuery`/`hydratePreloaded` subscribe to, so an optimistic
102
+ * write reflects in those accessors immediately and reverts on failure.
103
+ *
104
+ * `pending` is ref-counted across overlapping invocations of *this* handle, so
105
+ * it only flips back to `false` once every concurrent call has settled. The
106
+ * mutation also engages `@lunora/client`'s offline queue when the socket is
107
+ * down, so `mutate` stays durable across reconnects.
108
+ */
109
+ declare const createMutation: <F extends FunctionReference>(function_: F) => MutationHandle<F>;
110
+ /** The args a paginated query exposes minus the framework-supplied page cursor. */
111
+ type PaginatedArgs<F extends FunctionReference> = Omit<ArgsOf<F>, "paginationOpts">;
112
+ /** The element type of the `page` array a paginated query returns. */
113
+ type PageItemOf<F extends FunctionReference> = ReturnOf<F> extends {
114
+ page: (infer T)[];
115
+ } ? T : unknown;
116
+ interface CreatePaginatedQueryOptions {
117
+ /** Page size for the first page (and the default for `loadMore`). */
118
+ initialNumItems: number;
119
+ shardKey?: string;
120
+ }
121
+ interface CreatePaginatedQueryResult<T> {
122
+ /** `true` while the first page or a `loadMore` page is in flight. */
123
+ isLoading: Accessor<boolean>;
124
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
125
+ loadMore: (numberItems: number) => void;
126
+ /** Flattened items across every loaded page, in order. */
127
+ results: Accessor<T[]>;
128
+ status: Accessor<PaginationStatus>;
129
+ }
130
+ interface CreateInfiniteQueryOptions {
131
+ /** Page size for the first page (and the default for `fetchNextPage`). */
132
+ initialNumItems: number;
133
+ shardKey?: string;
134
+ }
135
+ interface CreateInfiniteQueryResult<T> {
136
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
137
+ fetchNextPage: (numberItems?: number) => void;
138
+ /** `true` when the loaded tail reports it can load another page. */
139
+ hasNextPage: Accessor<boolean>;
140
+ /** `true` while a `fetchNextPage` page (beyond the first) is in flight. */
141
+ isFetchingNextPage: Accessor<boolean>;
142
+ /** `true` while the first page is in flight. */
143
+ isLoading: Accessor<boolean>;
144
+ /** One inner array per loaded page, in order; unresolved pages are omitted. */
145
+ pages: Accessor<T[][]>;
146
+ status: Accessor<PaginationStatus>;
147
+ }
148
+ /**
149
+ * Subscribe to a reactively-paginated query and grow the feed page by page.
150
+ *
151
+ * The query function must accept a `paginationOpts: { numItems, cursor,
152
+ * endCursor }` arg and return a `PaginationResult`. `loadMore` appends the next
153
+ * page off the open-ended tail's `continueCursor`; it is a no-op unless
154
+ * `status === "CanLoadMore"`.
155
+ *
156
+ * Call inside a reactive context (component / `createRoot`).
157
+ */
158
+ declare const createPaginatedQuery: <F extends FunctionReference>(function_: F, args: "skip" | Accessor<"skip" | PaginatedArgs<F>> | PaginatedArgs<F>, options: CreatePaginatedQueryOptions) => CreatePaginatedQueryResult<PageItemOf<F>>;
159
+ /**
160
+ * Subscribe to a reactively-paginated query and expose its pages discretely.
161
+ *
162
+ * Shares `createPaginatedQuery`'s pagination engine but keeps each page as its
163
+ * own inner array (TanStack-Query-style `fetchNextPage` / `hasNextPage` shape).
164
+ *
165
+ * Call inside a reactive context (component / `createRoot`).
166
+ */
167
+ declare const createInfiniteQuery: <F extends FunctionReference>(function_: F, args: "skip" | Accessor<"skip" | PaginatedArgs<F>> | PaginatedArgs<F>, options: CreateInfiniteQueryOptions) => CreateInfiniteQueryResult<PageItemOf<F>>;
168
+ /**
169
+ * `createPresence` — collaborative-awareness primitive, the client half of the
170
+ * `@lunora/server` `definePresence` preset.
171
+ *
172
+ * Drives the heartbeat mutation (on mount, interval, and tab re-focus) and
173
+ * subscribes to the live `listPresent` query for the given room.
174
+ *
175
+ * Call inside a reactive context (component / `createRoot`).
176
+ */
177
+ /**
178
+ * A heartbeat mutation reference: takes `{ roomId, sessionId, data? }`.
179
+ */
180
+ type HeartbeatReference = FunctionReference<"mutation", {
181
+ data?: Record<string, unknown>;
182
+ roomId: string;
183
+ sessionId: string;
184
+ }>;
185
+ /**
186
+ * A listPresent query reference: takes `{ roomId }` and returns the array of
187
+ * present members.
188
+ */
189
+ type ListPresentReference = FunctionReference<"query", {
190
+ roomId: string;
191
+ }>;
192
+ interface CreatePresenceOptions<H extends HeartbeatReference, L extends ListPresentReference> {
193
+ /** Awareness blob for the first heartbeat (selection, cursor, name, color…). */
194
+ data?: Record<string, unknown>;
195
+ /** The `api.*` reference for the presence heartbeat mutation. */
196
+ heartbeat: H;
197
+ /** Heartbeat cadence in ms. Defaults to 10s. */
198
+ intervalMs?: number;
199
+ /** The `api.*` reference for the presence listPresent query. */
200
+ listPresent: L;
201
+ /**
202
+ * Stable id for this presence row. Defaults to a fresh per-mount id.
203
+ * Pass a user/connection id to control deduping across tabs.
204
+ */
205
+ sessionId?: string;
206
+ /** Forwarded to the heartbeat mutation / listPresent subscription when sharding by room. */
207
+ shardKey?: string;
208
+ }
209
+ interface CreatePresenceResult<L extends ListPresentReference> {
210
+ /** The present members for the room. `undefined` until the first push. */
211
+ present: () => ReturnOf<L> | undefined;
212
+ /** This mount's session id (generated when not supplied). */
213
+ sessionId: string;
214
+ /** Replace the awareness `data` sent with subsequent heartbeats, and heartbeat immediately. */
215
+ setData: (data: Record<string, unknown> | undefined) => void;
216
+ }
217
+ declare const createPresence: <H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: CreatePresenceOptions<H, L>) => CreatePresenceResult<L>;
218
+ interface CreateQueryOptions {
219
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
220
+ shardKey?: string;
221
+ }
222
+ /**
223
+ * Subscribe to a server query and return a reactive accessor of its value.
224
+ *
225
+ * The accessor reads `undefined` until the first server frame lands, then
226
+ * updates on every delta the WebSocket pushes — Solid's fine-grained signals
227
+ * mean only the components that read the accessor re-render, which maps cleanly
228
+ * onto Lunora's per-subscription delta model.
229
+ *
230
+ * `args` may be a plain value or an accessor; passing an accessor makes the
231
+ * subscription reactive — when the args change the old subscription is torn down
232
+ * (via `onCleanup`) and a fresh one opens for the new args. Pass `"skip"` (or an
233
+ * accessor returning `"skip"`) to short-circuit: no network call, no socket.
234
+ *
235
+ * ```tsx
236
+ * const messages = createQuery(api.messages.list, () => ({ channelId: channelId() }));
237
+ * return &lt;For each={messages()?.messages}>{(m) => &lt;li>{m.text}&lt;/li>}&lt;/For>;
238
+ * ```
239
+ */
240
+ declare const createQuery: <F extends FunctionReference>(function_: F, args: (ArgsOf<F> | "skip") | Accessor<ArgsOf<F> | "skip">, options?: CreateQueryOptions) => Accessor<ReturnOf<F> | undefined>;
241
+ interface CreateRateLimitOptions {
242
+ /** Clock injection for tests. Defaults to `Date.now`. */
243
+ now?: () => number;
244
+ /**
245
+ * Re-evaluation cadence in milliseconds while throttled, so `retryAfter`
246
+ * ticks down and `disabled` flips back automatically. Defaults to `1000`.
247
+ */
248
+ tickMs?: number;
249
+ }
250
+ interface CreateRateLimitResult {
251
+ /** Would consuming `count` (default 1) succeed right now? Does not consume. */
252
+ check: (count?: number) => boolean;
253
+ /** Optimistically consume `count` (default 1) locally; mirrors the server algorithm. */
254
+ consume: (count?: number) => RateLimitStatus;
255
+ /** Signal: `true` while a single unit cannot be consumed. */
256
+ disabled: () => boolean;
257
+ /** Signal: `true` while a single unit can be consumed. */
258
+ ok: () => boolean;
259
+ /** Clear local accounting (e.g. after the server confirms a reset). */
260
+ reset: () => void;
261
+ /** Signal: milliseconds until the next unit is available. `0` when `ok`. */
262
+ retryAfter: () => number;
263
+ }
264
+ /**
265
+ * Client-side mirror of a rate limit for instant UX — disable a button or show
266
+ * a countdown without a round-trip. It runs the same token-bucket / fixed-window
267
+ * math as `@lunora/ratelimit` on the server, so the prediction agrees with the
268
+ * authoritative check; the server remains the source of truth.
269
+ *
270
+ * `config` is read on every call; pass a stable reference (module constant) so
271
+ * the derived memos stay settled.
272
+ */
273
+ declare const createRateLimit: (config: RateLimitConfig, options?: CreateRateLimitOptions) => CreateRateLimitResult;
274
+ interface CreateSubscriptionResult<T> {
275
+ data: Accessor<T | undefined>;
276
+ error: Accessor<Error | undefined>;
277
+ }
278
+ /**
279
+ * Subscribe to a reactive server push stream. Returns `{ data, error }`
280
+ * accessors that update whenever the server emits. Passing `"skip"` as `args`
281
+ * (or an accessor that resolves to `"skip"`) tears down the subscription.
282
+ */
283
+ declare const createSubscription: <F extends FunctionReference>(function_: F, args: ArgsOf<F> | "skip" | Accessor<ArgsOf<F> | "skip">, options?: {
284
+ shardKey?: string;
285
+ }) => CreateSubscriptionResult<ReturnOf<F>>;
286
+ /**
287
+ * Hydrate a query from a {@link Preloaded} token produced by `preloadQuery`
288
+ * during SSR, then keep it live.
289
+ *
290
+ * This is the client half of PLAN4's "your loaders are live" handoff. The
291
+ * returned accessor is seeded **synchronously** from `preloaded.value`, so the
292
+ * very first read — during hydration — returns the server-rendered value with
293
+ * no loading flash and no `Suspense` fallback (unlike `createResource`, which
294
+ * always starts pending). After the component mounts, a WebSocket subscription
295
+ * attaches in an effect and every subsequent server delta flows into the same
296
+ * signal, so the UI goes live with zero refetch.
297
+ *
298
+ * ```tsx
299
+ * // route loader (server): const preloaded = await preloadQuery(client, api.messages.list, args);
300
+ * const messages = hydratePreloaded(preloaded); // seeded from SSR, then live
301
+ * return &lt;pre>{JSON.stringify(messages())}&lt;/pre>;
302
+ * ```
303
+ *
304
+ * Effects do not run on the server during SSR (Solid only runs them after
305
+ * hydration), so the subscription is strictly client-side — the seed is the
306
+ * only value the server render ever sees.
307
+ */
308
+ declare const hydratePreloaded: <T>(preloaded: Preloaded<T>) => Accessor<T>;
309
+ interface LunoraProviderProps {
310
+ children: JSX.Element;
311
+ /**
312
+ * The framework-neutral transport. Build it once at the app root with
313
+ * `new LunoraClient({ url })` (or `createServerClient` during SSR) and pass
314
+ * it here — the provider does not own its lifecycle, so the same instance
315
+ * survives across route navigations.
316
+ */
317
+ client: LunoraClient;
318
+ }
319
+ /**
320
+ * Provides a {@link LunoraClient} to the Solid tree via {@link LunoraContext}.
321
+ *
322
+ * Solid's context is reactive-graph scoped rather than render scoped, so unlike
323
+ * the React provider there is no QueryClient to detect or lazily create — the
324
+ * adapter's reactive primitives (`createQuery`, `createMutation`,
325
+ * `hydratePreloaded`) own their own signals and read the client straight from
326
+ * context. Drop one of these at the root of your app:
327
+ *
328
+ * ```tsx
329
+ * const client = new LunoraClient({ url: window.location.origin });
330
+ *
331
+ * render(() => (
332
+ * &lt;LunoraProvider client={client}>
333
+ * &lt;App />
334
+ * &lt;/LunoraProvider>
335
+ * ), root);
336
+ * ```
337
+ */
338
+ declare const LunoraProvider: (props: LunoraProviderProps) => JSX.Element;
339
+ export { AuthLoading, Authenticated, type CreateInfiniteQueryOptions, type CreateInfiniteQueryResult, type CreatePaginatedQueryOptions, type CreatePaginatedQueryResult, type CreatePresenceOptions, type CreatePresenceResult, type CreateQueryOptions, type CreateRateLimitOptions, type CreateRateLimitResult, type CreateSubscriptionResult, type HeartbeatReference, type ListPresentReference, LunoraContext, LunoraProvider, type LunoraProviderProps, type MutationClient, type MutationHandle, type PageItemOf, type PaginatedArgs, Unauthenticated, type UseAuthResult, createAuth, createConnectionStatus, createInfiniteQuery, createMutation, createMutationForClient, createPaginatedQuery, createPresence, createQuery, createRateLimit, createSubscription, hydratePreloaded, useLunora };
@@ -0,0 +1,339 @@
1
+ import { LunoraClient, User, ConnectionStatus, FunctionReference, ArgsOf, MutationCallOptions, ReturnOf, Preloaded } from '@lunora/client';
2
+ export type { ArgsOf, FunctionReference, OptimisticUpdate, Preloaded, ReturnOf, Unsubscribe } from '@lunora/client';
3
+ import { Context, JSX, Accessor } from 'solid-js';
4
+ import { PaginationStatus } from '@lunora/client/pagination';
5
+ import { RateLimitStatus, RateLimitConfig } from '@lunora/ratelimit';
6
+ /**
7
+ * Solid context carrying the framework-neutral {@link LunoraClient}. Every
8
+ * reactive primitive in this adapter (`createQuery`, `createMutation`,
9
+ * `hydratePreloaded`) reads the client from here, so a single
10
+ * `&lt;LunoraProvider client={…}>` at the root of the tree wires the whole app.
11
+ *
12
+ * Defaults to `undefined` so {@link useLunora} can throw a helpful error when a
13
+ * primitive is used outside a provider rather than dereferencing it.
14
+ */
15
+ declare const LunoraContext: Context<LunoraClient | undefined>;
16
+ /**
17
+ * Read the {@link LunoraClient} from the nearest `&lt;LunoraProvider>`.
18
+ *
19
+ * Throws when called outside a provider — the client is required to open the
20
+ * HTTP/WS transport, so there is no sensible fallback. The React adapter's
21
+ * `useLunora` has the same contract.
22
+ */
23
+ declare const useLunora: () => LunoraClient;
24
+ interface UseAuthResult {
25
+ setToken: (token: string | null) => void;
26
+ token: Accessor<string | null>;
27
+ user: Accessor<User | null>;
28
+ }
29
+ /**
30
+ * Token + identity plumbing for Solid. Returns `{ token, user, setToken }`
31
+ * where `token` and `user` are fine-grained signals. `setToken(jwt)` after
32
+ * sign-in updates the shared client token; `user` resolves asynchronously via
33
+ * `client.getCurrentUser()` and updates on every token change.
34
+ */
35
+ declare const createAuth: () => UseAuthResult;
36
+ interface AuthGateProps {
37
+ children: JSX.Element;
38
+ }
39
+ /**
40
+ * Render `children` only after authentication has settled and a token + user
41
+ * are both present.
42
+ */
43
+ declare const Authenticated: (props: AuthGateProps) => JSX.Element;
44
+ /**
45
+ * Render `children` while authentication is still in progress — token is set
46
+ * but the user has not yet resolved.
47
+ */
48
+ declare const AuthLoading: (props: AuthGateProps) => JSX.Element;
49
+ /**
50
+ * Render `children` only when auth has settled and no token is present (the
51
+ * signed-out state).
52
+ */
53
+ declare const Unauthenticated: (props: AuthGateProps) => JSX.Element;
54
+ /**
55
+ * Reactive accessor of the client's aggregate live-socket status across all
56
+ * shard connections. Reads the current status synchronously and updates on
57
+ * every transition (`idle` → `connecting` → `connected` → `offline`) — Solid's
58
+ * fine-grained signals mean only the components that read the accessor
59
+ * re-render. The Solid equivalent of `@lunora/react`'s `useConnectionStatus`.
60
+ *
61
+ * The status listener is torn down via `onCleanup` when the owning reactive
62
+ * scope disposes (component unmount). Call inside a component / reactive root.
63
+ */
64
+ declare const createConnectionStatus: () => Accessor<ConnectionStatus>;
65
+ interface MutationHandle<F extends FunctionReference> {
66
+ /** The latest invocation's resolved value, or `undefined` before the first success. */
67
+ data: Accessor<ReturnOf<F> | undefined>;
68
+ /** The latest invocation's error, or `undefined`. */
69
+ error: Accessor<Error | undefined>;
70
+ /** Invoke the mutation. Resolves with the server result; rejects on failure. */
71
+ mutate: (args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
72
+ /** `true` while any invocation from this handle is in flight (ref-counted, so overlapping calls compose). */
73
+ pending: Accessor<boolean>;
74
+ /** Clear `data`/`error` back to idle. */
75
+ reset: () => void;
76
+ }
77
+ /**
78
+ * The transport surface {@link createMutation} actually needs — just
79
+ * `client.mutation`. Narrowed so the primitive can be exercised against a stub
80
+ * in tests without constructing a full `LunoraClient`.
81
+ */
82
+ interface MutationClient<F extends FunctionReference> {
83
+ mutation: (function_: F, args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
84
+ }
85
+ /**
86
+ * Build a mutation handle bound to an explicit client. Internal seam used by the
87
+ * provider-bound {@link createMutation}; exported for tests that inject a stub.
88
+ * The ref-counted pending + error-normalize orchestration is the shared
89
+ * `createMutationRunner` from `@lunora/client`; only the reactive sinks (Solid
90
+ * signals) are adapter-specific.
91
+ */
92
+ declare const createMutationForClient: <F extends FunctionReference>(client: MutationClient<F>, function_: F) => MutationHandle<F>;
93
+ /**
94
+ * Returns a reactive handle `{ mutate, pending, data, error, reset }` for the
95
+ * given mutation reference, bound to the `LunoraClient` from the nearest
96
+ * `&lt;LunoraProvider>`.
97
+ *
98
+ * Optimistic updates stay client-owned: the `optimistic` / `optimisticUpdate`
99
+ * call options pass straight through to `client.mutation`, which applies and
100
+ * rolls them back against the live Lunora subscription cache — the same
101
+ * machinery `createQuery`/`hydratePreloaded` subscribe to, so an optimistic
102
+ * write reflects in those accessors immediately and reverts on failure.
103
+ *
104
+ * `pending` is ref-counted across overlapping invocations of *this* handle, so
105
+ * it only flips back to `false` once every concurrent call has settled. The
106
+ * mutation also engages `@lunora/client`'s offline queue when the socket is
107
+ * down, so `mutate` stays durable across reconnects.
108
+ */
109
+ declare const createMutation: <F extends FunctionReference>(function_: F) => MutationHandle<F>;
110
+ /** The args a paginated query exposes minus the framework-supplied page cursor. */
111
+ type PaginatedArgs<F extends FunctionReference> = Omit<ArgsOf<F>, "paginationOpts">;
112
+ /** The element type of the `page` array a paginated query returns. */
113
+ type PageItemOf<F extends FunctionReference> = ReturnOf<F> extends {
114
+ page: (infer T)[];
115
+ } ? T : unknown;
116
+ interface CreatePaginatedQueryOptions {
117
+ /** Page size for the first page (and the default for `loadMore`). */
118
+ initialNumItems: number;
119
+ shardKey?: string;
120
+ }
121
+ interface CreatePaginatedQueryResult<T> {
122
+ /** `true` while the first page or a `loadMore` page is in flight. */
123
+ isLoading: Accessor<boolean>;
124
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
125
+ loadMore: (numberItems: number) => void;
126
+ /** Flattened items across every loaded page, in order. */
127
+ results: Accessor<T[]>;
128
+ status: Accessor<PaginationStatus>;
129
+ }
130
+ interface CreateInfiniteQueryOptions {
131
+ /** Page size for the first page (and the default for `fetchNextPage`). */
132
+ initialNumItems: number;
133
+ shardKey?: string;
134
+ }
135
+ interface CreateInfiniteQueryResult<T> {
136
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
137
+ fetchNextPage: (numberItems?: number) => void;
138
+ /** `true` when the loaded tail reports it can load another page. */
139
+ hasNextPage: Accessor<boolean>;
140
+ /** `true` while a `fetchNextPage` page (beyond the first) is in flight. */
141
+ isFetchingNextPage: Accessor<boolean>;
142
+ /** `true` while the first page is in flight. */
143
+ isLoading: Accessor<boolean>;
144
+ /** One inner array per loaded page, in order; unresolved pages are omitted. */
145
+ pages: Accessor<T[][]>;
146
+ status: Accessor<PaginationStatus>;
147
+ }
148
+ /**
149
+ * Subscribe to a reactively-paginated query and grow the feed page by page.
150
+ *
151
+ * The query function must accept a `paginationOpts: { numItems, cursor,
152
+ * endCursor }` arg and return a `PaginationResult`. `loadMore` appends the next
153
+ * page off the open-ended tail's `continueCursor`; it is a no-op unless
154
+ * `status === "CanLoadMore"`.
155
+ *
156
+ * Call inside a reactive context (component / `createRoot`).
157
+ */
158
+ declare const createPaginatedQuery: <F extends FunctionReference>(function_: F, args: "skip" | Accessor<"skip" | PaginatedArgs<F>> | PaginatedArgs<F>, options: CreatePaginatedQueryOptions) => CreatePaginatedQueryResult<PageItemOf<F>>;
159
+ /**
160
+ * Subscribe to a reactively-paginated query and expose its pages discretely.
161
+ *
162
+ * Shares `createPaginatedQuery`'s pagination engine but keeps each page as its
163
+ * own inner array (TanStack-Query-style `fetchNextPage` / `hasNextPage` shape).
164
+ *
165
+ * Call inside a reactive context (component / `createRoot`).
166
+ */
167
+ declare const createInfiniteQuery: <F extends FunctionReference>(function_: F, args: "skip" | Accessor<"skip" | PaginatedArgs<F>> | PaginatedArgs<F>, options: CreateInfiniteQueryOptions) => CreateInfiniteQueryResult<PageItemOf<F>>;
168
+ /**
169
+ * `createPresence` — collaborative-awareness primitive, the client half of the
170
+ * `@lunora/server` `definePresence` preset.
171
+ *
172
+ * Drives the heartbeat mutation (on mount, interval, and tab re-focus) and
173
+ * subscribes to the live `listPresent` query for the given room.
174
+ *
175
+ * Call inside a reactive context (component / `createRoot`).
176
+ */
177
+ /**
178
+ * A heartbeat mutation reference: takes `{ roomId, sessionId, data? }`.
179
+ */
180
+ type HeartbeatReference = FunctionReference<"mutation", {
181
+ data?: Record<string, unknown>;
182
+ roomId: string;
183
+ sessionId: string;
184
+ }>;
185
+ /**
186
+ * A listPresent query reference: takes `{ roomId }` and returns the array of
187
+ * present members.
188
+ */
189
+ type ListPresentReference = FunctionReference<"query", {
190
+ roomId: string;
191
+ }>;
192
+ interface CreatePresenceOptions<H extends HeartbeatReference, L extends ListPresentReference> {
193
+ /** Awareness blob for the first heartbeat (selection, cursor, name, color…). */
194
+ data?: Record<string, unknown>;
195
+ /** The `api.*` reference for the presence heartbeat mutation. */
196
+ heartbeat: H;
197
+ /** Heartbeat cadence in ms. Defaults to 10s. */
198
+ intervalMs?: number;
199
+ /** The `api.*` reference for the presence listPresent query. */
200
+ listPresent: L;
201
+ /**
202
+ * Stable id for this presence row. Defaults to a fresh per-mount id.
203
+ * Pass a user/connection id to control deduping across tabs.
204
+ */
205
+ sessionId?: string;
206
+ /** Forwarded to the heartbeat mutation / listPresent subscription when sharding by room. */
207
+ shardKey?: string;
208
+ }
209
+ interface CreatePresenceResult<L extends ListPresentReference> {
210
+ /** The present members for the room. `undefined` until the first push. */
211
+ present: () => ReturnOf<L> | undefined;
212
+ /** This mount's session id (generated when not supplied). */
213
+ sessionId: string;
214
+ /** Replace the awareness `data` sent with subsequent heartbeats, and heartbeat immediately. */
215
+ setData: (data: Record<string, unknown> | undefined) => void;
216
+ }
217
+ declare const createPresence: <H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: CreatePresenceOptions<H, L>) => CreatePresenceResult<L>;
218
+ interface CreateQueryOptions {
219
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
220
+ shardKey?: string;
221
+ }
222
+ /**
223
+ * Subscribe to a server query and return a reactive accessor of its value.
224
+ *
225
+ * The accessor reads `undefined` until the first server frame lands, then
226
+ * updates on every delta the WebSocket pushes — Solid's fine-grained signals
227
+ * mean only the components that read the accessor re-render, which maps cleanly
228
+ * onto Lunora's per-subscription delta model.
229
+ *
230
+ * `args` may be a plain value or an accessor; passing an accessor makes the
231
+ * subscription reactive — when the args change the old subscription is torn down
232
+ * (via `onCleanup`) and a fresh one opens for the new args. Pass `"skip"` (or an
233
+ * accessor returning `"skip"`) to short-circuit: no network call, no socket.
234
+ *
235
+ * ```tsx
236
+ * const messages = createQuery(api.messages.list, () => ({ channelId: channelId() }));
237
+ * return &lt;For each={messages()?.messages}>{(m) => &lt;li>{m.text}&lt;/li>}&lt;/For>;
238
+ * ```
239
+ */
240
+ declare const createQuery: <F extends FunctionReference>(function_: F, args: (ArgsOf<F> | "skip") | Accessor<ArgsOf<F> | "skip">, options?: CreateQueryOptions) => Accessor<ReturnOf<F> | undefined>;
241
+ interface CreateRateLimitOptions {
242
+ /** Clock injection for tests. Defaults to `Date.now`. */
243
+ now?: () => number;
244
+ /**
245
+ * Re-evaluation cadence in milliseconds while throttled, so `retryAfter`
246
+ * ticks down and `disabled` flips back automatically. Defaults to `1000`.
247
+ */
248
+ tickMs?: number;
249
+ }
250
+ interface CreateRateLimitResult {
251
+ /** Would consuming `count` (default 1) succeed right now? Does not consume. */
252
+ check: (count?: number) => boolean;
253
+ /** Optimistically consume `count` (default 1) locally; mirrors the server algorithm. */
254
+ consume: (count?: number) => RateLimitStatus;
255
+ /** Signal: `true` while a single unit cannot be consumed. */
256
+ disabled: () => boolean;
257
+ /** Signal: `true` while a single unit can be consumed. */
258
+ ok: () => boolean;
259
+ /** Clear local accounting (e.g. after the server confirms a reset). */
260
+ reset: () => void;
261
+ /** Signal: milliseconds until the next unit is available. `0` when `ok`. */
262
+ retryAfter: () => number;
263
+ }
264
+ /**
265
+ * Client-side mirror of a rate limit for instant UX — disable a button or show
266
+ * a countdown without a round-trip. It runs the same token-bucket / fixed-window
267
+ * math as `@lunora/ratelimit` on the server, so the prediction agrees with the
268
+ * authoritative check; the server remains the source of truth.
269
+ *
270
+ * `config` is read on every call; pass a stable reference (module constant) so
271
+ * the derived memos stay settled.
272
+ */
273
+ declare const createRateLimit: (config: RateLimitConfig, options?: CreateRateLimitOptions) => CreateRateLimitResult;
274
+ interface CreateSubscriptionResult<T> {
275
+ data: Accessor<T | undefined>;
276
+ error: Accessor<Error | undefined>;
277
+ }
278
+ /**
279
+ * Subscribe to a reactive server push stream. Returns `{ data, error }`
280
+ * accessors that update whenever the server emits. Passing `"skip"` as `args`
281
+ * (or an accessor that resolves to `"skip"`) tears down the subscription.
282
+ */
283
+ declare const createSubscription: <F extends FunctionReference>(function_: F, args: ArgsOf<F> | "skip" | Accessor<ArgsOf<F> | "skip">, options?: {
284
+ shardKey?: string;
285
+ }) => CreateSubscriptionResult<ReturnOf<F>>;
286
+ /**
287
+ * Hydrate a query from a {@link Preloaded} token produced by `preloadQuery`
288
+ * during SSR, then keep it live.
289
+ *
290
+ * This is the client half of PLAN4's "your loaders are live" handoff. The
291
+ * returned accessor is seeded **synchronously** from `preloaded.value`, so the
292
+ * very first read — during hydration — returns the server-rendered value with
293
+ * no loading flash and no `Suspense` fallback (unlike `createResource`, which
294
+ * always starts pending). After the component mounts, a WebSocket subscription
295
+ * attaches in an effect and every subsequent server delta flows into the same
296
+ * signal, so the UI goes live with zero refetch.
297
+ *
298
+ * ```tsx
299
+ * // route loader (server): const preloaded = await preloadQuery(client, api.messages.list, args);
300
+ * const messages = hydratePreloaded(preloaded); // seeded from SSR, then live
301
+ * return &lt;pre>{JSON.stringify(messages())}&lt;/pre>;
302
+ * ```
303
+ *
304
+ * Effects do not run on the server during SSR (Solid only runs them after
305
+ * hydration), so the subscription is strictly client-side — the seed is the
306
+ * only value the server render ever sees.
307
+ */
308
+ declare const hydratePreloaded: <T>(preloaded: Preloaded<T>) => Accessor<T>;
309
+ interface LunoraProviderProps {
310
+ children: JSX.Element;
311
+ /**
312
+ * The framework-neutral transport. Build it once at the app root with
313
+ * `new LunoraClient({ url })` (or `createServerClient` during SSR) and pass
314
+ * it here — the provider does not own its lifecycle, so the same instance
315
+ * survives across route navigations.
316
+ */
317
+ client: LunoraClient;
318
+ }
319
+ /**
320
+ * Provides a {@link LunoraClient} to the Solid tree via {@link LunoraContext}.
321
+ *
322
+ * Solid's context is reactive-graph scoped rather than render scoped, so unlike
323
+ * the React provider there is no QueryClient to detect or lazily create — the
324
+ * adapter's reactive primitives (`createQuery`, `createMutation`,
325
+ * `hydratePreloaded`) own their own signals and read the client straight from
326
+ * context. Drop one of these at the root of your app:
327
+ *
328
+ * ```tsx
329
+ * const client = new LunoraClient({ url: window.location.origin });
330
+ *
331
+ * render(() => (
332
+ * &lt;LunoraProvider client={client}>
333
+ * &lt;App />
334
+ * &lt;/LunoraProvider>
335
+ * ), root);
336
+ * ```
337
+ */
338
+ declare const LunoraProvider: (props: LunoraProviderProps) => JSX.Element;
339
+ export { AuthLoading, Authenticated, type CreateInfiniteQueryOptions, type CreateInfiniteQueryResult, type CreatePaginatedQueryOptions, type CreatePaginatedQueryResult, type CreatePresenceOptions, type CreatePresenceResult, type CreateQueryOptions, type CreateRateLimitOptions, type CreateRateLimitResult, type CreateSubscriptionResult, type HeartbeatReference, type ListPresentReference, LunoraContext, LunoraProvider, type LunoraProviderProps, type MutationClient, type MutationHandle, type PageItemOf, type PaginatedArgs, Unauthenticated, type UseAuthResult, createAuth, createConnectionStatus, createInfiniteQuery, createMutation, createMutationForClient, createPaginatedQuery, createPresence, createQuery, createRateLimit, createSubscription, hydratePreloaded, useLunora };