@korajs/react 0.0.0-canary-20261008174129

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,322 @@
1
+ import { KoraAppLike as KoraAppLike$1, KoraContextValue as KoraContextValue$1, UseQueryOptions as UseQueryOptions$1, UseMutationOptions as UseMutationOptions$1, UseMutationResultBase, AuthSyncBinding, AuthSyncState } from '@korajs/core/bindings';
2
+ import { Store, QueryStoreCache, CollectionRecord, QueryBuilder, CollectionAccessor } from '@korajs/store';
3
+ import { SyncEngine, CursorInfo, SyncStatusInfo, AwarenessUser, AwarenessState } from '@korajs/sync';
4
+ import { ReactNode } from 'react';
5
+ import * as Y from 'yjs';
6
+
7
+ type KoraAppLike = KoraAppLike$1<Store, SyncEngine, QueryStoreCache>;
8
+ type KoraContextValue = KoraContextValue$1<Store, SyncEngine, QueryStoreCache>;
9
+ /** Options for {@link useQuery} and {@link useQueryState}. */
10
+ interface UseQueryOptions extends UseQueryOptions$1 {
11
+ /**
12
+ * When true (default), `useQuery` throws a failed query to the nearest error
13
+ * boundary. When false it keeps returning the last good rows; read the error with
14
+ * `useQueryState`. Ignored by `useQueryState`, which always returns the error.
15
+ */
16
+ throwOnError?: boolean;
17
+ }
18
+ /** Result of `useQueryState`. */
19
+ interface UseQueryStateResult<T> {
20
+ /** The current rows (the last good rows while `error` is set). */
21
+ data: readonly T[];
22
+ /** The query's failure, or null. Cleared when results flow again. */
23
+ error: Error | null;
24
+ /** False until the first result for the current query arrived (always false on the server). */
25
+ ready: boolean;
26
+ }
27
+ type UseMutationOptions<TData, TArgs extends unknown[], TContext = void> = UseMutationOptions$1<TData, TArgs, TContext>;
28
+ /**
29
+ * Props for the KoraProvider component.
30
+ *
31
+ * Accepts either an `app` instance (recommended, from createApp()) or
32
+ * explicit `store` + `syncEngine` props (advanced use case).
33
+ */
34
+ interface KoraProviderProps {
35
+ app?: KoraAppLike;
36
+ store?: Store;
37
+ syncEngine?: SyncEngine | null;
38
+ fallback?: ReactNode;
39
+ children?: ReactNode;
40
+ }
41
+ interface UseMutationResult<TData, TArgs extends unknown[]> extends UseMutationResultBase<TData, TArgs> {
42
+ isLoading: boolean;
43
+ error: Error | null;
44
+ }
45
+ interface UseRichTextResult {
46
+ doc: Y.Doc;
47
+ text: Y.Text;
48
+ undo: () => void;
49
+ redo: () => void;
50
+ canUndo: boolean;
51
+ canRedo: boolean;
52
+ ready: boolean;
53
+ error: Error | null;
54
+ cursors: CursorInfo[];
55
+ setCursor: (anchor: number, head: number) => void;
56
+ clearCursor: () => void;
57
+ /**
58
+ * True while local edits are not saved yet (a save pending or refused; see
59
+ * `error`). The edits stay in the document and the next edit saves them again.
60
+ */
61
+ hasUnsavedChanges: boolean;
62
+ /** Save the document now, for example after a refused save. */
63
+ retrySave: () => Promise<void>;
64
+ /** The full document state while edits are unsaved (for a recovery copy), else null. */
65
+ getUnsavedState: () => Uint8Array | null;
66
+ }
67
+
68
+ /**
69
+ * Provides Kora store and optional sync engine to all child components.
70
+ * Must wrap any component that uses Kora hooks (useQuery, useMutation, etc.).
71
+ */
72
+ declare function KoraProvider({ app, store, syncEngine, fallback, children, }: KoraProviderProps): ReactNode;
73
+
74
+ interface AuthBoundKoraProviderProps {
75
+ authClient: AuthSyncBinding;
76
+ createApp: (session: Extract<AuthSyncState, {
77
+ state: 'authenticated';
78
+ }>) => KoraAppLike & {
79
+ close(): Promise<void>;
80
+ };
81
+ signedOut?: ReactNode;
82
+ /**
83
+ * Rendered instead of the app while the session is locked (offline longer
84
+ * than the auth client's `maxOfflineGraceMs`, or the device clock moved
85
+ * backwards). The app and its local data stay open underneath; nothing is
86
+ * wiped. Defaults to `signedOut`.
87
+ */
88
+ locked?: ReactNode;
89
+ fallback?: ReactNode;
90
+ error?: (context: AuthBoundKoraErrorContext) => ReactNode;
91
+ children?: ReactNode;
92
+ }
93
+ interface AuthBoundKoraSession {
94
+ /** The authenticated identity only. Access tokens are deliberately omitted. */
95
+ userId: string;
96
+ }
97
+ interface AuthBoundKoraInitializationError {
98
+ /** Stable Kora/DOM error code suitable for application-owned copy and recovery UI. */
99
+ code: string;
100
+ name: string;
101
+ message: string;
102
+ /** Sanitized primitive metadata. Credential-shaped keys are always removed. */
103
+ metadata: Readonly<Record<string, string | number | boolean | null>>;
104
+ /** Sanitized Error for logging; custom credential-bearing properties are omitted. */
105
+ cause: Error;
106
+ }
107
+ interface AuthBoundKoraErrorContext {
108
+ error: AuthBoundKoraInitializationError;
109
+ retry(): void;
110
+ session: AuthBoundKoraSession;
111
+ }
112
+ /** Classify initialization failures without requiring applications to parse copy. */
113
+ declare function classifyKoraInitializationError(error: unknown): AuthBoundKoraInitializationError;
114
+ /**
115
+ * Owns the security-sensitive app lifetime for shared-browser applications.
116
+ * The previous app is fully closed before a different user's app is created,
117
+ * and no stale provider tree is rendered across that boundary.
118
+ */
119
+ declare function AuthBoundKoraProvider({ authClient, createApp, signedOut, locked, fallback, error: renderError, children, }: AuthBoundKoraProviderProps): ReactNode;
120
+
121
+ /**
122
+ * React hook that returns the Kora app instance from context.
123
+ *
124
+ * Use the generic parameter to cast to your typed app for full type inference:
125
+ *
126
+ * ```typescript
127
+ * // In your app setup:
128
+ * export const app = createApp({ schema: mySchema })
129
+ * export type App = typeof app
130
+ *
131
+ * // In components:
132
+ * const app = useApp<App>()
133
+ * app.todos.insert({ title: 'Hello' }) // fully typed
134
+ * const todos = useQuery(app.todos.where({ completed: false }))
135
+ * ```
136
+ *
137
+ * Requires `KoraProvider` to be initialized with the `app` prop.
138
+ * Throws if used outside of `KoraProvider` or without an `app` prop.
139
+ *
140
+ * @returns The KoraApp instance, typed as `T`
141
+ */
142
+ declare function useApp<T extends KoraAppLike = KoraAppLike>(): T;
143
+
144
+ /**
145
+ * React hook for reactive queries against the local Kora store.
146
+ *
147
+ * Returns the current rows synchronously (no loading state for local data) and
148
+ * re-renders only when the result set changes. The array keeps its identity while
149
+ * the result is unchanged. On the server (`renderToString`, Next.js App Router) and
150
+ * during hydration it returns `[]`; the rows arrive right after hydration.
151
+ *
152
+ * A query that fails (for example a where/orderBy on an unknown field) is thrown to
153
+ * the nearest React error boundary, so the failure is visible instead of an empty
154
+ * list. Pass `throwOnError: false`, or use {@link useQueryState}, to handle it inline.
155
+ *
156
+ * @param query - A query builder, for example `app.todos.where({ completed: false })`
157
+ * @param options - `enabled` (default true) and `throwOnError` (default true)
158
+ * @returns The current result rows
159
+ *
160
+ * @example
161
+ * ```tsx
162
+ * const todos = useQuery(app.todos.where({ completed: false }).orderBy('createdAt'))
163
+ * ```
164
+ */
165
+ declare function useQuery<T = CollectionRecord>(query: QueryBuilder<T>, options?: UseQueryOptions): readonly T[];
166
+ /**
167
+ * Like {@link useQuery}, but returns the error instead of throwing it.
168
+ *
169
+ * @param query - A query builder
170
+ * @param options - `enabled` (default true); `throwOnError` is ignored
171
+ * @returns `{ data, error, ready }`, the same object until one of them changes.
172
+ * `ready` is false until the first result for the current query has arrived.
173
+ *
174
+ * @example
175
+ * ```tsx
176
+ * const { data: todos, error } = useQueryState(app.todos.where({ completed: false }))
177
+ * if (error) return <p role="alert">{error.message}</p>
178
+ * ```
179
+ */
180
+ declare function useQueryState<T = CollectionRecord>(query: QueryBuilder<T>, options?: UseQueryOptions): UseQueryStateResult<T>;
181
+
182
+ /**
183
+ * React hook for performing mutations against the local Kora store.
184
+ *
185
+ * `mutate`, `mutateAsync` and `reset` keep their identity for the component's lifetime
186
+ * (safe in effect deps and as memoized props), and the result object only changes when
187
+ * `isLoading` or `error` does. The latest `mutationFn` and `options` are always used.
188
+ *
189
+ * @param mutationFn - The mutation to run, for example `app.todos.insert`
190
+ * @param options - Optional lifecycle callbacks (`onMutate`, `onSuccess`, `onError`, `onSettled`)
191
+ * @returns `{ mutate, mutateAsync, isLoading, error, reset }`
192
+ *
193
+ * @example
194
+ * ```tsx
195
+ * const { mutate: addTodo, isLoading } = useMutation(app.todos.insert)
196
+ * return <button disabled={isLoading} onClick={() => addTodo({ title: 'New' })}>Add</button>
197
+ * ```
198
+ */
199
+ declare function useMutation<TData, TArgs extends unknown[], TContext = void>(mutationFn: (...args: TArgs) => Promise<TData>, options?: UseMutationOptions<TData, TArgs, TContext>): UseMutationResult<TData, TArgs>;
200
+
201
+ /**
202
+ * React hook for monitoring the sync engine's connection status.
203
+ *
204
+ * Re-renders only when the status payload changes. The returned object, and its nested
205
+ * `heldNodes`, `initialSync` and `blockedFailure` values, keep their identity while
206
+ * unchanged, so they are safe as effect or memo dependencies. Beyond `status` and
207
+ * `pendingOperations` it reports `heldOperations` / `heldNodes` (writes of another
208
+ * user waiting on this device), `localDurability` (`'degraded'` when the local
209
+ * database cannot persist) and `serverProtocolVersion` / `protocolDeprecated`.
210
+ *
211
+ * @returns The current {@link SyncStatusInfo}
212
+ *
213
+ * @example
214
+ * ```tsx
215
+ * const { status, pendingOperations, localDurability } = useSyncStatus()
216
+ * ```
217
+ */
218
+ declare function useSyncStatus(): SyncStatusInfo;
219
+
220
+ /**
221
+ * The collections an app exposes, read from its `collections` namespace. Purely
222
+ * structural, so it follows whatever accessor types `createApp` infers from the schema.
223
+ */
224
+ type AppCollections<TApp> = TApp extends {
225
+ collections: infer C;
226
+ } ? C : never;
227
+ /** The collection names of an app, as string literals. */
228
+ type AppCollectionName<TApp> = Extract<keyof AppCollections<TApp>, string>;
229
+ /** The record type of a collection accessor, inferred from what its queries return. */
230
+ type AccessorRecord<TAccessor> = TAccessor extends {
231
+ where(...args: never[]): QueryBuilder<infer R>;
232
+ } ? R : CollectionRecord;
233
+ /** The record type of collection `N` of an app. */
234
+ type AppRecord<TApp, N extends AppCollectionName<TApp>> = AccessorRecord<AppCollections<TApp>[N]>;
235
+ /**
236
+ * Hooks bound to one app type, returned by {@link createKoraHooks}.
237
+ */
238
+ interface KoraHooks<TApp> {
239
+ /** The app from the nearest `<KoraProvider app={app}>`, typed as `TApp`. */
240
+ useApp: () => TApp;
241
+ /**
242
+ * The typed accessor of one collection: `insert`, `update` and `where` are checked
243
+ * against the schema. The accessor keeps its identity across renders.
244
+ */
245
+ useCollection: <N extends AppCollectionName<TApp>>(name: N) => AppCollections<TApp>[N];
246
+ /** {@link useQuery}: rows are typed from the query builder. */
247
+ useQuery: typeof useQuery;
248
+ /** {@link useQueryState}: rows are typed from the query builder. */
249
+ useQueryState: typeof useQueryState;
250
+ /** {@link useMutation}. */
251
+ useMutation: typeof useMutation;
252
+ /** {@link useSyncStatus}. */
253
+ useSyncStatus: typeof useSyncStatus;
254
+ }
255
+ /**
256
+ * Creates React hooks typed for one app, so components get schema-checked
257
+ * collection names, inserts, updates and rows without passing generics around.
258
+ * Call it once next to `createApp` and import the hooks from there. Nothing runs at
259
+ * call time; the hooks read the app from `<KoraProvider app={app}>`.
260
+ *
261
+ * @returns {@link KoraHooks} for `TApp`
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * // kora.ts
266
+ * export const app = createApp({ schema })
267
+ * export const { useCollection, useQuery, useMutation } = createKoraHooks<typeof app>()
268
+ *
269
+ * // TodoList.tsx
270
+ * const todos = useCollection('todos') // 'todoz' is a type error
271
+ * const rows = useQuery(todos.where({ completed: false }))
272
+ * const { mutate: add } = useMutation(todos.insert)
273
+ * ```
274
+ */
275
+ declare function createKoraHooks<TApp extends KoraAppLike>(): KoraHooks<TApp>;
276
+
277
+ /**
278
+ * React hook that returns a CollectionAccessor for the given collection name.
279
+ * Convenience hook for accessing a collection without going through the store directly.
280
+ *
281
+ * @param name - The collection name (must match a collection in the schema)
282
+ * @returns CollectionAccessor with insert, findById, update, delete, and where methods
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * const todos = useCollection('todos')
287
+ * await todos.insert({ title: 'New todo' })
288
+ * ```
289
+ */
290
+ declare function useCollection(name: string): CollectionAccessor;
291
+
292
+ interface UseRichTextOptions {
293
+ user?: AwarenessUser;
294
+ useDocChannel?: boolean;
295
+ }
296
+ /**
297
+ * Binds a richtext field to a shared Yjs document for editor integration.
298
+ */
299
+ declare function useRichText(collectionName: string, recordId: string, fieldName: string, options?: UseRichTextOptions): UseRichTextResult;
300
+
301
+ /**
302
+ * Sets the local user's collaborative presence state.
303
+ *
304
+ * Automatically cleans up presence on unmount.
305
+ */
306
+ declare function usePresence(user: {
307
+ name: string;
308
+ color: string;
309
+ avatar?: string;
310
+ } | null): void;
311
+
312
+ /**
313
+ * Returns all currently connected collaborators' awareness states.
314
+ *
315
+ * Excludes the local user — only returns remote peers. Safe under server rendering
316
+ * (`renderToString`, Next.js App Router): the server and the hydration pass see `[]`.
317
+ *
318
+ * @returns The remote peers' awareness states; the same array until they change
319
+ */
320
+ declare function useCollaborators(): AwarenessState[];
321
+
322
+ export { type AccessorRecord, type AppCollectionName, type AppCollections, type AppRecord, type AuthBoundKoraErrorContext, type AuthBoundKoraInitializationError, AuthBoundKoraProvider, type AuthBoundKoraProviderProps, type AuthBoundKoraSession, type KoraAppLike, type KoraContextValue, type KoraHooks, KoraProvider, type KoraProviderProps, type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseQueryStateResult, type UseRichTextOptions, type UseRichTextResult, classifyKoraInitializationError, createKoraHooks, useApp, useCollaborators, useCollection, useMutation, usePresence, useQuery, useQueryState, useRichText, useSyncStatus };