@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.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/index.cjs +626 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +322 -0
- package/dist/index.d.ts +322 -0
- package/dist/index.js +595 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|