@lunora/svelte 0.0.0 → 1.0.0-alpha.2

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,335 @@
1
+ import { LunoraClient, User, ConnectionStatus, Preloaded, FunctionReference, ReturnOf, ArgsOf, MutationCallOptions, SubscriptionErrorCallback } from '@lunora/client';
2
+ export type { ArgsOf, ConnectionStatus, FunctionReference, LunoraClient, MutationCallOptions, Preloaded, ReturnOf } from '@lunora/client';
3
+ import { Readable } from 'svelte/store';
4
+ import { PaginationStatus } from '@lunora/client/pagination';
5
+ import { RateLimitStatus, RateLimitConfig } from '@lunora/ratelimit';
6
+ /**
7
+ * Publish a {@link LunoraClient} on the Svelte component context so that
8
+ * descendant components can read it with {@link getLunoraClient} (or implicitly,
9
+ * via the default-client lookups inside `query`/`mutation`/`hydratePreloaded`).
10
+ *
11
+ * Call this once, high in the tree (typically your root `+layout.svelte` or
12
+ * `App.svelte`), during component initialisation — `setContext` must run while
13
+ * the component is being constructed, exactly like React's provider mounts once.
14
+ * This is the Svelte analogue of mounting `LunoraProvider`.
15
+ */
16
+ declare const setLunoraClient: (client: LunoraClient) => LunoraClient;
17
+ /**
18
+ * Read the {@link LunoraClient} published by {@link setLunoraClient} from the
19
+ * nearest ancestor. Throws if no provider is mounted, mirroring `useLunora`'s
20
+ * "must be used inside a LunoraProvider" guard so the failure is loud and
21
+ * early rather than a confusing `undefined` deref later.
22
+ *
23
+ * Must be called during component initialisation (Svelte's `getContext`
24
+ * constraint); the live stores returned by `query`/`hydratePreloaded` resolve
25
+ * the client eagerly at call time for exactly this reason.
26
+ */
27
+ declare const getLunoraClient: () => LunoraClient;
28
+ interface AuthStore {
29
+ /** Set the auth token on the underlying `LunoraClient`. */
30
+ setToken: (token: string | null) => void;
31
+ /** Readable store of the auth token (`null` when signed out). */
32
+ token: Readable<string | null>;
33
+ /** Readable store of the resolved user (`null` when signed out or still loading). */
34
+ user: Readable<User | null>;
35
+ }
36
+ /**
37
+ * Create a pair of Svelte readable stores tracking the auth token and the
38
+ * resolved user identity. The stores are lazy: subscriptions open on the first
39
+ * reader and close when the last unsubscribes. Calling `setToken(jwt)` after
40
+ * sign-in refreshes both stores.
41
+ *
42
+ * Pass an explicit client to bypass the ambient context (useful in tests).
43
+ */
44
+ declare const auth: (explicitClient?: ReturnType<typeof getLunoraClient>) => AuthStore;
45
+ /** The shape held by a {@link connectionStatus} store: the latest aggregate live-socket status. */
46
+ type ConnectionStatusStore = Readable<ConnectionStatus>;
47
+ /**
48
+ * Expose the client's aggregate live-socket status as a Svelte readable store.
49
+ * Read it with the `$store` idiom (`{$status}`) and it stays current: the value
50
+ * transitions through `idle` → `connecting` → `connected` → `offline` as
51
+ * sockets open and drop — the Svelte equivalent of `@lunora/react`'s
52
+ * `useConnectionStatus`. Use it to drive a connection indicator.
53
+ *
54
+ * The status listener attaches inside `readable`'s start callback (on the first
55
+ * `$`-read / `.subscribe()`) and is released by the returned stop function when
56
+ * the last subscriber goes away, so a store that's never read attaches nothing.
57
+ *
58
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
59
+ * by `setLunoraClient` (which must therefore be called during component init,
60
+ * before this runs).
61
+ */
62
+ declare const connectionStatus: (client?: LunoraClient) => ConnectionStatusStore;
63
+ /**
64
+ * Hydrate a query store from a {@link Preloaded} token produced by
65
+ * `preloadQuery` during SSR, then keep it live — the reactive-loader handoff.
66
+ *
67
+ * The store is seeded **synchronously** with `preloaded.value`, so the very
68
+ * first read (`$store` during hydration) returns the server value with no
69
+ * loading flash and no hydration mismatch — there is no `undefined` window and
70
+ * no refetch. When the store gains its first subscriber on the client, a live
71
+ * WS subscription attaches and every subsequent delta re-emits, exactly like a
72
+ * plain `query` store. This is the Svelte equivalent of React's
73
+ * `usePreloadedQuery`.
74
+ *
75
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
76
+ * by `setLunoraClient`.
77
+ *
78
+ * Note on SSR: `readable`'s start callback only runs when the store is actually
79
+ * subscribed (the browser), so on the server the store simply holds the seeded
80
+ * value and opens no socket. The token's `value` is the single source of truth
81
+ * for the first paint either way.
82
+ */
83
+ declare const hydratePreloaded: <T>(preloaded: Preloaded<T>, client?: LunoraClient) => Readable<T>;
84
+ /**
85
+ * The reactive handle returned by {@link mutation} — the Svelte counterpart to
86
+ * React's `useMutation`, re-expressed as stores you read with `$`. The surface
87
+ * is identical across the Lunora adapters (`@lunora/solid`, `/vue`):
88
+ * `data`/`error`/`pending` are readable stores and `mutate` is an awaitable.
89
+ */
90
+ interface MutationHandle<F extends FunctionReference> {
91
+ /** The latest invocation's resolved value, or `undefined` before the first success. */
92
+ data: Readable<ReturnOf<F> | undefined>;
93
+ /** The latest invocation's error, or `undefined`. */
94
+ error: Readable<Error | undefined>;
95
+ /**
96
+ * Run the mutation. Resolves with the server result and rejects on failure
97
+ * (errors propagate — there is no swallowing). Optimistic updates passed in
98
+ * `options` are applied and rolled back by the client against the live query
99
+ * subscriptions, exactly as in the React adapter.
100
+ */
101
+ mutate: (args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
102
+ /**
103
+ * `true` while any invocation from this handle is in flight. Ref-counted, so
104
+ * overlapping calls compose and it only flips back to `false` once the last
105
+ * one settles. Read it with `$pending` in a component to disable a button.
106
+ */
107
+ pending: Readable<boolean>;
108
+ /** Clear `data`/`error` back to idle. */
109
+ reset: () => void;
110
+ }
111
+ /**
112
+ * Create an optimistic {@link MutationHandle} for a mutation reference. The
113
+ * Svelte counterpart to React's `useMutation`: returns
114
+ * `{ data, error, pending, mutate, reset }` of readable stores plus an awaitable
115
+ * `mutate`. The ref-counted pending + error-normalize orchestration is the
116
+ * shared `createMutationRunner` from `@lunora/client`; only the stores are
117
+ * adapter-specific.
118
+ *
119
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
120
+ * by `setLunoraClient`.
121
+ */
122
+ declare function mutation<F extends FunctionReference>(function_: F): MutationHandle<F>;
123
+ declare function mutation<F extends FunctionReference>(client: LunoraClient, function_: F): MutationHandle<F>;
124
+ /** The args a paginated query exposes minus the framework-supplied page cursor. */
125
+ type PaginatedArgs<F extends FunctionReference> = Omit<ArgsOf<F>, "paginationOpts">;
126
+ /** The element type of the `page` array a paginated query returns. */
127
+ type PageItemOf<F extends FunctionReference> = ReturnOf<F> extends {
128
+ page: (infer T)[];
129
+ } ? T : unknown;
130
+ interface PaginatedQueryOptions {
131
+ /** Page size for the first page (and the default for `loadMore`). */
132
+ initialNumItems: number;
133
+ shardKey?: string;
134
+ }
135
+ interface PaginatedQueryHandle<T> {
136
+ /** `true` while the first page or a `loadMore` page is in flight. */
137
+ isLoading: Readable<boolean>;
138
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
139
+ loadMore: (numberItems: number) => void;
140
+ /** Flattened items across every loaded page, in order. */
141
+ results: Readable<T[]>;
142
+ status: Readable<PaginationStatus>;
143
+ }
144
+ interface InfiniteQueryOptions {
145
+ /** Page size for the first page (and the default for `fetchNextPage`). */
146
+ initialNumItems: number;
147
+ shardKey?: string;
148
+ }
149
+ interface InfiniteQueryHandle<T> {
150
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
151
+ fetchNextPage: (numberItems?: number) => void;
152
+ /** `true` when the loaded tail reports it can load another page. */
153
+ hasNextPage: Readable<boolean>;
154
+ /** `true` while a `fetchNextPage` page (beyond the first) is in flight. */
155
+ isFetchingNextPage: Readable<boolean>;
156
+ /** `true` while the first page is in flight. */
157
+ isLoading: Readable<boolean>;
158
+ /** One inner array per loaded page, in order; unresolved pages are omitted. */
159
+ pages: Readable<T[][]>;
160
+ status: Readable<PaginationStatus>;
161
+ }
162
+ /**
163
+ * Open a live paginated query as Svelte stores. The first page opens when
164
+ * called; call `loadMore(n)` to append the next page. Results are flattened
165
+ * across all loaded pages.
166
+ *
167
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
168
+ * Svelte context.
169
+ */
170
+ declare function paginatedQuery<F extends FunctionReference>(function_: F, args: "skip" | PaginatedArgs<F>, options: PaginatedQueryOptions): PaginatedQueryHandle<PageItemOf<F>>;
171
+ declare function paginatedQuery<F extends FunctionReference>(client: LunoraClient, function_: F, args: "skip" | PaginatedArgs<F>, options: PaginatedQueryOptions): PaginatedQueryHandle<PageItemOf<F>>;
172
+ /**
173
+ * Open a live paginated query as Svelte stores, keeping each page as its own
174
+ * inner array (TanStack-Query-style `fetchNextPage` / `hasNextPage` shape).
175
+ *
176
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
177
+ * Svelte context.
178
+ */
179
+ declare function infiniteQuery<F extends FunctionReference>(function_: F, args: "skip" | PaginatedArgs<F>, options: InfiniteQueryOptions): InfiniteQueryHandle<PageItemOf<F>>;
180
+ declare function infiniteQuery<F extends FunctionReference>(client: LunoraClient, function_: F, args: "skip" | PaginatedArgs<F>, options: InfiniteQueryOptions): InfiniteQueryHandle<PageItemOf<F>>;
181
+ /**
182
+ * `presence` — collaborative-awareness stores, the client half of the
183
+ * `@lunora/server` `definePresence` preset.
184
+ *
185
+ * Drives the heartbeat mutation (on call, interval, and tab re-focus) and
186
+ * subscribes to the live `listPresent` query for the given room.
187
+ *
188
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
189
+ * Svelte context.
190
+ */
191
+ /**
192
+ * A heartbeat mutation reference: takes `{ roomId, sessionId, data? }`.
193
+ */
194
+ type HeartbeatReference = FunctionReference<"mutation", {
195
+ data?: Record<string, unknown>;
196
+ roomId: string;
197
+ sessionId: string;
198
+ }>;
199
+ /**
200
+ * A listPresent query reference: takes `{ roomId }` and returns the array of
201
+ * present members.
202
+ */
203
+ type ListPresentReference = FunctionReference<"query", {
204
+ roomId: string;
205
+ }>;
206
+ interface PresenceOptions<H extends HeartbeatReference, L extends ListPresentReference> {
207
+ /** Awareness blob for the first heartbeat (selection, cursor, name, color…). */
208
+ data?: Record<string, unknown>;
209
+ /** The `api.*` reference for the presence heartbeat mutation. */
210
+ heartbeat: H;
211
+ /** Heartbeat cadence in ms. Defaults to 10s. */
212
+ intervalMs?: number;
213
+ /** The `api.*` reference for the presence listPresent query. */
214
+ listPresent: L;
215
+ /**
216
+ * Stable id for this presence row. Defaults to a fresh per-mount id.
217
+ * Pass a user/connection id to control deduping across tabs.
218
+ */
219
+ sessionId?: string;
220
+ /** Forwarded to the heartbeat mutation / listPresent subscription when sharding by room. */
221
+ shardKey?: string;
222
+ }
223
+ interface PresenceHandle<L extends ListPresentReference> {
224
+ /** The present members for the room. `undefined` until the first push. */
225
+ present: Readable<ReturnOf<L> | undefined>;
226
+ /** This handle's session id. */
227
+ sessionId: string;
228
+ /** Replace the awareness `data` sent with subsequent heartbeats, and heartbeat immediately. */
229
+ setData: (data: Record<string, unknown> | undefined) => void;
230
+ /** Stop all heartbeats, remove the visibility listener, and unsubscribe. Call in `onDestroy`. */
231
+ teardown: () => void;
232
+ }
233
+ /**
234
+ * Open a live presence handle.
235
+ *
236
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
237
+ * Svelte context (requires calling inside a component's `&lt;script>` block or
238
+ * inside a function called during component initialisation).
239
+ *
240
+ * Call `teardown()` when the component is destroyed to stop heartbeats and
241
+ * remove the visibility listener (`onDestroy(handle.teardown)`).
242
+ */
243
+ declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
244
+ declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(client: LunoraClient, roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
245
+ /** Options accepted by {@link query}. */
246
+ interface QueryStoreOptions {
247
+ /** Called when the underlying subscription reports an error. */
248
+ onError?: SubscriptionErrorCallback;
249
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
250
+ shardKey?: string;
251
+ }
252
+ /**
253
+ * The shape held by a {@link query} store: the latest server value (`undefined`
254
+ * until the first response lands, mirroring React's `useQuery`).
255
+ */
256
+ type QueryStore<F extends FunctionReference> = Readable<ReturnOf<F> | undefined>;
257
+ /**
258
+ * Open a live query as a Svelte readable store. Read it with the `$store`
259
+ * idiom in a component (`{$messages}`) and it stays current: a WS subscription
260
+ * attaches the moment the store gains its first subscriber and the value
261
+ * re-emits on every server delta — the Svelte equivalent of React's `useQuery`.
262
+ *
263
+ * The subscription is opened lazily (inside `readable`'s start callback, on the
264
+ * first `$`-read / `.subscribe()`) and torn down by the returned stop function
265
+ * when the last subscriber goes away — so a store that's never read opens no
266
+ * socket, and a component that unmounts releases its subscription. Sharing one
267
+ * store across several components shares a single underlying subscription
268
+ * (the `LunoraClient` de-dupes by `(fn, args, shardKey)`).
269
+ *
270
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
271
+ * by `setLunoraClient` (which must therefore be called during component init,
272
+ * before this runs).
273
+ */
274
+ declare function query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
275
+ declare function query<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
276
+ interface RateLimitOptions {
277
+ /** Clock injection for tests. Defaults to `Date.now`. */
278
+ now?: () => number;
279
+ /**
280
+ * Re-evaluation cadence in milliseconds while throttled, so `retryAfter`
281
+ * ticks down and `disabled` flips back automatically. Defaults to `1000`.
282
+ */
283
+ tickMs?: number;
284
+ }
285
+ interface RateLimitHandle {
286
+ /** Would consuming `count` (default 1) succeed right now? Does not consume. */
287
+ check: (count?: number) => boolean;
288
+ /** Optimistically consume `count` (default 1) locally; mirrors the server algorithm. */
289
+ consume: (count?: number) => RateLimitStatus;
290
+ /** Readable store: `true` while a single unit cannot be consumed. */
291
+ disabled: Readable<boolean>;
292
+ /** Readable store: `true` while a single unit can be consumed. */
293
+ ok: Readable<boolean>;
294
+ /** Clear local accounting (e.g. after the server confirms a reset). */
295
+ reset: () => void;
296
+ /** Readable store: milliseconds until the next unit is available. `0` when `ok`. */
297
+ retryAfter: Readable<number>;
298
+ /** Stop the auto-tick interval. Call from `onDestroy` to prevent leaks. */
299
+ teardown: () => void;
300
+ }
301
+ /**
302
+ * Client-side mirror of a rate limit for instant UX — disable a button or show
303
+ * a countdown without a round-trip. It runs the same token-bucket / fixed-window
304
+ * math as `@lunora/ratelimit` on the server, so the prediction agrees with the
305
+ * authoritative check; the server remains the source of truth.
306
+ *
307
+ * `config` is read on every call; pass a stable reference (module constant).
308
+ *
309
+ * Call `teardown()` when the component is destroyed to stop the auto-tick
310
+ * interval (`onDestroy(handle.teardown)`).
311
+ */
312
+ declare const rateLimit: (config: RateLimitConfig, options?: RateLimitOptions) => RateLimitHandle;
313
+ interface SubscriptionStoreOptions {
314
+ onError?: (error: Error) => void;
315
+ shardKey?: string;
316
+ }
317
+ interface SubscriptionHandle<T> {
318
+ /** Svelte readable store of the latest server-pushed value (`undefined` until the first push). */
319
+ data: Readable<T | undefined>;
320
+ /** Svelte readable store of the latest subscription error (`undefined` when healthy). */
321
+ error: Readable<Error | undefined>;
322
+ }
323
+ /**
324
+ * Create a pair of Svelte readable stores that open a live subscription
325
+ * against the Lunora backend. `data` updates on every server push; `error`
326
+ * captures the last subscription error. Both stores are lazy: the subscription
327
+ * opens on the first subscriber to `data` and tears down when it stops.
328
+ *
329
+ * Passing `"skip"` as `args` keeps the stores connected but the subscription
330
+ * dormant (`data` stays `undefined`). Pass an explicit `client` as the first
331
+ * argument to bypass the ambient context (useful in tests).
332
+ */
333
+ declare function subscription<F extends FunctionReference>(function_: F, args: ArgsOf<F> | "skip", options?: SubscriptionStoreOptions): SubscriptionHandle<ReturnOf<F>>;
334
+ declare function subscription<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F> | "skip", options?: SubscriptionStoreOptions): SubscriptionHandle<ReturnOf<F>>;
335
+ export { type AuthStore, type ConnectionStatusStore, type HeartbeatReference, type InfiniteQueryHandle, type InfiniteQueryOptions, type ListPresentReference, type MutationHandle, type PageItemOf, type PaginatedArgs, type PaginatedQueryHandle, type PaginatedQueryOptions, type PresenceHandle, type PresenceOptions, type QueryStore, type QueryStoreOptions, type RateLimitHandle, type RateLimitOptions, type SubscriptionHandle, type SubscriptionStoreOptions, auth, connectionStatus, getLunoraClient, hydratePreloaded, infiniteQuery, mutation, paginatedQuery, presence, query, rateLimit, setLunoraClient, subscription };
@@ -0,0 +1,335 @@
1
+ import { LunoraClient, User, ConnectionStatus, Preloaded, FunctionReference, ReturnOf, ArgsOf, MutationCallOptions, SubscriptionErrorCallback } from '@lunora/client';
2
+ export type { ArgsOf, ConnectionStatus, FunctionReference, LunoraClient, MutationCallOptions, Preloaded, ReturnOf } from '@lunora/client';
3
+ import { Readable } from 'svelte/store';
4
+ import { PaginationStatus } from '@lunora/client/pagination';
5
+ import { RateLimitStatus, RateLimitConfig } from '@lunora/ratelimit';
6
+ /**
7
+ * Publish a {@link LunoraClient} on the Svelte component context so that
8
+ * descendant components can read it with {@link getLunoraClient} (or implicitly,
9
+ * via the default-client lookups inside `query`/`mutation`/`hydratePreloaded`).
10
+ *
11
+ * Call this once, high in the tree (typically your root `+layout.svelte` or
12
+ * `App.svelte`), during component initialisation — `setContext` must run while
13
+ * the component is being constructed, exactly like React's provider mounts once.
14
+ * This is the Svelte analogue of mounting `LunoraProvider`.
15
+ */
16
+ declare const setLunoraClient: (client: LunoraClient) => LunoraClient;
17
+ /**
18
+ * Read the {@link LunoraClient} published by {@link setLunoraClient} from the
19
+ * nearest ancestor. Throws if no provider is mounted, mirroring `useLunora`'s
20
+ * "must be used inside a LunoraProvider" guard so the failure is loud and
21
+ * early rather than a confusing `undefined` deref later.
22
+ *
23
+ * Must be called during component initialisation (Svelte's `getContext`
24
+ * constraint); the live stores returned by `query`/`hydratePreloaded` resolve
25
+ * the client eagerly at call time for exactly this reason.
26
+ */
27
+ declare const getLunoraClient: () => LunoraClient;
28
+ interface AuthStore {
29
+ /** Set the auth token on the underlying `LunoraClient`. */
30
+ setToken: (token: string | null) => void;
31
+ /** Readable store of the auth token (`null` when signed out). */
32
+ token: Readable<string | null>;
33
+ /** Readable store of the resolved user (`null` when signed out or still loading). */
34
+ user: Readable<User | null>;
35
+ }
36
+ /**
37
+ * Create a pair of Svelte readable stores tracking the auth token and the
38
+ * resolved user identity. The stores are lazy: subscriptions open on the first
39
+ * reader and close when the last unsubscribes. Calling `setToken(jwt)` after
40
+ * sign-in refreshes both stores.
41
+ *
42
+ * Pass an explicit client to bypass the ambient context (useful in tests).
43
+ */
44
+ declare const auth: (explicitClient?: ReturnType<typeof getLunoraClient>) => AuthStore;
45
+ /** The shape held by a {@link connectionStatus} store: the latest aggregate live-socket status. */
46
+ type ConnectionStatusStore = Readable<ConnectionStatus>;
47
+ /**
48
+ * Expose the client's aggregate live-socket status as a Svelte readable store.
49
+ * Read it with the `$store` idiom (`{$status}`) and it stays current: the value
50
+ * transitions through `idle` → `connecting` → `connected` → `offline` as
51
+ * sockets open and drop — the Svelte equivalent of `@lunora/react`'s
52
+ * `useConnectionStatus`. Use it to drive a connection indicator.
53
+ *
54
+ * The status listener attaches inside `readable`'s start callback (on the first
55
+ * `$`-read / `.subscribe()`) and is released by the returned stop function when
56
+ * the last subscriber goes away, so a store that's never read attaches nothing.
57
+ *
58
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
59
+ * by `setLunoraClient` (which must therefore be called during component init,
60
+ * before this runs).
61
+ */
62
+ declare const connectionStatus: (client?: LunoraClient) => ConnectionStatusStore;
63
+ /**
64
+ * Hydrate a query store from a {@link Preloaded} token produced by
65
+ * `preloadQuery` during SSR, then keep it live — the reactive-loader handoff.
66
+ *
67
+ * The store is seeded **synchronously** with `preloaded.value`, so the very
68
+ * first read (`$store` during hydration) returns the server value with no
69
+ * loading flash and no hydration mismatch — there is no `undefined` window and
70
+ * no refetch. When the store gains its first subscriber on the client, a live
71
+ * WS subscription attaches and every subsequent delta re-emits, exactly like a
72
+ * plain `query` store. This is the Svelte equivalent of React's
73
+ * `usePreloadedQuery`.
74
+ *
75
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
76
+ * by `setLunoraClient`.
77
+ *
78
+ * Note on SSR: `readable`'s start callback only runs when the store is actually
79
+ * subscribed (the browser), so on the server the store simply holds the seeded
80
+ * value and opens no socket. The token's `value` is the single source of truth
81
+ * for the first paint either way.
82
+ */
83
+ declare const hydratePreloaded: <T>(preloaded: Preloaded<T>, client?: LunoraClient) => Readable<T>;
84
+ /**
85
+ * The reactive handle returned by {@link mutation} — the Svelte counterpart to
86
+ * React's `useMutation`, re-expressed as stores you read with `$`. The surface
87
+ * is identical across the Lunora adapters (`@lunora/solid`, `/vue`):
88
+ * `data`/`error`/`pending` are readable stores and `mutate` is an awaitable.
89
+ */
90
+ interface MutationHandle<F extends FunctionReference> {
91
+ /** The latest invocation's resolved value, or `undefined` before the first success. */
92
+ data: Readable<ReturnOf<F> | undefined>;
93
+ /** The latest invocation's error, or `undefined`. */
94
+ error: Readable<Error | undefined>;
95
+ /**
96
+ * Run the mutation. Resolves with the server result and rejects on failure
97
+ * (errors propagate — there is no swallowing). Optimistic updates passed in
98
+ * `options` are applied and rolled back by the client against the live query
99
+ * subscriptions, exactly as in the React adapter.
100
+ */
101
+ mutate: (args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
102
+ /**
103
+ * `true` while any invocation from this handle is in flight. Ref-counted, so
104
+ * overlapping calls compose and it only flips back to `false` once the last
105
+ * one settles. Read it with `$pending` in a component to disable a button.
106
+ */
107
+ pending: Readable<boolean>;
108
+ /** Clear `data`/`error` back to idle. */
109
+ reset: () => void;
110
+ }
111
+ /**
112
+ * Create an optimistic {@link MutationHandle} for a mutation reference. The
113
+ * Svelte counterpart to React's `useMutation`: returns
114
+ * `{ data, error, pending, mutate, reset }` of readable stores plus an awaitable
115
+ * `mutate`. The ref-counted pending + error-normalize orchestration is the
116
+ * shared `createMutationRunner` from `@lunora/client`; only the stores are
117
+ * adapter-specific.
118
+ *
119
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
120
+ * by `setLunoraClient`.
121
+ */
122
+ declare function mutation<F extends FunctionReference>(function_: F): MutationHandle<F>;
123
+ declare function mutation<F extends FunctionReference>(client: LunoraClient, function_: F): MutationHandle<F>;
124
+ /** The args a paginated query exposes minus the framework-supplied page cursor. */
125
+ type PaginatedArgs<F extends FunctionReference> = Omit<ArgsOf<F>, "paginationOpts">;
126
+ /** The element type of the `page` array a paginated query returns. */
127
+ type PageItemOf<F extends FunctionReference> = ReturnOf<F> extends {
128
+ page: (infer T)[];
129
+ } ? T : unknown;
130
+ interface PaginatedQueryOptions {
131
+ /** Page size for the first page (and the default for `loadMore`). */
132
+ initialNumItems: number;
133
+ shardKey?: string;
134
+ }
135
+ interface PaginatedQueryHandle<T> {
136
+ /** `true` while the first page or a `loadMore` page is in flight. */
137
+ isLoading: Readable<boolean>;
138
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
139
+ loadMore: (numberItems: number) => void;
140
+ /** Flattened items across every loaded page, in order. */
141
+ results: Readable<T[]>;
142
+ status: Readable<PaginationStatus>;
143
+ }
144
+ interface InfiniteQueryOptions {
145
+ /** Page size for the first page (and the default for `fetchNextPage`). */
146
+ initialNumItems: number;
147
+ shardKey?: string;
148
+ }
149
+ interface InfiniteQueryHandle<T> {
150
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
151
+ fetchNextPage: (numberItems?: number) => void;
152
+ /** `true` when the loaded tail reports it can load another page. */
153
+ hasNextPage: Readable<boolean>;
154
+ /** `true` while a `fetchNextPage` page (beyond the first) is in flight. */
155
+ isFetchingNextPage: Readable<boolean>;
156
+ /** `true` while the first page is in flight. */
157
+ isLoading: Readable<boolean>;
158
+ /** One inner array per loaded page, in order; unresolved pages are omitted. */
159
+ pages: Readable<T[][]>;
160
+ status: Readable<PaginationStatus>;
161
+ }
162
+ /**
163
+ * Open a live paginated query as Svelte stores. The first page opens when
164
+ * called; call `loadMore(n)` to append the next page. Results are flattened
165
+ * across all loaded pages.
166
+ *
167
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
168
+ * Svelte context.
169
+ */
170
+ declare function paginatedQuery<F extends FunctionReference>(function_: F, args: "skip" | PaginatedArgs<F>, options: PaginatedQueryOptions): PaginatedQueryHandle<PageItemOf<F>>;
171
+ declare function paginatedQuery<F extends FunctionReference>(client: LunoraClient, function_: F, args: "skip" | PaginatedArgs<F>, options: PaginatedQueryOptions): PaginatedQueryHandle<PageItemOf<F>>;
172
+ /**
173
+ * Open a live paginated query as Svelte stores, keeping each page as its own
174
+ * inner array (TanStack-Query-style `fetchNextPage` / `hasNextPage` shape).
175
+ *
176
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
177
+ * Svelte context.
178
+ */
179
+ declare function infiniteQuery<F extends FunctionReference>(function_: F, args: "skip" | PaginatedArgs<F>, options: InfiniteQueryOptions): InfiniteQueryHandle<PageItemOf<F>>;
180
+ declare function infiniteQuery<F extends FunctionReference>(client: LunoraClient, function_: F, args: "skip" | PaginatedArgs<F>, options: InfiniteQueryOptions): InfiniteQueryHandle<PageItemOf<F>>;
181
+ /**
182
+ * `presence` — collaborative-awareness stores, the client half of the
183
+ * `@lunora/server` `definePresence` preset.
184
+ *
185
+ * Drives the heartbeat mutation (on call, interval, and tab re-focus) and
186
+ * subscribes to the live `listPresent` query for the given room.
187
+ *
188
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
189
+ * Svelte context.
190
+ */
191
+ /**
192
+ * A heartbeat mutation reference: takes `{ roomId, sessionId, data? }`.
193
+ */
194
+ type HeartbeatReference = FunctionReference<"mutation", {
195
+ data?: Record<string, unknown>;
196
+ roomId: string;
197
+ sessionId: string;
198
+ }>;
199
+ /**
200
+ * A listPresent query reference: takes `{ roomId }` and returns the array of
201
+ * present members.
202
+ */
203
+ type ListPresentReference = FunctionReference<"query", {
204
+ roomId: string;
205
+ }>;
206
+ interface PresenceOptions<H extends HeartbeatReference, L extends ListPresentReference> {
207
+ /** Awareness blob for the first heartbeat (selection, cursor, name, color…). */
208
+ data?: Record<string, unknown>;
209
+ /** The `api.*` reference for the presence heartbeat mutation. */
210
+ heartbeat: H;
211
+ /** Heartbeat cadence in ms. Defaults to 10s. */
212
+ intervalMs?: number;
213
+ /** The `api.*` reference for the presence listPresent query. */
214
+ listPresent: L;
215
+ /**
216
+ * Stable id for this presence row. Defaults to a fresh per-mount id.
217
+ * Pass a user/connection id to control deduping across tabs.
218
+ */
219
+ sessionId?: string;
220
+ /** Forwarded to the heartbeat mutation / listPresent subscription when sharding by room. */
221
+ shardKey?: string;
222
+ }
223
+ interface PresenceHandle<L extends ListPresentReference> {
224
+ /** The present members for the room. `undefined` until the first push. */
225
+ present: Readable<ReturnOf<L> | undefined>;
226
+ /** This handle's session id. */
227
+ sessionId: string;
228
+ /** Replace the awareness `data` sent with subsequent heartbeats, and heartbeat immediately. */
229
+ setData: (data: Record<string, unknown> | undefined) => void;
230
+ /** Stop all heartbeats, remove the visibility listener, and unsubscribe. Call in `onDestroy`. */
231
+ teardown: () => void;
232
+ }
233
+ /**
234
+ * Open a live presence handle.
235
+ *
236
+ * Pass `client` explicitly, or omit it to resolve the ambient client from the
237
+ * Svelte context (requires calling inside a component's `&lt;script>` block or
238
+ * inside a function called during component initialisation).
239
+ *
240
+ * Call `teardown()` when the component is destroyed to stop heartbeats and
241
+ * remove the visibility listener (`onDestroy(handle.teardown)`).
242
+ */
243
+ declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
244
+ declare function presence<H extends HeartbeatReference, L extends ListPresentReference>(client: LunoraClient, roomId: string, options: PresenceOptions<H, L>): PresenceHandle<L>;
245
+ /** Options accepted by {@link query}. */
246
+ interface QueryStoreOptions {
247
+ /** Called when the underlying subscription reports an error. */
248
+ onError?: SubscriptionErrorCallback;
249
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
250
+ shardKey?: string;
251
+ }
252
+ /**
253
+ * The shape held by a {@link query} store: the latest server value (`undefined`
254
+ * until the first response lands, mirroring React's `useQuery`).
255
+ */
256
+ type QueryStore<F extends FunctionReference> = Readable<ReturnOf<F> | undefined>;
257
+ /**
258
+ * Open a live query as a Svelte readable store. Read it with the `$store`
259
+ * idiom in a component (`{$messages}`) and it stays current: a WS subscription
260
+ * attaches the moment the store gains its first subscriber and the value
261
+ * re-emits on every server delta — the Svelte equivalent of React's `useQuery`.
262
+ *
263
+ * The subscription is opened lazily (inside `readable`'s start callback, on the
264
+ * first `$`-read / `.subscribe()`) and torn down by the returned stop function
265
+ * when the last subscriber goes away — so a store that's never read opens no
266
+ * socket, and a component that unmounts releases its subscription. Sharing one
267
+ * store across several components shares a single underlying subscription
268
+ * (the `LunoraClient` de-dupes by `(fn, args, shardKey)`).
269
+ *
270
+ * Pass `client` explicitly, or omit it to resolve the ambient client published
271
+ * by `setLunoraClient` (which must therefore be called during component init,
272
+ * before this runs).
273
+ */
274
+ declare function query<F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
275
+ declare function query<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F>, options?: QueryStoreOptions): QueryStore<F>;
276
+ interface RateLimitOptions {
277
+ /** Clock injection for tests. Defaults to `Date.now`. */
278
+ now?: () => number;
279
+ /**
280
+ * Re-evaluation cadence in milliseconds while throttled, so `retryAfter`
281
+ * ticks down and `disabled` flips back automatically. Defaults to `1000`.
282
+ */
283
+ tickMs?: number;
284
+ }
285
+ interface RateLimitHandle {
286
+ /** Would consuming `count` (default 1) succeed right now? Does not consume. */
287
+ check: (count?: number) => boolean;
288
+ /** Optimistically consume `count` (default 1) locally; mirrors the server algorithm. */
289
+ consume: (count?: number) => RateLimitStatus;
290
+ /** Readable store: `true` while a single unit cannot be consumed. */
291
+ disabled: Readable<boolean>;
292
+ /** Readable store: `true` while a single unit can be consumed. */
293
+ ok: Readable<boolean>;
294
+ /** Clear local accounting (e.g. after the server confirms a reset). */
295
+ reset: () => void;
296
+ /** Readable store: milliseconds until the next unit is available. `0` when `ok`. */
297
+ retryAfter: Readable<number>;
298
+ /** Stop the auto-tick interval. Call from `onDestroy` to prevent leaks. */
299
+ teardown: () => void;
300
+ }
301
+ /**
302
+ * Client-side mirror of a rate limit for instant UX — disable a button or show
303
+ * a countdown without a round-trip. It runs the same token-bucket / fixed-window
304
+ * math as `@lunora/ratelimit` on the server, so the prediction agrees with the
305
+ * authoritative check; the server remains the source of truth.
306
+ *
307
+ * `config` is read on every call; pass a stable reference (module constant).
308
+ *
309
+ * Call `teardown()` when the component is destroyed to stop the auto-tick
310
+ * interval (`onDestroy(handle.teardown)`).
311
+ */
312
+ declare const rateLimit: (config: RateLimitConfig, options?: RateLimitOptions) => RateLimitHandle;
313
+ interface SubscriptionStoreOptions {
314
+ onError?: (error: Error) => void;
315
+ shardKey?: string;
316
+ }
317
+ interface SubscriptionHandle<T> {
318
+ /** Svelte readable store of the latest server-pushed value (`undefined` until the first push). */
319
+ data: Readable<T | undefined>;
320
+ /** Svelte readable store of the latest subscription error (`undefined` when healthy). */
321
+ error: Readable<Error | undefined>;
322
+ }
323
+ /**
324
+ * Create a pair of Svelte readable stores that open a live subscription
325
+ * against the Lunora backend. `data` updates on every server push; `error`
326
+ * captures the last subscription error. Both stores are lazy: the subscription
327
+ * opens on the first subscriber to `data` and tears down when it stops.
328
+ *
329
+ * Passing `"skip"` as `args` keeps the stores connected but the subscription
330
+ * dormant (`data` stays `undefined`). Pass an explicit `client` as the first
331
+ * argument to bypass the ambient context (useful in tests).
332
+ */
333
+ declare function subscription<F extends FunctionReference>(function_: F, args: ArgsOf<F> | "skip", options?: SubscriptionStoreOptions): SubscriptionHandle<ReturnOf<F>>;
334
+ declare function subscription<F extends FunctionReference>(client: LunoraClient, function_: F, args: ArgsOf<F> | "skip", options?: SubscriptionStoreOptions): SubscriptionHandle<ReturnOf<F>>;
335
+ export { type AuthStore, type ConnectionStatusStore, type HeartbeatReference, type InfiniteQueryHandle, type InfiniteQueryOptions, type ListPresentReference, type MutationHandle, type PageItemOf, type PaginatedArgs, type PaginatedQueryHandle, type PaginatedQueryOptions, type PresenceHandle, type PresenceOptions, type QueryStore, type QueryStoreOptions, type RateLimitHandle, type RateLimitOptions, type SubscriptionHandle, type SubscriptionStoreOptions, auth, connectionStatus, getLunoraClient, hydratePreloaded, infiniteQuery, mutation, paginatedQuery, presence, query, rateLimit, setLunoraClient, subscription };