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