@lunora/vue 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,348 @@
1
+ import { Component, Ref, InjectionKey, App, DeepReadonly, MaybeRefOrGetter, ShallowRef, ComputedRef } from 'vue';
2
+ import { Preloaded, LunoraClient, User, ConnectionStatus, FunctionReference, ReturnOf, ArgsOf, MutationCallOptions } from '@lunora/client';
3
+ export type { ArgsOf, FunctionReference, LunoraClient, MutationCallOptions, OptimisticLocalStore, OptimisticUpdate, Preloaded, ReturnOf, Unsubscribe, User } from '@lunora/client';
4
+ import { PaginationStatus } from '@lunora/client/pagination';
5
+ export type { PaginationResult, PaginationStatus } from '@lunora/client/pagination';
6
+ import { RateLimitStatus, RateLimitConfig } from '@lunora/ratelimit';
7
+ /**
8
+ * Render the default slot only after auth has settled and a token + user are
9
+ * both present. Hides the slot on first render and when signed out.
10
+ */
11
+ declare const Authenticated: Component;
12
+ /**
13
+ * Render the default slot only when auth has settled and no token is present
14
+ * (the signed-out state). Hidden while the user is still loading.
15
+ */
16
+ declare const Unauthenticated: Component;
17
+ /**
18
+ * Render the default slot while authentication is still in progress — token is
19
+ * set but `getCurrentUser()` has not yet resolved.
20
+ */
21
+ declare const AuthLoading: Component;
22
+ /**
23
+ * Hydrate a query from a {@link Preloaded} token produced by `preloadQuery`
24
+ * during SSR, then keep it live — the Vue half of PLAN4's reactive-loader
25
+ * handoff.
26
+ *
27
+ * The returned `ref` is seeded **synchronously** from `preloaded.value`, so the
28
+ * very first read (during hydration) shows the server value: no loading flash,
29
+ * no hydration mismatch. After seeding it opens a WebSocket subscription on the
30
+ * same `(functionPath, args, shardKey)` the SSR loader used, so every later
31
+ * server delta updates the ref exactly like `useQuery`.
32
+ *
33
+ * The subscription tears down with the surrounding effect scope (component
34
+ * unmount or `effectScope().stop()`), inherited from `subscribeToQuery`.
35
+ */
36
+ declare const hydratePreloaded: <T>(preloaded: Preloaded<T>) => Ref<T | undefined>;
37
+ /**
38
+ * Injection key carrying the {@link LunoraClient} down the component tree.
39
+ * Exported so advanced consumers can inject it by hand; most apps use
40
+ * {@link createLunora} or {@link provideLunora}.
41
+ */
42
+ declare const LUNORA_INJECTION_KEY: InjectionKey<LunoraClient>;
43
+ /**
44
+ * Vue plugin form: `app.use(createLunora(client))`. Mirrors the React
45
+ * `LunoraProvider` — establishes the single app-wide client every composable
46
+ * resolves through {@link useLunora}.
47
+ *
48
+ * The client is framework-neutral (`@lunora/client`): it owns the WebSocket
49
+ * transport, subscription registry, offline queue, and delta-merge. This plugin
50
+ * only wires it into Vue's `provide`/`inject` graph (read it with
51
+ * {@link useLunora}); it adds no React, no store, and no extra reactivity layer.
52
+ */
53
+ declare const createLunora: (client: LunoraClient) => {
54
+ install: (app: App) => void;
55
+ };
56
+ /**
57
+ * Composition-API form: call inside a parent component's `setup()` to provide
58
+ * the client to its subtree. The counterpart to `app.use(createLunora(client))`
59
+ * when you'd rather scope the client to a subtree than the whole app. Must run
60
+ * synchronously inside `setup()` (Vue's `provide` constraint).
61
+ */
62
+ declare const provideLunora: (client: LunoraClient) => void;
63
+ /**
64
+ * Read the {@link LunoraClient} from the nearest provider — the Vue counterpart
65
+ * to `@lunora/react`/`@lunora/solid`'s `useLunora`. Throws with a clear message
66
+ * when called outside a `createLunora`/`provideLunora` scope so the failure
67
+ * points at the missing provider rather than a later `undefined` deref.
68
+ */
69
+ declare const useLunora: () => LunoraClient;
70
+ /** Options shared by the live-query composables. */
71
+ interface UseQueryOptions {
72
+ /** Route to a specific shard when the target function is `.shardBy(...)`-partitioned. */
73
+ shardKey?: string;
74
+ }
75
+ interface UseAuthResult {
76
+ setToken: (token: string | null) => void;
77
+ token: DeepReadonly<Ref<string | null>>;
78
+ user: DeepReadonly<Ref<User | null>>;
79
+ }
80
+ /**
81
+ * Token + identity plumbing for Vue. `token` is a readonly ref tracking the
82
+ * client's auth token; `user` is a readonly ref resolved from `getCurrentUser()`
83
+ * whenever the token changes. `setToken(jwt)` after sign-in makes subsequent
84
+ * RPC calls carry the `Authorization` header.
85
+ *
86
+ * Multiple `useAuth` instances within the same effect scope share a single
87
+ * per-client identity store (from `@lunora/client/auth`) — a `setToken` from
88
+ * one component re-renders every watcher with the freshly-resolved user.
89
+ */
90
+ declare const useAuth: () => UseAuthResult;
91
+ /**
92
+ * Reactive view of the client's aggregate live-socket status across all shard
93
+ * connections, exposed as a read-only `ref`. The value transitions through
94
+ * `idle` → `connecting` → `connected` → `offline` as sockets open and drop —
95
+ * use it to drive a connection indicator so an operator can tell a healthy live
96
+ * channel from a silently-dropped one. The Vue-idiomatic equivalent of
97
+ * `@lunora/react`'s `useConnectionStatus`.
98
+ *
99
+ * Teardown is wired to the active effect scope (`onScopeDispose`), so the
100
+ * status listener is released on component unmount (or `effectScope().stop()`).
101
+ * Call inside `setup()` / an active effect scope.
102
+ */
103
+ declare const useConnectionStatus: () => Readonly<Ref<ConnectionStatus>>;
104
+ /**
105
+ * The reactive handle returned by {@link useMutation} — the Vue counterpart to
106
+ * React's `useMutation`, re-expressed with refs. The surface is identical across
107
+ * the Lunora adapters (`@lunora/solid`, `/svelte`): `data`/`error`/`pending` are
108
+ * refs you read in a template, `mutate` is an awaitable that resolves with the
109
+ * server value (or rejects). Per-call `optimistic` / `optimisticUpdate` options
110
+ * pass straight through to `client.mutation`.
111
+ */
112
+ interface MutationHandle<F extends FunctionReference> {
113
+ /** The latest invocation's resolved value, or `undefined` before the first success. */
114
+ data: Ref<ReturnOf<F> | undefined>;
115
+ /** The latest invocation's error, or `undefined`. */
116
+ error: Ref<Error | undefined>;
117
+ /** Invoke the mutation. Resolves with the server value; rejects on failure. */
118
+ mutate: (args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
119
+ /** `true` while ANY invocation from this handle is in flight (ref-counted, so overlapping calls compose). */
120
+ pending: Ref<boolean>;
121
+ /** Clear the latest `data`/`error` back to idle. */
122
+ reset: () => void;
123
+ }
124
+ /**
125
+ * Returns a reactive {@link MutationHandle} for the given mutation reference —
126
+ * the Vue equivalent of React's `useMutation`.
127
+ *
128
+ * Optimistic updates stay client-owned: the `optimistic` / `optimisticUpdate`
129
+ * call options pass straight through to `client.mutation`, which applies and
130
+ * rolls them back against the Lunora subscription cache (Convex parity).
131
+ *
132
+ * `pending` is ref-counted across overlapping invocations of THIS handle, so it
133
+ * flips back to `false` only once every concurrent call has settled. The
134
+ * ref-counted pending + error-normalize orchestration is the shared
135
+ * `createMutationRunner` from `@lunora/client`; only the refs are
136
+ * adapter-specific.
137
+ */
138
+ declare const useMutation: <F extends FunctionReference>(function_: F) => MutationHandle<F>;
139
+ /** The args a paginated query exposes minus the framework-supplied page cursor. */
140
+ type PaginatedArgs<F extends FunctionReference> = Omit<ArgsOf<F>, "paginationOpts">;
141
+ /** The element type of the `page` array a paginated query returns. */
142
+ type PageItemOf<F extends FunctionReference> = ReturnOf<F> extends {
143
+ page: (infer T)[];
144
+ } ? T : unknown;
145
+ interface UsePaginatedQueryOptions {
146
+ /** Page size for the first page (and the default for `loadMore`). */
147
+ initialNumItems: number;
148
+ shardKey?: string;
149
+ }
150
+ interface UsePaginatedQueryResult<T> {
151
+ /** `true` while the first page or a `loadMore` page is in flight. */
152
+ isLoading: Ref<boolean>;
153
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
154
+ loadMore: (numberItems: number) => void;
155
+ /** Flattened items across every loaded page, in order. */
156
+ results: Ref<T[]>;
157
+ status: Ref<PaginationStatus>;
158
+ }
159
+ /**
160
+ * Subscribe to a reactively-paginated query and grow the feed page by page.
161
+ *
162
+ * The query function must accept a `paginationOpts: { numItems, cursor,
163
+ * endCursor }` arg and return a `PaginationResult`. Pages are tracked as an
164
+ * ordered list of stable boundary cursors; each loaded page is a live
165
+ * subscription over a FIXED `(lower, upper]` range. Inserting or deleting a row
166
+ * grows/shrinks the affected page without duplicating or skipping rows across
167
+ * boundaries.
168
+ *
169
+ * `loadMore` appends the next page off the open-ended tail's `continueCursor`;
170
+ * it is a no-op unless `status === "CanLoadMore"`. Background split/join
171
+ * maintenance keeps page sizes near `initialNumItems` as edits accumulate.
172
+ *
173
+ * Changing `fn`, the base `args`, `initialNumItems`, or `shardKey` resets the
174
+ * feed to its first page.
175
+ *
176
+ * Call inside `setup()` (or any active effect scope).
177
+ */
178
+ declare const usePaginatedQuery: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<"skip" | PaginatedArgs<F>>, options: UsePaginatedQueryOptions) => UsePaginatedQueryResult<PageItemOf<F>>;
179
+ interface UseInfiniteQueryOptions {
180
+ /** Page size for the first page (and the default for `fetchNextPage`). */
181
+ initialNumItems: number;
182
+ shardKey?: string;
183
+ }
184
+ interface UseInfiniteQueryResult<T> {
185
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
186
+ fetchNextPage: (numberItems?: number) => void;
187
+ /** `true` when the loaded tail reports it can load another page. */
188
+ hasNextPage: Ref<boolean>;
189
+ /** `true` while a `fetchNextPage` page (beyond the first) is in flight. */
190
+ isFetchingNextPage: Ref<boolean>;
191
+ /** `true` while the first page is in flight. */
192
+ isLoading: Ref<boolean>;
193
+ /** One inner array per loaded page, in order; unresolved pages are omitted. */
194
+ pages: Ref<T[][]>;
195
+ status: Ref<PaginationStatus>;
196
+ }
197
+ /**
198
+ * Subscribe to a reactively-paginated query and expose its pages discretely.
199
+ *
200
+ * Shares `usePaginatedQuery`'s reactive-pagination engine but keeps each page
201
+ * as its own inner array rather than flattening them, and adds the
202
+ * TanStack-Query-style `fetchNextPage` / `hasNextPage` / `isFetchingNextPage`
203
+ * shape.
204
+ *
205
+ * Call inside `setup()` (or any active effect scope).
206
+ */
207
+ declare const useInfiniteQuery: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<"skip" | PaginatedArgs<F>>, options: UseInfiniteQueryOptions) => UseInfiniteQueryResult<PageItemOf<F>>;
208
+ /**
209
+ * `usePresence` — collaborative-awareness composable, the client half of the
210
+ * `@lunora/server` `definePresence` preset.
211
+ *
212
+ * Drives the heartbeat mutation (on mount, interval, and tab re-focus) and
213
+ * subscribes to the live `listPresent` query for the given room.
214
+ *
215
+ * Call inside `setup()` (or any active effect scope).
216
+ */
217
+ /**
218
+ * A heartbeat mutation reference: takes `{ roomId, sessionId, data? }`.
219
+ */
220
+ type HeartbeatReference = FunctionReference<"mutation", {
221
+ data?: Record<string, unknown>;
222
+ roomId: string;
223
+ sessionId: string;
224
+ }>;
225
+ /**
226
+ * A listPresent query reference: takes `{ roomId }` and returns the array of
227
+ * present members.
228
+ */
229
+ type ListPresentReference = FunctionReference<"query", {
230
+ roomId: string;
231
+ }>;
232
+ interface UsePresenceOptions<H extends HeartbeatReference, L extends ListPresentReference> {
233
+ /** Awareness blob for the first heartbeat (selection, cursor, name, color…). */
234
+ data?: Record<string, unknown>;
235
+ /** The `api.*` reference for the presence heartbeat mutation. */
236
+ heartbeat: H;
237
+ /** Heartbeat cadence in ms. Defaults to 10s. */
238
+ intervalMs?: number;
239
+ /** The `api.*` reference for the presence listPresent query. */
240
+ listPresent: L;
241
+ /**
242
+ * Stable id for this presence row. Defaults to a fresh per-mount id.
243
+ * Pass a user/connection id to control deduping across tabs.
244
+ */
245
+ sessionId?: string;
246
+ /** Forwarded to the heartbeat mutation / listPresent subscription when sharding by room. */
247
+ shardKey?: string;
248
+ }
249
+ interface UsePresenceResult<L extends ListPresentReference> {
250
+ /** The present members for the room. `undefined` until the first push. */
251
+ present: ShallowRef<ReturnOf<L> | undefined>;
252
+ /** This mount's session id (generated when not supplied). */
253
+ sessionId: string;
254
+ /** Replace the awareness `data` sent with subsequent heartbeats, and heartbeat immediately. */
255
+ setData: (data: Record<string, unknown> | undefined) => void;
256
+ }
257
+ declare const usePresence: <H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: UsePresenceOptions<H, L>) => UsePresenceResult<L>;
258
+ /**
259
+ * Open a live subscription against `client` for FIXED args and stream its values
260
+ * into a `ref`. The low-level primitive behind `hydratePreloaded` (whose args
261
+ * come from an immutable `Preloaded` token and never change); {@link useQuery}
262
+ * handles the reactive-args case separately.
263
+ *
264
+ * `client.subscribe` already dedupes by `(functionPath, args, shardKey)` and
265
+ * replays the last value synchronously, so multiple consumers of the same query
266
+ * ride one server-side registration. `seed` sets the ref's value synchronously
267
+ * before the subscription attaches, so the first read shows the SSR value with
268
+ * no loading flash.
269
+ *
270
+ * Teardown is wired to the active effect scope (`onScopeDispose`), so it fires
271
+ * on component unmount or `effectScope().stop()`. Call it inside `setup()` / an
272
+ * effect scope (as `hydratePreloaded` does); outside any scope there is nothing
273
+ * to own the subscription, so it would leak until the process exits — the
274
+ * `getCurrentScope` guard only avoids throwing, it does not auto-clean.
275
+ */
276
+ declare const subscribeToQuery: <F extends FunctionReference, T = ReturnOf<F>>(client: LunoraClient, function_: F, args: ArgsOf<F>, options?: {
277
+ seed?: T;
278
+ shardKey?: string;
279
+ }) => Ref<T | undefined>;
280
+ /**
281
+ * Subscribe to a server query and expose its latest value as a `ref`.
282
+ *
283
+ * The returned ref is `undefined` until the first server response lands, then
284
+ * updates on every delta the server pushes — the Vue-idiomatic equivalent of
285
+ * React's `useQuery`. `args` may be a plain value, a `ref`, or a getter: passing
286
+ * a reactive source makes the subscription reactive — when the args change the
287
+ * old subscription is torn down and a fresh one opens for the new args (matching
288
+ * `@lunora/react`/`@lunora/solid`). Pass `"skip"` (or a source resolving to
289
+ * `"skip"`) to short-circuit: no network call, no socket. The subscription tears
290
+ * down automatically when the owning component unmounts (or the effect scope
291
+ * stops).
292
+ *
293
+ * Call inside `setup()` (or any active effect scope). For SSR seeding with no
294
+ * loading flash, use `hydratePreloaded` instead.
295
+ */
296
+ declare const useQuery: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<ArgsOf<F> | "skip">, options?: UseQueryOptions) => Ref<ReturnOf<F> | undefined>;
297
+ interface UseRateLimitOptions {
298
+ /** Clock injection for tests. Defaults to `Date.now`. */
299
+ now?: () => number;
300
+ /**
301
+ * Re-render cadence in milliseconds while throttled, so `retryAfter` ticks
302
+ * down and `disabled` flips back automatically. Defaults to `1000`.
303
+ */
304
+ tickMs?: number;
305
+ }
306
+ interface UseRateLimitResult {
307
+ /** Would consuming `count` (default 1) succeed right now? Does not consume. */
308
+ check: (count?: number) => boolean;
309
+ /** Optimistically consume `count` (default 1) locally; mirrors the server algorithm. */
310
+ consume: (count?: number) => RateLimitStatus;
311
+ /** `true` while a single unit cannot be consumed — convenient for disabling a control. */
312
+ disabled: ComputedRef<boolean>;
313
+ /** `true` while a single unit can be consumed. */
314
+ ok: ComputedRef<boolean>;
315
+ /** Clear local accounting (e.g. after the server confirms a reset). */
316
+ reset: () => void;
317
+ /** Milliseconds until the next unit is available. `0` when `ok`. */
318
+ retryAfter: ComputedRef<number>;
319
+ }
320
+ /**
321
+ * Client-side mirror of a rate limit for instant UX — disable a button or show
322
+ * a countdown without a round-trip. It runs the same token-bucket / fixed-window
323
+ * math as `@lunora/ratelimit` on the server, so the prediction agrees with the
324
+ * authoritative check; the server remains the source of truth.
325
+ *
326
+ * `config` accepts a plain object, a `ref`, or a getter (`MaybeRefOrGetter`).
327
+ * When you pass a ref/getter it is tracked reactively — changing the config
328
+ * re-derives `status` (and the `ok` / `disabled` / `retryAfter` views) on the
329
+ * fly. A plain object keeps working unchanged; pass a stable reference (module
330
+ * constant) so the reactive derived values stay settled.
331
+ */
332
+ declare const useRateLimit: (config: MaybeRefOrGetter<RateLimitConfig>, options?: UseRateLimitOptions) => UseRateLimitResult;
333
+ interface UseSubscriptionResult<T> {
334
+ data: Ref<T | undefined>;
335
+ error: Ref<Error | undefined>;
336
+ }
337
+ /**
338
+ * Subscribe to a reactive server push stream. Returns `{ data, error }` refs
339
+ * that update whenever the server emits a new value. Passing `"skip"` as `args`
340
+ * (or a ref/getter that resolves to `"skip"`) tears down the subscription
341
+ * without unmounting.
342
+ *
343
+ * Unlike `useQuery`, which tracks the full reactive cache, `useSubscription`
344
+ * owns a single lightweight subscription and is suitable for ephemeral,
345
+ * high-frequency streams.
346
+ */
347
+ declare const useSubscription: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<ArgsOf<F> | "skip">, options?: UseQueryOptions) => UseSubscriptionResult<ReturnOf<F>>;
348
+ export { AuthLoading, Authenticated, type HeartbeatReference, LUNORA_INJECTION_KEY, type ListPresentReference, type MutationHandle, type PageItemOf, type PaginatedArgs, Unauthenticated, type UseAuthResult, type UseInfiniteQueryOptions, type UseInfiniteQueryResult, type UsePaginatedQueryOptions, type UsePaginatedQueryResult, type UsePresenceOptions, type UsePresenceResult, type UseQueryOptions, type UseRateLimitOptions, type UseRateLimitResult, type UseSubscriptionResult, createLunora, hydratePreloaded, provideLunora, subscribeToQuery, useAuth, useConnectionStatus, useInfiniteQuery, useLunora, useMutation, usePaginatedQuery, usePresence, useQuery, useRateLimit, useSubscription };