@lunora/vue 0.0.0 → 1.0.0-alpha.10

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,384 @@
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
+ /** A targeting context merged on top of the app's default (`defineFlags({ identify })`). */
105
+ type FlagContext = Record<string, unknown>;
106
+ /** The value kinds a flag resolves to — OpenFeature's boolean / number / string / structured (JSON) flags. */
107
+ type FlagValue = boolean | number | string | {
108
+ [key: string]: unknown;
109
+ } | unknown[] | null;
110
+ /**
111
+ * Subscribe to a single feature flag, live over Lunora's WebSocket.
112
+ *
113
+ * The returned `ref` holds `defaultValue` until the first evaluation lands, then
114
+ * the server's resolved value — re-pushed whenever the provider re-evaluates
115
+ * (e.g. a flag is toggled in Cloudflare Flagship). The flag's kind is inferred
116
+ * from `defaultValue`'s runtime type, so `useFlag("dark", false)` reads a boolean
117
+ * and `useFlag("hero", "control")` a string.
118
+ *
119
+ * `key` and `context` may be plain values, `ref`s, or getters: passing a reactive
120
+ * source makes the subscription reactive — when it changes the old subscription
121
+ * is torn down and a fresh one opens. `context` supplies a per-call targeting
122
+ * context merged on top of the app's default `identify` targeting key.
123
+ *
124
+ * Evaluation runs through whatever OpenFeature provider the app wired in
125
+ * `lunora/flags.ts`; the read never throws — a provider error resolves the
126
+ * default (the same fail-open contract as server-side `ctx.flags`). Call inside
127
+ * `setup()` (or any active effect scope); the subscription tears down on unmount.
128
+ */
129
+ declare const useFlag: <T extends FlagValue>(key: MaybeRefOrGetter<string>, defaultValue: T, context?: MaybeRefOrGetter<FlagContext | undefined>) => Readonly<Ref<T>>;
130
+ /**
131
+ * Subscribe to several feature flags at once, live over Lunora's WebSocket.
132
+ *
133
+ * Pass a record of `key → defaultValue`; each flag's kind is inferred from its
134
+ * default, and the returned `ref` holds the same-shaped record with resolved
135
+ * values (the defaults until each evaluation lands). A single `context` applies
136
+ * to every flag and may be reactive. This is the batched form of {@link useFlag}
137
+ * — one watcher manages one subscription per key.
138
+ */
139
+ declare const useFlags: <T extends Record<string, FlagValue>>(flags: T, context?: MaybeRefOrGetter<FlagContext | undefined>) => Readonly<Ref<T>>;
140
+ /**
141
+ * The reactive handle returned by {@link useMutation} — the Vue counterpart to
142
+ * React's `useMutation`, re-expressed with refs. The surface is identical across
143
+ * the Lunora adapters (`@lunora/solid`, `/svelte`): `data`/`error`/`pending` are
144
+ * refs you read in a template, `mutate` is an awaitable that resolves with the
145
+ * server value (or rejects). Per-call `optimistic` / `optimisticUpdate` options
146
+ * pass straight through to `client.mutation`.
147
+ */
148
+ interface MutationHandle<F extends FunctionReference> {
149
+ /** The latest invocation's resolved value, or `undefined` before the first success. */
150
+ data: Ref<ReturnOf<F> | undefined>;
151
+ /** The latest invocation's error, or `undefined`. */
152
+ error: Ref<Error | undefined>;
153
+ /** Invoke the mutation. Resolves with the server value; rejects on failure. */
154
+ mutate: (args: ArgsOf<F>, options?: MutationCallOptions<unknown, unknown, ArgsOf<F>>) => Promise<ReturnOf<F>>;
155
+ /** `true` while ANY invocation from this handle is in flight (ref-counted, so overlapping calls compose). */
156
+ pending: Ref<boolean>;
157
+ /** Clear the latest `data`/`error` back to idle. */
158
+ reset: () => void;
159
+ }
160
+ /**
161
+ * Returns a reactive {@link MutationHandle} for the given mutation reference —
162
+ * the Vue equivalent of React's `useMutation`.
163
+ *
164
+ * Optimistic updates stay client-owned: the `optimistic` / `optimisticUpdate`
165
+ * call options pass straight through to `client.mutation`, which applies and
166
+ * rolls them back against the Lunora subscription cache (Convex parity).
167
+ *
168
+ * `pending` is ref-counted across overlapping invocations of THIS handle, so it
169
+ * flips back to `false` only once every concurrent call has settled. The
170
+ * ref-counted pending + error-normalize orchestration is the shared
171
+ * `createMutationRunner` from `@lunora/client`; only the refs are
172
+ * adapter-specific.
173
+ */
174
+ declare const useMutation: <F extends FunctionReference>(function_: F) => MutationHandle<F>;
175
+ /** The args a paginated query exposes minus the framework-supplied page cursor. */
176
+ type PaginatedArgs<F extends FunctionReference> = Omit<ArgsOf<F>, "paginationOpts">;
177
+ /** The element type of the `page` array a paginated query returns. */
178
+ type PageItemOf<F extends FunctionReference> = ReturnOf<F> extends {
179
+ page: (infer T)[];
180
+ } ? T : unknown;
181
+ interface UsePaginatedQueryOptions {
182
+ /** Page size for the first page (and the default for `loadMore`). */
183
+ initialNumItems: number;
184
+ shardKey?: string;
185
+ }
186
+ interface UsePaginatedQueryResult<T> {
187
+ /** `true` while the first page or a `loadMore` page is in flight. */
188
+ isLoading: Ref<boolean>;
189
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
190
+ loadMore: (numberItems: number) => void;
191
+ /** Flattened items across every loaded page, in order. */
192
+ results: Ref<T[]>;
193
+ status: Ref<PaginationStatus>;
194
+ }
195
+ /**
196
+ * Subscribe to a reactively-paginated query and grow the feed page by page.
197
+ *
198
+ * The query function must accept a `paginationOpts: { numItems, cursor,
199
+ * endCursor }` arg and return a `PaginationResult`. Pages are tracked as an
200
+ * ordered list of stable boundary cursors; each loaded page is a live
201
+ * subscription over a FIXED `(lower, upper]` range. Inserting or deleting a row
202
+ * grows/shrinks the affected page without duplicating or skipping rows across
203
+ * boundaries.
204
+ *
205
+ * `loadMore` appends the next page off the open-ended tail's `continueCursor`;
206
+ * it is a no-op unless `status === "CanLoadMore"`. Background split/join
207
+ * maintenance keeps page sizes near `initialNumItems` as edits accumulate.
208
+ *
209
+ * Changing `fn`, the base `args`, `initialNumItems`, or `shardKey` resets the
210
+ * feed to its first page.
211
+ *
212
+ * Call inside `setup()` (or any active effect scope).
213
+ */
214
+ declare const usePaginatedQuery: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<"skip" | PaginatedArgs<F>>, options: UsePaginatedQueryOptions) => UsePaginatedQueryResult<PageItemOf<F>>;
215
+ interface UseInfiniteQueryOptions {
216
+ /** Page size for the first page (and the default for `fetchNextPage`). */
217
+ initialNumItems: number;
218
+ shardKey?: string;
219
+ }
220
+ interface UseInfiniteQueryResult<T> {
221
+ /** Request the next page. A no-op unless `status === "CanLoadMore"`. */
222
+ fetchNextPage: (numberItems?: number) => void;
223
+ /** `true` when the loaded tail reports it can load another page. */
224
+ hasNextPage: Ref<boolean>;
225
+ /** `true` while a `fetchNextPage` page (beyond the first) is in flight. */
226
+ isFetchingNextPage: Ref<boolean>;
227
+ /** `true` while the first page is in flight. */
228
+ isLoading: Ref<boolean>;
229
+ /** One inner array per loaded page, in order; unresolved pages are omitted. */
230
+ pages: Ref<T[][]>;
231
+ status: Ref<PaginationStatus>;
232
+ }
233
+ /**
234
+ * Subscribe to a reactively-paginated query and expose its pages discretely.
235
+ *
236
+ * Shares `usePaginatedQuery`'s reactive-pagination engine but keeps each page
237
+ * as its own inner array rather than flattening them, and adds the
238
+ * TanStack-Query-style `fetchNextPage` / `hasNextPage` / `isFetchingNextPage`
239
+ * shape.
240
+ *
241
+ * Call inside `setup()` (or any active effect scope).
242
+ */
243
+ declare const useInfiniteQuery: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<"skip" | PaginatedArgs<F>>, options: UseInfiniteQueryOptions) => UseInfiniteQueryResult<PageItemOf<F>>;
244
+ /**
245
+ * `usePresence` — collaborative-awareness composable, the client half of the
246
+ * `@lunora/server` `definePresence` preset.
247
+ *
248
+ * Drives the heartbeat mutation (on mount, interval, and tab re-focus) and
249
+ * subscribes to the live `listPresent` query for the given room.
250
+ *
251
+ * Call inside `setup()` (or any active effect scope).
252
+ */
253
+ /**
254
+ * A heartbeat mutation reference: takes `{ roomId, sessionId, data? }`.
255
+ */
256
+ type HeartbeatReference = FunctionReference<"mutation", {
257
+ data?: Record<string, unknown>;
258
+ roomId: string;
259
+ sessionId: string;
260
+ }>;
261
+ /**
262
+ * A listPresent query reference: takes `{ roomId }` and returns the array of
263
+ * present members.
264
+ */
265
+ type ListPresentReference = FunctionReference<"query", {
266
+ roomId: string;
267
+ }>;
268
+ interface UsePresenceOptions<H extends HeartbeatReference, L extends ListPresentReference> {
269
+ /** Awareness blob for the first heartbeat (selection, cursor, name, color…). */
270
+ data?: Record<string, unknown>;
271
+ /** The `api.*` reference for the presence heartbeat mutation. */
272
+ heartbeat: H;
273
+ /** Heartbeat cadence in ms. Defaults to 10s. */
274
+ intervalMs?: number;
275
+ /** The `api.*` reference for the presence listPresent query. */
276
+ listPresent: L;
277
+ /**
278
+ * Stable id for this presence row. Defaults to a fresh per-mount id.
279
+ * Pass a user/connection id to control deduping across tabs.
280
+ */
281
+ sessionId?: string;
282
+ /** Forwarded to the heartbeat mutation / listPresent subscription when sharding by room. */
283
+ shardKey?: string;
284
+ }
285
+ interface UsePresenceResult<L extends ListPresentReference> {
286
+ /** The present members for the room. `undefined` until the first push. */
287
+ present: ShallowRef<ReturnOf<L> | undefined>;
288
+ /** This mount's session id (generated when not supplied). */
289
+ sessionId: string;
290
+ /** Replace the awareness `data` sent with subsequent heartbeats, and heartbeat immediately. */
291
+ setData: (data: Record<string, unknown> | undefined) => void;
292
+ }
293
+ declare const usePresence: <H extends HeartbeatReference, L extends ListPresentReference>(roomId: string, options: UsePresenceOptions<H, L>) => UsePresenceResult<L>;
294
+ /**
295
+ * Open a live subscription against `client` for FIXED args and stream its values
296
+ * into a `ref`. The low-level primitive behind `hydratePreloaded` (whose args
297
+ * come from an immutable `Preloaded` token and never change); {@link useQuery}
298
+ * handles the reactive-args case separately.
299
+ *
300
+ * `client.subscribe` already dedupes by `(functionPath, args, shardKey)` and
301
+ * replays the last value synchronously, so multiple consumers of the same query
302
+ * ride one server-side registration. `seed` sets the ref's value synchronously
303
+ * before the subscription attaches, so the first read shows the SSR value with
304
+ * no loading flash.
305
+ *
306
+ * Teardown is wired to the active effect scope (`onScopeDispose`), so it fires
307
+ * on component unmount or `effectScope().stop()`. Call it inside `setup()` / an
308
+ * effect scope (as `hydratePreloaded` does); outside any scope there is nothing
309
+ * to own the subscription, so it would leak until the process exits — the
310
+ * `getCurrentScope` guard only avoids throwing, it does not auto-clean.
311
+ */
312
+ declare const subscribeToQuery: <F extends FunctionReference, T = ReturnOf<F>>(client: LunoraClient, function_: F, args: ArgsOf<F>, options?: {
313
+ seed?: T;
314
+ shardKey?: string;
315
+ }) => Ref<T | undefined>;
316
+ /**
317
+ * Subscribe to a server query and expose its latest value as a `ref`.
318
+ *
319
+ * The returned ref is `undefined` until the first server response lands, then
320
+ * updates on every delta the server pushes — the Vue-idiomatic equivalent of
321
+ * React's `useQuery`. `args` may be a plain value, a `ref`, or a getter: passing
322
+ * a reactive source makes the subscription reactive — when the args change the
323
+ * old subscription is torn down and a fresh one opens for the new args (matching
324
+ * `@lunora/react`/`@lunora/solid`). Pass `"skip"` (or a source resolving to
325
+ * `"skip"`) to short-circuit: no network call, no socket. The subscription tears
326
+ * down automatically when the owning component unmounts (or the effect scope
327
+ * stops).
328
+ *
329
+ * Call inside `setup()` (or any active effect scope). For SSR seeding with no
330
+ * loading flash, use `hydratePreloaded` instead.
331
+ */
332
+ declare const useQuery: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<ArgsOf<F> | "skip">, options?: UseQueryOptions) => Ref<ReturnOf<F> | undefined>;
333
+ interface UseRateLimitOptions {
334
+ /** Clock injection for tests. Defaults to `Date.now`. */
335
+ now?: () => number;
336
+ /**
337
+ * Re-render cadence in milliseconds while throttled, so `retryAfter` ticks
338
+ * down and `disabled` flips back automatically. Defaults to `1000`.
339
+ */
340
+ tickMs?: number;
341
+ }
342
+ interface UseRateLimitResult {
343
+ /** Would consuming `count` (default 1) succeed right now? Does not consume. */
344
+ check: (count?: number) => boolean;
345
+ /** Optimistically consume `count` (default 1) locally; mirrors the server algorithm. */
346
+ consume: (count?: number) => RateLimitStatus;
347
+ /** `true` while a single unit cannot be consumed — convenient for disabling a control. */
348
+ disabled: ComputedRef<boolean>;
349
+ /** `true` while a single unit can be consumed. */
350
+ ok: ComputedRef<boolean>;
351
+ /** Clear local accounting (e.g. after the server confirms a reset). */
352
+ reset: () => void;
353
+ /** Milliseconds until the next unit is available. `0` when `ok`. */
354
+ retryAfter: ComputedRef<number>;
355
+ }
356
+ /**
357
+ * Client-side mirror of a rate limit for instant UX — disable a button or show
358
+ * a countdown without a round-trip. It runs the same token-bucket / fixed-window
359
+ * math as `@lunora/ratelimit` on the server, so the prediction agrees with the
360
+ * authoritative check; the server remains the source of truth.
361
+ *
362
+ * `config` accepts a plain object, a `ref`, or a getter (`MaybeRefOrGetter`).
363
+ * When you pass a ref/getter it is tracked reactively — changing the config
364
+ * re-derives `status` (and the `ok` / `disabled` / `retryAfter` views) on the
365
+ * fly. A plain object keeps working unchanged; pass a stable reference (module
366
+ * constant) so the reactive derived values stay settled.
367
+ */
368
+ declare const useRateLimit: (config: MaybeRefOrGetter<RateLimitConfig>, options?: UseRateLimitOptions) => UseRateLimitResult;
369
+ interface UseSubscriptionResult<T> {
370
+ data: Ref<T | undefined>;
371
+ error: Ref<Error | undefined>;
372
+ }
373
+ /**
374
+ * Subscribe to a reactive server push stream. Returns `{ data, error }` refs
375
+ * that update whenever the server emits a new value. Passing `"skip"` as `args`
376
+ * (or a ref/getter that resolves to `"skip"`) tears down the subscription
377
+ * without unmounting.
378
+ *
379
+ * Unlike `useQuery`, which tracks the full reactive cache, `useSubscription`
380
+ * owns a single lightweight subscription and is suitable for ephemeral,
381
+ * high-frequency streams.
382
+ */
383
+ declare const useSubscription: <F extends FunctionReference>(function_: F, args: MaybeRefOrGetter<ArgsOf<F> | "skip">, options?: UseQueryOptions) => UseSubscriptionResult<ReturnOf<F>>;
384
+ export { AuthLoading, Authenticated, type FlagContext, type FlagValue, 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, useFlag, useFlags, useInfiniteQuery, useLunora, useMutation, usePaginatedQuery, usePresence, useQuery, useRateLimit, useSubscription };
package/dist/index.mjs ADDED
@@ -0,0 +1,12 @@
1
+ export { AuthLoading, Authenticated, Unauthenticated } from './packem_shared/AuthLoading-_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 { useFlag, useFlags } from './packem_shared/useFlag-yTJu5_q7.mjs';
7
+ export { useMutation } from './packem_shared/useMutation-DWubV5pv.mjs';
8
+ export { useInfiniteQuery, usePaginatedQuery } from './packem_shared/useInfiniteQuery-CEfbw7Nw.mjs';
9
+ export { usePresence } from './packem_shared/usePresence-BeN5xaZM.mjs';
10
+ export { subscribeToQuery, useQuery } from './packem_shared/subscribeToQuery-Cv5YL-SI.mjs';
11
+ export { useRateLimit } from './packem_shared/useRateLimit-DlPWjcX1.mjs';
12
+ 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 };
@@ -0,0 +1,98 @@
1
+ import { shallowRef, watch, toValue } from 'vue';
2
+ import { useLunora } from './LUNORA_INJECTION_KEY-DtFXLDQ_.mjs';
3
+
4
+ const compareKeys = (a, b) => {
5
+ if (a < b) {
6
+ return -1;
7
+ }
8
+ return a > b ? 1 : 0;
9
+ };
10
+ const stableStringify = (value) => {
11
+ if (value === void 0) {
12
+ return "null";
13
+ }
14
+ if (value === null || typeof value !== "object") {
15
+ return JSON.stringify(value);
16
+ }
17
+ if (Array.isArray(value)) {
18
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
19
+ }
20
+ const record = value;
21
+ const keys = Object.keys(record).toSorted(compareKeys);
22
+ const parts = [];
23
+ for (const key of keys) {
24
+ const raw = record[key];
25
+ if (raw === void 0) {
26
+ continue;
27
+ }
28
+ parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
29
+ }
30
+ return `{${parts.join(",")}}`;
31
+ };
32
+
33
+ const FLAGS_EVAL_PATH = "__lunora_flags__:eval";
34
+ const flagKind = (value) => {
35
+ const kind = typeof value;
36
+ if (kind === "boolean" || kind === "number" || kind === "string") {
37
+ return kind;
38
+ }
39
+ return "object";
40
+ };
41
+ const flagsReference = { __lunoraRef: FLAGS_EVAL_PATH };
42
+ const serializeContext = (context) => context === void 0 ? "" : stableStringify(context);
43
+ const useFlag = (key, defaultValue, context) => {
44
+ const client = useLunora();
45
+ const type = flagKind(defaultValue);
46
+ const value = shallowRef(defaultValue);
47
+ watch(
48
+ () => `${toValue(key)}\0${serializeContext(toValue(context))}`,
49
+ (_serialized, _previous, onCleanup) => {
50
+ const currentKey = toValue(key);
51
+ const currentContext = toValue(context);
52
+ value.value = defaultValue;
53
+ let unsubscribe;
54
+ try {
55
+ unsubscribe = client.subscribe(flagsReference, { context: currentContext, default: defaultValue, key: currentKey, type }, (next) => {
56
+ value.value = next;
57
+ });
58
+ } catch {
59
+ return;
60
+ }
61
+ onCleanup(unsubscribe);
62
+ },
63
+ { immediate: true }
64
+ );
65
+ return value;
66
+ };
67
+ const useFlags = (flags, context) => {
68
+ const client = useLunora();
69
+ const values = shallowRef(flags);
70
+ const spec = stableStringify(flags);
71
+ watch(
72
+ () => `${spec}\0${serializeContext(toValue(context))}`,
73
+ (_serialized, _previous, onCleanup) => {
74
+ const currentContext = toValue(context);
75
+ values.value = flags;
76
+ const unsubscribes = [];
77
+ for (const [key, defaultValue] of Object.entries(flags)) {
78
+ try {
79
+ unsubscribes.push(
80
+ client.subscribe(flagsReference, { context: currentContext, default: defaultValue, key, type: flagKind(defaultValue) }, (next) => {
81
+ values.value = { ...values.value, [key]: next };
82
+ })
83
+ );
84
+ } catch {
85
+ }
86
+ }
87
+ onCleanup(() => {
88
+ for (const unsubscribe of unsubscribes) {
89
+ unsubscribe();
90
+ }
91
+ });
92
+ },
93
+ { immediate: true }
94
+ );
95
+ return values;
96
+ };
97
+
98
+ export { useFlag, useFlags };