@korajs/react 0.4.0 → 0.5.0

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/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
+ import { KoraEventEmitter } from '@korajs/core';
1
2
  import { Store, CollectionRecord, QueryBuilder, CollectionAccessor } from '@korajs/store';
2
- import { SyncEngine, CursorInfo, SyncStatusInfo } from '@korajs/sync';
3
+ import { SyncStatusInfo, SyncEngine, CursorInfo, AwarenessUser } from '@korajs/sync';
3
4
  import { ReactNode } from 'react';
4
5
  import * as Y from 'yjs';
5
6
 
@@ -10,6 +11,12 @@ import * as Y from 'yjs';
10
11
  interface KoraAppLike {
11
12
  /** Resolves when the store is open and collections are ready. */
12
13
  ready: Promise<void>;
14
+ /** Framework event emitter (sync, store, merge, query). */
15
+ events?: KoraEventEmitter;
16
+ /** Sync control when sync is configured. */
17
+ sync?: {
18
+ subscribeStatus(listener: (status: SyncStatusInfo) => void): () => void;
19
+ } | null;
13
20
  /** Get the underlying Store instance. */
14
21
  getStore(): Store;
15
22
  /** Get the underlying SyncEngine instance. Null if sync not configured. */
@@ -25,6 +32,10 @@ interface KoraContextValue {
25
32
  syncEngine: SyncEngine | null;
26
33
  /** The KoraApp instance (when provided via app prop). */
27
34
  app: KoraAppLike | null;
35
+ /** App event emitter (when using app prop). */
36
+ events: KoraEventEmitter | null;
37
+ /** Event-driven sync status subscription from app.sync. */
38
+ subscribeSyncStatus: ((listener: (status: SyncStatusInfo) => void) => () => void) | null;
28
39
  }
29
40
  /**
30
41
  * Props for the KoraProvider component.
@@ -51,6 +62,25 @@ interface UseQueryOptions {
51
62
  /** Set to false to disable the subscription (query won't execute). Defaults to true. */
52
63
  enabled?: boolean;
53
64
  }
65
+ /**
66
+ * Options for optimistic updates and rollback in {@link useMutation}.
67
+ */
68
+ interface UseMutationOptions<TData, TArgs extends unknown[], TContext = void> {
69
+ /**
70
+ * Runs before the mutation. Return a context value for rollback.
71
+ */
72
+ onMutate?: (...args: TArgs) => TContext | Promise<TContext>;
73
+ /**
74
+ * Reverts optimistic changes when the mutation fails.
75
+ */
76
+ onRollback?: (context: TContext, ...args: TArgs) => void | Promise<void>;
77
+ /** Called when the mutation succeeds. */
78
+ onSuccess?: (data: TData, ...args: TArgs) => void;
79
+ /** Called when the mutation fails (after optional rollback). */
80
+ onError?: (error: Error, ...args: TArgs) => void;
81
+ /** Called after success or failure. */
82
+ onSettled?: (data: TData | undefined, error: Error | null, ...args: TArgs) => void;
83
+ }
54
84
  /**
55
85
  * Result from the useMutation hook.
56
86
  */
@@ -88,6 +118,10 @@ interface UseRichTextResult {
88
118
  error: Error | null;
89
119
  /** Remote collaborators' cursor positions in this field. Empty if no sync engine. */
90
120
  cursors: CursorInfo[];
121
+ /** Publish local cursor/selection to connected collaborators. No-op without sync. */
122
+ setCursor: (anchor: number, head: number) => void;
123
+ /** Clear local cursor presence for this field. */
124
+ clearCursor: () => void;
91
125
  }
92
126
 
93
127
  /**
@@ -159,26 +193,33 @@ declare function useQuery<T = CollectionRecord>(query: QueryBuilder<T>, options?
159
193
  /**
160
194
  * React hook for performing mutations against the local Kora store.
161
195
  *
162
- * Returns `mutate` for fire-and-forget usage (optimistic) and `mutateAsync`
163
- * for when you need to await the result. Tracks loading and error state.
196
+ * Returns `mutate` for fire-and-forget usage and `mutateAsync` when you need
197
+ * to await the result. Optional `onMutate` / `onRollback` support optimistic
198
+ * UI updates that revert if the mutation throws.
164
199
  *
165
200
  * @param mutationFn - An async function to execute (e.g., `app.todos.insert`)
201
+ * @param options - Optional optimistic/rollback and lifecycle callbacks
166
202
  * @returns Object with mutate, mutateAsync, isLoading, error, and reset
167
203
  *
168
204
  * @example
169
205
  * ```typescript
170
- * const { mutate } = useMutation(app.todos.insert)
171
- * mutate({ title: 'New todo' }) // fire-and-forget
206
+ * const { mutate } = useMutation(app.todos.insert, {
207
+ * onMutate: (data) => {
208
+ * const previous = todos
209
+ * setTodos((list) => [...list, { id: 'temp', ...data }])
210
+ * return previous
211
+ * },
212
+ * onRollback: (previous) => setTodos(previous),
213
+ * })
172
214
  * ```
173
215
  */
174
- declare function useMutation<TData, TArgs extends unknown[]>(mutationFn: (...args: TArgs) => Promise<TData>): UseMutationResult<TData, TArgs>;
216
+ declare function useMutation<TData, TArgs extends unknown[], TContext = void>(mutationFn: (...args: TArgs) => Promise<TData>, options?: UseMutationOptions<TData, TArgs, TContext>): UseMutationResult<TData, TArgs>;
175
217
 
176
218
  /**
177
219
  * React hook for monitoring the sync engine's connection status.
178
220
  *
179
- * Polls the SyncEngine at ~500ms intervals and re-renders only when
180
- * the status actually changes. Returns a default offline status when
181
- * no sync engine is configured.
221
+ * Subscribes to sync events via `app.sync.subscribeStatus` (or the sync engine
222
+ * emitter when using store-only mode) and re-renders only when status changes.
182
223
  *
183
224
  * @returns Current sync status information
184
225
  *
@@ -186,8 +227,6 @@ declare function useMutation<TData, TArgs extends unknown[]>(mutationFn: (...arg
186
227
  * ```typescript
187
228
  * const status = useSyncStatus()
188
229
  * // status.status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error'
189
- * // status.pendingOperations: number
190
- * // status.lastSyncedAt: number | null
191
230
  * ```
192
231
  */
193
232
  declare function useSyncStatus(): SyncStatusInfo;
@@ -207,9 +246,18 @@ declare function useSyncStatus(): SyncStatusInfo;
207
246
  */
208
247
  declare function useCollection(name: string): CollectionAccessor;
209
248
 
249
+ interface UseRichTextOptions {
250
+ /** Presence identity broadcast with cursor updates. */
251
+ user?: AwarenessUser;
252
+ /**
253
+ * Use the incremental Yjs doc channel for live edits (recommended for large documents).
254
+ * When omitted, the channel activates automatically once the snapshot exceeds the sync threshold.
255
+ */
256
+ useDocChannel?: boolean;
257
+ }
210
258
  /**
211
259
  * Binds a richtext field to a shared Yjs document for editor integration.
212
260
  */
213
- declare function useRichText(collectionName: string, recordId: string, fieldName: string): UseRichTextResult;
261
+ declare function useRichText(collectionName: string, recordId: string, fieldName: string, options?: UseRichTextOptions): UseRichTextResult;
214
262
 
215
- export { type KoraAppLike, type KoraContextValue, KoraProvider, type KoraProviderProps, type UseMutationResult, type UseQueryOptions, type UseRichTextResult, useApp, useCollection, useMutation, useQuery, useRichText, useSyncStatus };
263
+ export { type KoraAppLike, type KoraContextValue, KoraProvider, type KoraProviderProps, type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseRichTextOptions, type UseRichTextResult, useApp, useCollection, useMutation, useQuery, useRichText, useSyncStatus };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { KoraEventEmitter } from '@korajs/core';
1
2
  import { Store, CollectionRecord, QueryBuilder, CollectionAccessor } from '@korajs/store';
2
- import { SyncEngine, CursorInfo, SyncStatusInfo } from '@korajs/sync';
3
+ import { SyncStatusInfo, SyncEngine, CursorInfo, AwarenessUser } from '@korajs/sync';
3
4
  import { ReactNode } from 'react';
4
5
  import * as Y from 'yjs';
5
6
 
@@ -10,6 +11,12 @@ import * as Y from 'yjs';
10
11
  interface KoraAppLike {
11
12
  /** Resolves when the store is open and collections are ready. */
12
13
  ready: Promise<void>;
14
+ /** Framework event emitter (sync, store, merge, query). */
15
+ events?: KoraEventEmitter;
16
+ /** Sync control when sync is configured. */
17
+ sync?: {
18
+ subscribeStatus(listener: (status: SyncStatusInfo) => void): () => void;
19
+ } | null;
13
20
  /** Get the underlying Store instance. */
14
21
  getStore(): Store;
15
22
  /** Get the underlying SyncEngine instance. Null if sync not configured. */
@@ -25,6 +32,10 @@ interface KoraContextValue {
25
32
  syncEngine: SyncEngine | null;
26
33
  /** The KoraApp instance (when provided via app prop). */
27
34
  app: KoraAppLike | null;
35
+ /** App event emitter (when using app prop). */
36
+ events: KoraEventEmitter | null;
37
+ /** Event-driven sync status subscription from app.sync. */
38
+ subscribeSyncStatus: ((listener: (status: SyncStatusInfo) => void) => () => void) | null;
28
39
  }
29
40
  /**
30
41
  * Props for the KoraProvider component.
@@ -51,6 +62,25 @@ interface UseQueryOptions {
51
62
  /** Set to false to disable the subscription (query won't execute). Defaults to true. */
52
63
  enabled?: boolean;
53
64
  }
65
+ /**
66
+ * Options for optimistic updates and rollback in {@link useMutation}.
67
+ */
68
+ interface UseMutationOptions<TData, TArgs extends unknown[], TContext = void> {
69
+ /**
70
+ * Runs before the mutation. Return a context value for rollback.
71
+ */
72
+ onMutate?: (...args: TArgs) => TContext | Promise<TContext>;
73
+ /**
74
+ * Reverts optimistic changes when the mutation fails.
75
+ */
76
+ onRollback?: (context: TContext, ...args: TArgs) => void | Promise<void>;
77
+ /** Called when the mutation succeeds. */
78
+ onSuccess?: (data: TData, ...args: TArgs) => void;
79
+ /** Called when the mutation fails (after optional rollback). */
80
+ onError?: (error: Error, ...args: TArgs) => void;
81
+ /** Called after success or failure. */
82
+ onSettled?: (data: TData | undefined, error: Error | null, ...args: TArgs) => void;
83
+ }
54
84
  /**
55
85
  * Result from the useMutation hook.
56
86
  */
@@ -88,6 +118,10 @@ interface UseRichTextResult {
88
118
  error: Error | null;
89
119
  /** Remote collaborators' cursor positions in this field. Empty if no sync engine. */
90
120
  cursors: CursorInfo[];
121
+ /** Publish local cursor/selection to connected collaborators. No-op without sync. */
122
+ setCursor: (anchor: number, head: number) => void;
123
+ /** Clear local cursor presence for this field. */
124
+ clearCursor: () => void;
91
125
  }
92
126
 
93
127
  /**
@@ -159,26 +193,33 @@ declare function useQuery<T = CollectionRecord>(query: QueryBuilder<T>, options?
159
193
  /**
160
194
  * React hook for performing mutations against the local Kora store.
161
195
  *
162
- * Returns `mutate` for fire-and-forget usage (optimistic) and `mutateAsync`
163
- * for when you need to await the result. Tracks loading and error state.
196
+ * Returns `mutate` for fire-and-forget usage and `mutateAsync` when you need
197
+ * to await the result. Optional `onMutate` / `onRollback` support optimistic
198
+ * UI updates that revert if the mutation throws.
164
199
  *
165
200
  * @param mutationFn - An async function to execute (e.g., `app.todos.insert`)
201
+ * @param options - Optional optimistic/rollback and lifecycle callbacks
166
202
  * @returns Object with mutate, mutateAsync, isLoading, error, and reset
167
203
  *
168
204
  * @example
169
205
  * ```typescript
170
- * const { mutate } = useMutation(app.todos.insert)
171
- * mutate({ title: 'New todo' }) // fire-and-forget
206
+ * const { mutate } = useMutation(app.todos.insert, {
207
+ * onMutate: (data) => {
208
+ * const previous = todos
209
+ * setTodos((list) => [...list, { id: 'temp', ...data }])
210
+ * return previous
211
+ * },
212
+ * onRollback: (previous) => setTodos(previous),
213
+ * })
172
214
  * ```
173
215
  */
174
- declare function useMutation<TData, TArgs extends unknown[]>(mutationFn: (...args: TArgs) => Promise<TData>): UseMutationResult<TData, TArgs>;
216
+ declare function useMutation<TData, TArgs extends unknown[], TContext = void>(mutationFn: (...args: TArgs) => Promise<TData>, options?: UseMutationOptions<TData, TArgs, TContext>): UseMutationResult<TData, TArgs>;
175
217
 
176
218
  /**
177
219
  * React hook for monitoring the sync engine's connection status.
178
220
  *
179
- * Polls the SyncEngine at ~500ms intervals and re-renders only when
180
- * the status actually changes. Returns a default offline status when
181
- * no sync engine is configured.
221
+ * Subscribes to sync events via `app.sync.subscribeStatus` (or the sync engine
222
+ * emitter when using store-only mode) and re-renders only when status changes.
182
223
  *
183
224
  * @returns Current sync status information
184
225
  *
@@ -186,8 +227,6 @@ declare function useMutation<TData, TArgs extends unknown[]>(mutationFn: (...arg
186
227
  * ```typescript
187
228
  * const status = useSyncStatus()
188
229
  * // status.status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error'
189
- * // status.pendingOperations: number
190
- * // status.lastSyncedAt: number | null
191
230
  * ```
192
231
  */
193
232
  declare function useSyncStatus(): SyncStatusInfo;
@@ -207,9 +246,18 @@ declare function useSyncStatus(): SyncStatusInfo;
207
246
  */
208
247
  declare function useCollection(name: string): CollectionAccessor;
209
248
 
249
+ interface UseRichTextOptions {
250
+ /** Presence identity broadcast with cursor updates. */
251
+ user?: AwarenessUser;
252
+ /**
253
+ * Use the incremental Yjs doc channel for live edits (recommended for large documents).
254
+ * When omitted, the channel activates automatically once the snapshot exceeds the sync threshold.
255
+ */
256
+ useDocChannel?: boolean;
257
+ }
210
258
  /**
211
259
  * Binds a richtext field to a shared Yjs document for editor integration.
212
260
  */
213
- declare function useRichText(collectionName: string, recordId: string, fieldName: string): UseRichTextResult;
261
+ declare function useRichText(collectionName: string, recordId: string, fieldName: string, options?: UseRichTextOptions): UseRichTextResult;
214
262
 
215
- export { type KoraAppLike, type KoraContextValue, KoraProvider, type KoraProviderProps, type UseMutationResult, type UseQueryOptions, type UseRichTextResult, useApp, useCollection, useMutation, useQuery, useRichText, useSyncStatus };
263
+ export { type KoraAppLike, type KoraContextValue, KoraProvider, type KoraProviderProps, type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseRichTextOptions, type UseRichTextResult, useApp, useCollection, useMutation, useQuery, useRichText, useSyncStatus };