@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.
- package/LICENSE.md +105 -0
- package/README.md +124 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +348 -0
- package/dist/index.d.ts +348 -0
- package/dist/index.mjs +11 -0
- package/dist/packem_shared/Authenticated-_6-uOycE.mjs +29 -0
- package/dist/packem_shared/LUNORA_INJECTION_KEY-DtFXLDQ_.mjs +22 -0
- package/dist/packem_shared/hydratePreloaded-rVY68iFv.mjs +14 -0
- package/dist/packem_shared/subscribeToQuery-Cv5YL-SI.mjs +51 -0
- package/dist/packem_shared/useAuth-C2aEzXXH.mjs +28 -0
- package/dist/packem_shared/useConnectionStatus-CDgduQYe.mjs +20 -0
- package/dist/packem_shared/useInfiniteQuery-CEfbw7Nw.mjs +199 -0
- package/dist/packem_shared/useMutation-DWubV5pv.mjs +29 -0
- package/dist/packem_shared/usePresence-BeN5xaZM.mjs +59 -0
- package/dist/packem_shared/useRateLimit-DlPWjcX1.mjs +67 -0
- package/dist/packem_shared/useSubscription-Ol0AZFUv.mjs +47 -0
- package/dist/server.d.mts +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.mjs +1 -0
- package/dist/worker.d.mts +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.mjs +1 -0
- package/package.json +58 -17
package/dist/index.d.ts
ADDED
|
@@ -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 };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { AuthLoading, Authenticated, Unauthenticated } from './packem_shared/Authenticated-_6-uOycE.mjs';
|
|
2
|
+
export { hydratePreloaded } from './packem_shared/hydratePreloaded-rVY68iFv.mjs';
|
|
3
|
+
export { LUNORA_INJECTION_KEY, createLunora, provideLunora, useLunora } from './packem_shared/LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
|
|
4
|
+
export { useAuth } from './packem_shared/useAuth-C2aEzXXH.mjs';
|
|
5
|
+
export { default as useConnectionStatus } from './packem_shared/useConnectionStatus-CDgduQYe.mjs';
|
|
6
|
+
export { useMutation } from './packem_shared/useMutation-DWubV5pv.mjs';
|
|
7
|
+
export { useInfiniteQuery, usePaginatedQuery } from './packem_shared/useInfiniteQuery-CEfbw7Nw.mjs';
|
|
8
|
+
export { usePresence } from './packem_shared/usePresence-BeN5xaZM.mjs';
|
|
9
|
+
export { subscribeToQuery, useQuery } from './packem_shared/subscribeToQuery-Cv5YL-SI.mjs';
|
|
10
|
+
export { useRateLimit } from './packem_shared/useRateLimit-DlPWjcX1.mjs';
|
|
11
|
+
export { useSubscription } from './packem_shared/useSubscription-Ol0AZFUv.mjs';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineComponent, computed } from 'vue';
|
|
2
|
+
import { useAuth } from './useAuth-C2aEzXXH.mjs';
|
|
3
|
+
|
|
4
|
+
const Authenticated = defineComponent({
|
|
5
|
+
name: "Authenticated",
|
|
6
|
+
setup(_props, { slots }) {
|
|
7
|
+
const { token, user } = useAuth();
|
|
8
|
+
const isAuthenticated = computed(() => token.value !== null && user.value !== null);
|
|
9
|
+
return () => isAuthenticated.value ? slots.default?.() : void 0;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
const Unauthenticated = defineComponent({
|
|
13
|
+
name: "Unauthenticated",
|
|
14
|
+
setup(_props, { slots }) {
|
|
15
|
+
const { token, user } = useAuth();
|
|
16
|
+
const isLoading = computed(() => token.value !== null && user.value === null);
|
|
17
|
+
return () => !isLoading.value && token.value === null ? slots.default?.() : void 0;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const AuthLoading = defineComponent({
|
|
21
|
+
name: "AuthLoading",
|
|
22
|
+
setup(_props, { slots }) {
|
|
23
|
+
const { token, user } = useAuth();
|
|
24
|
+
const isLoading = computed(() => token.value !== null && user.value === null);
|
|
25
|
+
return () => isLoading.value ? slots.default?.() : void 0;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export { AuthLoading, Authenticated, Unauthenticated };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { inject, provide } from 'vue';
|
|
2
|
+
|
|
3
|
+
const LUNORA_INJECTION_KEY = /* @__PURE__ */ Symbol("lunora.client");
|
|
4
|
+
const createLunora = (client) => {
|
|
5
|
+
return {
|
|
6
|
+
install(app) {
|
|
7
|
+
app.provide(LUNORA_INJECTION_KEY, client);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
const provideLunora = (client) => {
|
|
12
|
+
provide(LUNORA_INJECTION_KEY, client);
|
|
13
|
+
};
|
|
14
|
+
const useLunora = () => {
|
|
15
|
+
const client = inject(LUNORA_INJECTION_KEY, void 0);
|
|
16
|
+
if (!client) {
|
|
17
|
+
throw new Error("useLunora(): no LunoraClient provided — call app.use(createLunora(client)) or provideLunora(client) in a parent setup().");
|
|
18
|
+
}
|
|
19
|
+
return client;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { LUNORA_INJECTION_KEY, createLunora, provideLunora, useLunora };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
|
|
2
|
+
import { subscribeToQuery } from './subscribeToQuery-Cv5YL-SI.mjs';
|
|
3
|
+
|
|
4
|
+
const hydratePreloaded = (preloaded) => {
|
|
5
|
+
const client = useLunora();
|
|
6
|
+
const { args, functionPath, shardKey, value } = preloaded;
|
|
7
|
+
const functionReference = { __lunoraRef: functionPath };
|
|
8
|
+
return subscribeToQuery(client, functionReference, args, {
|
|
9
|
+
seed: value,
|
|
10
|
+
shardKey
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { hydratePreloaded };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createQuerySubscription } from '@lunora/client/query';
|
|
2
|
+
import { shallowRef, getCurrentScope, onScopeDispose, watch, toValue } from 'vue';
|
|
3
|
+
import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
|
|
4
|
+
|
|
5
|
+
const subscribeToQuery = (client, function_, args, options = {}) => {
|
|
6
|
+
const data = shallowRef(options.seed);
|
|
7
|
+
const unsubscribe = client.subscribe(
|
|
8
|
+
function_,
|
|
9
|
+
args,
|
|
10
|
+
(value) => {
|
|
11
|
+
data.value = value;
|
|
12
|
+
},
|
|
13
|
+
{ shardKey: options.shardKey }
|
|
14
|
+
);
|
|
15
|
+
if (getCurrentScope()) {
|
|
16
|
+
onScopeDispose(unsubscribe);
|
|
17
|
+
} else {
|
|
18
|
+
console.warn(
|
|
19
|
+
"[@lunora/vue] subscribeToQuery called with no active effect scope — its subscription will not be cleaned up automatically. Call it inside setup()/an effect scope, or call the returned teardown yourself."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
};
|
|
24
|
+
const useQuery = (function_, args, options = {}) => {
|
|
25
|
+
const client = useLunora();
|
|
26
|
+
const data = shallowRef(void 0);
|
|
27
|
+
watch(
|
|
28
|
+
() => toValue(args),
|
|
29
|
+
(current, _previous, onCleanup) => {
|
|
30
|
+
const unsubscribe = createQuerySubscription(
|
|
31
|
+
client,
|
|
32
|
+
function_,
|
|
33
|
+
current,
|
|
34
|
+
{
|
|
35
|
+
onData: (value) => {
|
|
36
|
+
data.value = value;
|
|
37
|
+
},
|
|
38
|
+
onReset: () => {
|
|
39
|
+
data.value = void 0;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{ shardKey: options.shardKey }
|
|
43
|
+
);
|
|
44
|
+
onCleanup(unsubscribe);
|
|
45
|
+
},
|
|
46
|
+
{ immediate: true }
|
|
47
|
+
);
|
|
48
|
+
return data;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { subscribeToQuery, useQuery };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getIdentityStore } from '@lunora/client/auth';
|
|
2
|
+
import { ref, onScopeDispose, readonly } from 'vue';
|
|
3
|
+
import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
|
|
4
|
+
|
|
5
|
+
const useAuth = () => {
|
|
6
|
+
const client = useLunora();
|
|
7
|
+
const store = getIdentityStore(client);
|
|
8
|
+
const tokenRef = ref(client.getAuthToken());
|
|
9
|
+
const userRef = ref(store.getUser());
|
|
10
|
+
const onTokenChange = () => {
|
|
11
|
+
tokenRef.value = client.getAuthToken();
|
|
12
|
+
};
|
|
13
|
+
const onUserChange = () => {
|
|
14
|
+
userRef.value = store.getUser();
|
|
15
|
+
};
|
|
16
|
+
const unsubToken = client.onAuthTokenChange(onTokenChange);
|
|
17
|
+
const unsubUser = store.subscribe(onUserChange);
|
|
18
|
+
onScopeDispose(() => {
|
|
19
|
+
unsubToken();
|
|
20
|
+
unsubUser();
|
|
21
|
+
});
|
|
22
|
+
const setToken = (next) => {
|
|
23
|
+
client.setAuthToken(next);
|
|
24
|
+
};
|
|
25
|
+
return { setToken, token: readonly(tokenRef), user: readonly(userRef) };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { useAuth };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { shallowRef, getCurrentScope, onScopeDispose } from 'vue';
|
|
2
|
+
import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
|
|
3
|
+
|
|
4
|
+
const useConnectionStatus = () => {
|
|
5
|
+
const client = useLunora();
|
|
6
|
+
const status = shallowRef(client.connectionStatus());
|
|
7
|
+
const unsubscribe = client.onConnectionStatus((next) => {
|
|
8
|
+
status.value = next;
|
|
9
|
+
});
|
|
10
|
+
if (getCurrentScope()) {
|
|
11
|
+
onScopeDispose(unsubscribe);
|
|
12
|
+
} else {
|
|
13
|
+
console.warn(
|
|
14
|
+
"[@lunora/vue] useConnectionStatus called with no active effect scope — its listener will not be cleaned up automatically. Call it inside setup()/an effect scope."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return status;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { useConnectionStatus as default };
|