@korajs/react 0.1.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.cjs +441 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +188 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.js +399 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
KoraProvider: () => KoraProvider,
|
|
34
|
+
useCollection: () => useCollection,
|
|
35
|
+
useMutation: () => useMutation,
|
|
36
|
+
useQuery: () => useQuery,
|
|
37
|
+
useRichText: () => useRichText,
|
|
38
|
+
useSyncStatus: () => useSyncStatus
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/context/kora-context.ts
|
|
43
|
+
var import_react = require("react");
|
|
44
|
+
var KoraContext = (0, import_react.createContext)(null);
|
|
45
|
+
function KoraProvider({
|
|
46
|
+
app,
|
|
47
|
+
store,
|
|
48
|
+
syncEngine,
|
|
49
|
+
fallback,
|
|
50
|
+
children
|
|
51
|
+
}) {
|
|
52
|
+
const [resolvedStore, setResolvedStore] = (0, import_react.useState)(
|
|
53
|
+
store ?? null
|
|
54
|
+
);
|
|
55
|
+
const [resolvedSync, setResolvedSync] = (0, import_react.useState)(
|
|
56
|
+
syncEngine ?? null
|
|
57
|
+
);
|
|
58
|
+
const [ready, setReady] = (0, import_react.useState)(!app);
|
|
59
|
+
(0, import_react.useEffect)(() => {
|
|
60
|
+
if (!app) return;
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
app.ready.then(() => {
|
|
63
|
+
if (cancelled) return;
|
|
64
|
+
setResolvedStore(app.getStore());
|
|
65
|
+
setResolvedSync(app.getSyncEngine());
|
|
66
|
+
setReady(true);
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
cancelled = true;
|
|
70
|
+
};
|
|
71
|
+
}, [app]);
|
|
72
|
+
if (!ready) {
|
|
73
|
+
return fallback ?? null;
|
|
74
|
+
}
|
|
75
|
+
if (!resolvedStore) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'KoraProvider requires either an "app" or "store" prop. Pass a KoraApp from createApp() or a Store instance.'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const value = {
|
|
81
|
+
store: resolvedStore,
|
|
82
|
+
syncEngine: resolvedSync
|
|
83
|
+
};
|
|
84
|
+
return (0, import_react.createElement)(KoraContext.Provider, { value }, children);
|
|
85
|
+
}
|
|
86
|
+
function useKoraContext() {
|
|
87
|
+
const context = (0, import_react.useContext)(KoraContext);
|
|
88
|
+
if (context === null) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"useKoraContext must be used within a <KoraProvider>. Wrap your component tree with <KoraProvider store={store}>."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return context;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/hooks/use-query.ts
|
|
97
|
+
var import_react2 = require("react");
|
|
98
|
+
|
|
99
|
+
// src/query-store/query-store.ts
|
|
100
|
+
var EMPTY_ARRAY = Object.freeze([]);
|
|
101
|
+
var QueryStore = class {
|
|
102
|
+
snapshot = EMPTY_ARRAY;
|
|
103
|
+
listeners = /* @__PURE__ */ new Set();
|
|
104
|
+
unsubscribeQuery = null;
|
|
105
|
+
destroyed = false;
|
|
106
|
+
constructor(queryBuilder) {
|
|
107
|
+
this.unsubscribeQuery = queryBuilder.subscribe((results) => {
|
|
108
|
+
if (this.destroyed) return;
|
|
109
|
+
const newSnapshot = Object.freeze([...results]);
|
|
110
|
+
this.snapshot = newSnapshot;
|
|
111
|
+
this.notifyListeners();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe to snapshot changes. Compatible with useSyncExternalStore.
|
|
116
|
+
*
|
|
117
|
+
* @returns Unsubscribe function
|
|
118
|
+
*/
|
|
119
|
+
subscribe = (onStoreChange) => {
|
|
120
|
+
this.listeners.add(onStoreChange);
|
|
121
|
+
return () => {
|
|
122
|
+
this.listeners.delete(onStoreChange);
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Get the current snapshot synchronously. Compatible with useSyncExternalStore.
|
|
127
|
+
* Returns EMPTY_ARRAY before the first async fetch completes.
|
|
128
|
+
*/
|
|
129
|
+
getSnapshot = () => {
|
|
130
|
+
return this.snapshot;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Clean up the underlying subscription and release resources.
|
|
134
|
+
*/
|
|
135
|
+
destroy() {
|
|
136
|
+
this.destroyed = true;
|
|
137
|
+
if (this.unsubscribeQuery) {
|
|
138
|
+
this.unsubscribeQuery();
|
|
139
|
+
this.unsubscribeQuery = null;
|
|
140
|
+
}
|
|
141
|
+
this.listeners.clear();
|
|
142
|
+
this.snapshot = EMPTY_ARRAY;
|
|
143
|
+
}
|
|
144
|
+
notifyListeners() {
|
|
145
|
+
for (const listener of this.listeners) {
|
|
146
|
+
listener();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/hooks/use-query.ts
|
|
152
|
+
var EMPTY_ARRAY2 = Object.freeze([]);
|
|
153
|
+
var noopSubscribe = (_onStoreChange) => {
|
|
154
|
+
return () => {
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
function useQuery(query, options) {
|
|
158
|
+
const enabled = options?.enabled !== false;
|
|
159
|
+
const descriptorKey = JSON.stringify(query.getDescriptor());
|
|
160
|
+
const queryStoreRef = (0, import_react2.useRef)(null);
|
|
161
|
+
const prevKeyRef = (0, import_react2.useRef)(null);
|
|
162
|
+
const queryStore = (0, import_react2.useMemo)(() => {
|
|
163
|
+
if (!enabled) {
|
|
164
|
+
if (queryStoreRef.current) {
|
|
165
|
+
queryStoreRef.current.destroy();
|
|
166
|
+
queryStoreRef.current = null;
|
|
167
|
+
}
|
|
168
|
+
prevKeyRef.current = null;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (prevKeyRef.current !== descriptorKey) {
|
|
172
|
+
if (queryStoreRef.current) {
|
|
173
|
+
queryStoreRef.current.destroy();
|
|
174
|
+
}
|
|
175
|
+
const newStore = new QueryStore(query);
|
|
176
|
+
queryStoreRef.current = newStore;
|
|
177
|
+
prevKeyRef.current = descriptorKey;
|
|
178
|
+
return newStore;
|
|
179
|
+
}
|
|
180
|
+
return queryStoreRef.current;
|
|
181
|
+
}, [descriptorKey, enabled, query]);
|
|
182
|
+
(0, import_react2.useEffect)(() => {
|
|
183
|
+
return () => {
|
|
184
|
+
if (queryStoreRef.current) {
|
|
185
|
+
queryStoreRef.current.destroy();
|
|
186
|
+
queryStoreRef.current = null;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}, []);
|
|
190
|
+
const disabledGetSnapshot = () => EMPTY_ARRAY2;
|
|
191
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
192
|
+
queryStore ? queryStore.subscribe : noopSubscribe,
|
|
193
|
+
queryStore ? queryStore.getSnapshot : disabledGetSnapshot
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/hooks/use-mutation.ts
|
|
198
|
+
var import_react3 = require("react");
|
|
199
|
+
function useMutation(mutationFn) {
|
|
200
|
+
const [state, setState] = (0, import_react3.useState)({
|
|
201
|
+
isLoading: false,
|
|
202
|
+
error: null
|
|
203
|
+
});
|
|
204
|
+
const mountedRef = (0, import_react3.useRef)(true);
|
|
205
|
+
(0, import_react3.useEffect)(() => {
|
|
206
|
+
mountedRef.current = true;
|
|
207
|
+
return () => {
|
|
208
|
+
mountedRef.current = false;
|
|
209
|
+
};
|
|
210
|
+
}, []);
|
|
211
|
+
const fnRef = (0, import_react3.useRef)(mutationFn);
|
|
212
|
+
fnRef.current = mutationFn;
|
|
213
|
+
const mutateAsync = (0, import_react3.useCallback)(async (...args) => {
|
|
214
|
+
if (mountedRef.current) {
|
|
215
|
+
setState({ isLoading: true, error: null });
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const result = await fnRef.current(...args);
|
|
219
|
+
if (mountedRef.current) {
|
|
220
|
+
setState({ isLoading: false, error: null });
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
225
|
+
if (mountedRef.current) {
|
|
226
|
+
setState({ isLoading: false, error });
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}, []);
|
|
231
|
+
const mutate = (0, import_react3.useCallback)(
|
|
232
|
+
(...args) => {
|
|
233
|
+
mutateAsync(...args).catch(() => {
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
[mutateAsync]
|
|
237
|
+
);
|
|
238
|
+
const reset = (0, import_react3.useCallback)(() => {
|
|
239
|
+
if (mountedRef.current) {
|
|
240
|
+
setState({ isLoading: false, error: null });
|
|
241
|
+
}
|
|
242
|
+
}, []);
|
|
243
|
+
return {
|
|
244
|
+
mutate,
|
|
245
|
+
mutateAsync,
|
|
246
|
+
isLoading: state.isLoading,
|
|
247
|
+
error: state.error,
|
|
248
|
+
reset
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/hooks/use-sync-status.ts
|
|
253
|
+
var import_react4 = require("react");
|
|
254
|
+
var POLL_INTERVAL_MS = 500;
|
|
255
|
+
var OFFLINE_STATUS = Object.freeze({
|
|
256
|
+
status: "offline",
|
|
257
|
+
pendingOperations: 0,
|
|
258
|
+
lastSyncedAt: null
|
|
259
|
+
});
|
|
260
|
+
function useSyncStatus() {
|
|
261
|
+
const { syncEngine } = useKoraContext();
|
|
262
|
+
const snapshotRef = (0, import_react4.useRef)(OFFLINE_STATUS);
|
|
263
|
+
const serializedRef = (0, import_react4.useRef)(JSON.stringify(OFFLINE_STATUS));
|
|
264
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
265
|
+
(onStoreChange) => {
|
|
266
|
+
if (!syncEngine) return () => {
|
|
267
|
+
};
|
|
268
|
+
const intervalId = setInterval(() => {
|
|
269
|
+
const newStatus = syncEngine.getStatus();
|
|
270
|
+
const newSerialized = JSON.stringify(newStatus);
|
|
271
|
+
if (newSerialized !== serializedRef.current) {
|
|
272
|
+
snapshotRef.current = newStatus;
|
|
273
|
+
serializedRef.current = newSerialized;
|
|
274
|
+
onStoreChange();
|
|
275
|
+
}
|
|
276
|
+
}, POLL_INTERVAL_MS);
|
|
277
|
+
const initialStatus = syncEngine.getStatus();
|
|
278
|
+
const initialSerialized = JSON.stringify(initialStatus);
|
|
279
|
+
if (initialSerialized !== serializedRef.current) {
|
|
280
|
+
snapshotRef.current = initialStatus;
|
|
281
|
+
serializedRef.current = initialSerialized;
|
|
282
|
+
onStoreChange();
|
|
283
|
+
}
|
|
284
|
+
return () => {
|
|
285
|
+
clearInterval(intervalId);
|
|
286
|
+
};
|
|
287
|
+
},
|
|
288
|
+
[syncEngine]
|
|
289
|
+
);
|
|
290
|
+
const getSnapshot = (0, import_react4.useCallback)(() => {
|
|
291
|
+
return snapshotRef.current;
|
|
292
|
+
}, []);
|
|
293
|
+
(0, import_react4.useEffect)(() => {
|
|
294
|
+
if (!syncEngine) {
|
|
295
|
+
snapshotRef.current = OFFLINE_STATUS;
|
|
296
|
+
serializedRef.current = JSON.stringify(OFFLINE_STATUS);
|
|
297
|
+
}
|
|
298
|
+
}, [syncEngine]);
|
|
299
|
+
return (0, import_react4.useSyncExternalStore)(subscribe, getSnapshot);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/hooks/use-collection.ts
|
|
303
|
+
var import_react5 = require("react");
|
|
304
|
+
function useCollection(name) {
|
|
305
|
+
const { store } = useKoraContext();
|
|
306
|
+
return (0, import_react5.useMemo)(() => {
|
|
307
|
+
return store.collection(name);
|
|
308
|
+
}, [store, name]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/hooks/use-rich-text.ts
|
|
312
|
+
var import_react6 = require("react");
|
|
313
|
+
var Y = __toESM(require("yjs"), 1);
|
|
314
|
+
var LOAD_ORIGIN = "kora-load";
|
|
315
|
+
var TEXT_KEY = "content";
|
|
316
|
+
var COMPACT_AFTER_DELTAS = 20;
|
|
317
|
+
function useRichText(collectionName, recordId, fieldName) {
|
|
318
|
+
const { store } = useKoraContext();
|
|
319
|
+
const collection = (0, import_react6.useMemo)(() => store.collection(collectionName), [store, collectionName]);
|
|
320
|
+
const [doc] = (0, import_react6.useState)(() => new Y.Doc());
|
|
321
|
+
const [ready, setReady] = (0, import_react6.useState)(false);
|
|
322
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
323
|
+
const [canUndo, setCanUndo] = (0, import_react6.useState)(false);
|
|
324
|
+
const [canRedo, setCanRedo] = (0, import_react6.useState)(false);
|
|
325
|
+
const baseUpdateRef = (0, import_react6.useRef)(null);
|
|
326
|
+
const pendingDeltasRef = (0, import_react6.useRef)([]);
|
|
327
|
+
const text = (0, import_react6.useMemo)(() => doc.getText(TEXT_KEY), [doc]);
|
|
328
|
+
const undoManager = (0, import_react6.useMemo)(() => new Y.UndoManager(text), [text]);
|
|
329
|
+
const syncHistoryState = (0, import_react6.useCallback)(() => {
|
|
330
|
+
setCanUndo(undoManager.undoStack.length > 0);
|
|
331
|
+
setCanRedo(undoManager.redoStack.length > 0);
|
|
332
|
+
}, [undoManager]);
|
|
333
|
+
const undo = (0, import_react6.useCallback)(() => {
|
|
334
|
+
undoManager.undo();
|
|
335
|
+
syncHistoryState();
|
|
336
|
+
}, [syncHistoryState, undoManager]);
|
|
337
|
+
const redo = (0, import_react6.useCallback)(() => {
|
|
338
|
+
undoManager.redo();
|
|
339
|
+
syncHistoryState();
|
|
340
|
+
}, [syncHistoryState, undoManager]);
|
|
341
|
+
(0, import_react6.useEffect)(() => {
|
|
342
|
+
let disposed = false;
|
|
343
|
+
const initialize = async () => {
|
|
344
|
+
setReady(false);
|
|
345
|
+
setError(null);
|
|
346
|
+
try {
|
|
347
|
+
const record = await collection.findById(recordId);
|
|
348
|
+
if (disposed) return;
|
|
349
|
+
doc.transact(() => {
|
|
350
|
+
const target = doc.getText(TEXT_KEY);
|
|
351
|
+
target.delete(0, target.length);
|
|
352
|
+
}, LOAD_ORIGIN);
|
|
353
|
+
const encoded = encodeRichtextInput(record?.[fieldName]);
|
|
354
|
+
baseUpdateRef.current = encoded;
|
|
355
|
+
pendingDeltasRef.current = [];
|
|
356
|
+
if (encoded) {
|
|
357
|
+
Y.applyUpdate(doc, encoded, LOAD_ORIGIN);
|
|
358
|
+
}
|
|
359
|
+
setReady(true);
|
|
360
|
+
} catch (cause) {
|
|
361
|
+
if (disposed) return;
|
|
362
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
const persist = async (_update, origin) => {
|
|
366
|
+
syncHistoryState();
|
|
367
|
+
if (origin === LOAD_ORIGIN) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
pendingDeltasRef.current.push(_update);
|
|
371
|
+
const snapshot = composeRichtextSnapshot(baseUpdateRef.current, pendingDeltasRef.current);
|
|
372
|
+
if (pendingDeltasRef.current.length >= COMPACT_AFTER_DELTAS) {
|
|
373
|
+
baseUpdateRef.current = snapshot;
|
|
374
|
+
pendingDeltasRef.current = [];
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
await collection.update(recordId, {
|
|
378
|
+
[fieldName]: snapshot
|
|
379
|
+
});
|
|
380
|
+
} catch (cause) {
|
|
381
|
+
if (!disposed) {
|
|
382
|
+
setError(cause instanceof Error ? cause : new Error(String(cause)));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
doc.on("update", persist);
|
|
387
|
+
void initialize();
|
|
388
|
+
syncHistoryState();
|
|
389
|
+
return () => {
|
|
390
|
+
disposed = true;
|
|
391
|
+
doc.off("update", persist);
|
|
392
|
+
undoManager.destroy();
|
|
393
|
+
baseUpdateRef.current = null;
|
|
394
|
+
pendingDeltasRef.current = [];
|
|
395
|
+
};
|
|
396
|
+
}, [collection, doc, fieldName, recordId, syncHistoryState, undoManager]);
|
|
397
|
+
return { doc, text, undo, redo, canUndo, canRedo, ready, error };
|
|
398
|
+
}
|
|
399
|
+
function encodeRichtextInput(value) {
|
|
400
|
+
if (value === null || value === void 0) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
if (typeof value === "string") {
|
|
404
|
+
const doc = new Y.Doc();
|
|
405
|
+
doc.getText(TEXT_KEY).insert(0, value);
|
|
406
|
+
return Y.encodeStateAsUpdate(doc);
|
|
407
|
+
}
|
|
408
|
+
if (value instanceof Uint8Array) {
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
if (value instanceof ArrayBuffer) {
|
|
412
|
+
return new Uint8Array(value);
|
|
413
|
+
}
|
|
414
|
+
if (typeof globalThis !== "undefined" && "Buffer" in globalThis) {
|
|
415
|
+
const BufferCtor = globalThis.Buffer;
|
|
416
|
+
if (BufferCtor.isBuffer(value)) {
|
|
417
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
throw new Error("Richtext record value must be a string, Uint8Array, ArrayBuffer, or null.");
|
|
421
|
+
}
|
|
422
|
+
function composeRichtextSnapshot(base, deltas) {
|
|
423
|
+
const doc = new Y.Doc();
|
|
424
|
+
if (base) {
|
|
425
|
+
Y.applyUpdate(doc, base);
|
|
426
|
+
}
|
|
427
|
+
for (const delta of deltas) {
|
|
428
|
+
Y.applyUpdate(doc, delta);
|
|
429
|
+
}
|
|
430
|
+
return Y.encodeStateAsUpdate(doc);
|
|
431
|
+
}
|
|
432
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
433
|
+
0 && (module.exports = {
|
|
434
|
+
KoraProvider,
|
|
435
|
+
useCollection,
|
|
436
|
+
useMutation,
|
|
437
|
+
useQuery,
|
|
438
|
+
useRichText,
|
|
439
|
+
useSyncStatus
|
|
440
|
+
});
|
|
441
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/context/kora-context.ts","../src/hooks/use-query.ts","../src/query-store/query-store.ts","../src/hooks/use-mutation.ts","../src/hooks/use-sync-status.ts","../src/hooks/use-collection.ts","../src/hooks/use-rich-text.ts"],"sourcesContent":["// @korajs/react — public API\n// Every export here is a public API commitment. Be explicit.\n\n// === Types ===\nexport type {\n\tKoraAppLike,\n\tKoraContextValue,\n\tKoraProviderProps,\n\tUseQueryOptions,\n\tUseMutationResult,\n\tUseRichTextResult,\n} from './types'\n\n// === Context ===\nexport { KoraProvider } from './context/kora-context'\n\n// === Hooks ===\nexport { useQuery } from './hooks/use-query'\nexport { useMutation } from './hooks/use-mutation'\nexport { useSyncStatus } from './hooks/use-sync-status'\nexport { useCollection } from './hooks/use-collection'\nexport { useRichText } from './hooks/use-rich-text'\n","import type { Store } from '@korajs/store'\nimport type { SyncEngine } from '@korajs/sync'\nimport { createContext, createElement, useContext, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { KoraContextValue, KoraProviderProps } from '../types'\n\nconst KoraContext = createContext<KoraContextValue | null>(null)\n\n/**\n * Provides Kora store and optional sync engine to all child components.\n * Must wrap any component that uses Kora hooks (useQuery, useMutation, etc.).\n *\n * Accepts either an `app` prop (recommended) or explicit `store` + `syncEngine` props.\n *\n * When using the `app` prop, KoraProvider waits for `app.ready` before rendering\n * children. A `fallback` prop can be provided to show content while initializing.\n *\n * @example\n * ```typescript\n * // Recommended: pass the app object directly\n * const app = createApp({ schema })\n * <KoraProvider app={app}><App /></KoraProvider>\n *\n * // Advanced: pass store and syncEngine explicitly\n * <KoraProvider store={store} syncEngine={syncEngine}><App /></KoraProvider>\n * ```\n */\nfunction KoraProvider({\n\tapp,\n\tstore,\n\tsyncEngine,\n\tfallback,\n\tchildren,\n}: KoraProviderProps): ReactNode {\n\tconst [resolvedStore, setResolvedStore] = useState<Store | null>(\n\t\tstore ?? null,\n\t)\n\tconst [resolvedSync, setResolvedSync] = useState<SyncEngine | null>(\n\t\tsyncEngine ?? null,\n\t)\n\t// If no app prop, we're using the store prop and are ready immediately\n\tconst [ready, setReady] = useState(!app)\n\n\tuseEffect(() => {\n\t\tif (!app) return\n\t\tlet cancelled = false\n\t\tapp.ready.then(() => {\n\t\t\tif (cancelled) return\n\t\t\tsetResolvedStore(app.getStore())\n\t\t\tsetResolvedSync(app.getSyncEngine())\n\t\t\tsetReady(true)\n\t\t})\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [app])\n\n\tif (!ready) {\n\t\treturn (fallback ?? null) as ReactNode\n\t}\n\n\tif (!resolvedStore) {\n\t\tthrow new Error(\n\t\t\t'KoraProvider requires either an \"app\" or \"store\" prop. ' +\n\t\t\t\t'Pass a KoraApp from createApp() or a Store instance.',\n\t\t)\n\t}\n\n\tconst value: KoraContextValue = {\n\t\tstore: resolvedStore,\n\t\tsyncEngine: resolvedSync,\n\t}\n\treturn createElement(KoraContext.Provider, { value }, children)\n}\n\n/**\n * Internal hook to access the Kora context.\n * Throws if used outside of a KoraProvider.\n */\nfunction useKoraContext(): KoraContextValue {\n\tconst context = useContext(KoraContext)\n\tif (context === null) {\n\t\tthrow new Error(\n\t\t\t'useKoraContext must be used within a <KoraProvider>. ' +\n\t\t\t\t'Wrap your component tree with <KoraProvider store={store}>.',\n\t\t)\n\t}\n\treturn context\n}\n\nexport { KoraProvider, useKoraContext }\n","import type { CollectionRecord, QueryBuilder } from '@korajs/store'\nimport { useEffect, useMemo, useRef, useSyncExternalStore } from 'react'\nimport { QueryStore } from '../query-store/query-store'\nimport type { UseQueryOptions } from '../types'\n\n/**\n * Frozen empty array returned when the query is disabled or before data loads.\n * Same reference prevents unnecessary re-renders.\n */\nconst EMPTY_ARRAY: readonly unknown[] = Object.freeze([])\n\nconst noopSubscribe = (_onStoreChange: () => void): (() => void) => {\n\treturn () => {}\n}\n\n/**\n * React hook for reactive queries against the local Kora store.\n *\n * Returns data synchronously from the local store — no loading spinners needed.\n * Re-renders automatically when the query results change due to mutations.\n * Uses `useSyncExternalStore` for React 18+ concurrent mode safety.\n *\n * The generic parameter `T` is inferred from the QueryBuilder, providing\n * full type safety when used with typed collection accessors.\n *\n * @param query - A QueryBuilder instance (e.g., `app.todos.where({ done: false })`)\n * @param options - Optional configuration (e.g., `{ enabled: false }` to skip the query)\n * @returns Readonly array of matching records\n *\n * @example\n * ```typescript\n * const todos = useQuery(app.todos.where({ completed: false }).orderBy('createdAt'))\n * // todos is typed as readonly InferRecord<typeof todoFields>[]\n * ```\n */\nexport function useQuery<T = CollectionRecord>(\n\tquery: QueryBuilder<T>,\n\toptions?: UseQueryOptions,\n): readonly T[] {\n\tconst enabled = options?.enabled !== false\n\n\t// Compute a stable key from the query descriptor\n\tconst descriptorKey = JSON.stringify(query.getDescriptor())\n\n\t// Track the current QueryStore instance\n\tconst queryStoreRef = useRef<QueryStore<T> | null>(null)\n\tconst prevKeyRef = useRef<string | null>(null)\n\n\t// Create or reuse a QueryStore when the descriptor changes\n\tconst queryStore = useMemo(() => {\n\t\tif (!enabled) {\n\t\t\t// Destroy previous if it exists\n\t\t\tif (queryStoreRef.current) {\n\t\t\t\tqueryStoreRef.current.destroy()\n\t\t\t\tqueryStoreRef.current = null\n\t\t\t}\n\t\t\tprevKeyRef.current = null\n\t\t\treturn null\n\t\t}\n\n\t\t// If descriptor changed, destroy previous and create new\n\t\tif (prevKeyRef.current !== descriptorKey) {\n\t\t\tif (queryStoreRef.current) {\n\t\t\t\tqueryStoreRef.current.destroy()\n\t\t\t}\n\t\t\tconst newStore = new QueryStore<T>(query)\n\t\t\tqueryStoreRef.current = newStore\n\t\t\tprevKeyRef.current = descriptorKey\n\t\t\treturn newStore\n\t\t}\n\n\t\treturn queryStoreRef.current\n\t}, [descriptorKey, enabled, query])\n\n\t// Clean up on unmount\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (queryStoreRef.current) {\n\t\t\t\tqueryStoreRef.current.destroy()\n\t\t\t\tqueryStoreRef.current = null\n\t\t\t}\n\t\t}\n\t}, [])\n\n\tconst disabledGetSnapshot = (): readonly T[] => EMPTY_ARRAY as readonly T[]\n\n\treturn useSyncExternalStore(\n\t\tqueryStore ? queryStore.subscribe : noopSubscribe,\n\t\tqueryStore ? queryStore.getSnapshot : disabledGetSnapshot,\n\t)\n}\n","import type { CollectionRecord, QueryBuilder } from '@korajs/store'\n\n/**\n * Frozen empty array returned as the initial snapshot before any data loads.\n * Same reference every time prevents infinite re-render loops with useSyncExternalStore.\n */\nconst EMPTY_ARRAY: readonly unknown[] = Object.freeze([])\n\n/**\n * Bridges the async QueryBuilder.subscribe() API with the synchronous\n * getSnapshot() required by React's useSyncExternalStore.\n *\n * Starts the underlying subscription eagerly on construction, so that\n * the snapshot is populated by the time useSyncExternalStore reads it.\n *\n * Generic parameter `T` defaults to `CollectionRecord` for backward compatibility.\n */\nexport class QueryStore<T = CollectionRecord> {\n\tprivate snapshot: readonly T[] = EMPTY_ARRAY as readonly T[]\n\tprivate listeners = new Set<() => void>()\n\tprivate unsubscribeQuery: (() => void) | null = null\n\tprivate destroyed = false\n\n\tconstructor(queryBuilder: QueryBuilder<T>) {\n\t\t// Start subscription eagerly so snapshot is populated before first getSnapshot() call\n\t\tthis.unsubscribeQuery = queryBuilder.subscribe((results) => {\n\t\t\tif (this.destroyed) return\n\n\t\t\tconst newSnapshot = Object.freeze([...results])\n\t\t\tthis.snapshot = newSnapshot\n\t\t\tthis.notifyListeners()\n\t\t})\n\t}\n\n\t/**\n\t * Subscribe to snapshot changes. Compatible with useSyncExternalStore.\n\t *\n\t * @returns Unsubscribe function\n\t */\n\tsubscribe = (onStoreChange: () => void): (() => void) => {\n\t\tthis.listeners.add(onStoreChange)\n\n\t\treturn () => {\n\t\t\tthis.listeners.delete(onStoreChange)\n\t\t}\n\t}\n\n\t/**\n\t * Get the current snapshot synchronously. Compatible with useSyncExternalStore.\n\t * Returns EMPTY_ARRAY before the first async fetch completes.\n\t */\n\tgetSnapshot = (): readonly T[] => {\n\t\treturn this.snapshot\n\t}\n\n\t/**\n\t * Clean up the underlying subscription and release resources.\n\t */\n\tdestroy(): void {\n\t\tthis.destroyed = true\n\t\tif (this.unsubscribeQuery) {\n\t\t\tthis.unsubscribeQuery()\n\t\t\tthis.unsubscribeQuery = null\n\t\t}\n\t\tthis.listeners.clear()\n\t\tthis.snapshot = EMPTY_ARRAY as readonly T[]\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\tlistener()\n\t\t}\n\t}\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { UseMutationResult } from '../types'\n\n/**\n * React hook for performing mutations against the local Kora store.\n *\n * Returns `mutate` for fire-and-forget usage (optimistic) and `mutateAsync`\n * for when you need to await the result. Tracks loading and error state.\n *\n * @param mutationFn - An async function to execute (e.g., `app.todos.insert`)\n * @returns Object with mutate, mutateAsync, isLoading, error, and reset\n *\n * @example\n * ```typescript\n * const { mutate } = useMutation(app.todos.insert)\n * mutate({ title: 'New todo' }) // fire-and-forget\n * ```\n */\nexport function useMutation<TData, TArgs extends unknown[]>(\n\tmutationFn: (...args: TArgs) => Promise<TData>,\n): UseMutationResult<TData, TArgs> {\n\tconst [state, setState] = useState<{ isLoading: boolean; error: Error | null }>({\n\t\tisLoading: false,\n\t\terror: null,\n\t})\n\n\t// Track mounted state to avoid state updates after unmount\n\tconst mountedRef = useRef(true)\n\tuseEffect(() => {\n\t\tmountedRef.current = true\n\t\treturn () => {\n\t\t\tmountedRef.current = false\n\t\t}\n\t}, [])\n\n\t// Keep latest mutation function in a ref to avoid stale closures\n\tconst fnRef = useRef(mutationFn)\n\tfnRef.current = mutationFn\n\n\tconst mutateAsync = useCallback(async (...args: TArgs): Promise<TData> => {\n\t\tif (mountedRef.current) {\n\t\t\tsetState({ isLoading: true, error: null })\n\t\t}\n\t\ttry {\n\t\t\tconst result = await fnRef.current(...args)\n\t\t\tif (mountedRef.current) {\n\t\t\t\tsetState({ isLoading: false, error: null })\n\t\t\t}\n\t\t\treturn result\n\t\t} catch (err) {\n\t\t\tconst error = err instanceof Error ? err : new Error(String(err))\n\t\t\tif (mountedRef.current) {\n\t\t\t\tsetState({ isLoading: false, error })\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}, [])\n\n\tconst mutate = useCallback(\n\t\t(...args: TArgs): void => {\n\t\t\tmutateAsync(...args).catch(() => {\n\t\t\t\t// Fire-and-forget: error is captured in state, no unhandled rejection\n\t\t\t})\n\t\t},\n\t\t[mutateAsync],\n\t)\n\n\tconst reset = useCallback((): void => {\n\t\tif (mountedRef.current) {\n\t\t\tsetState({ isLoading: false, error: null })\n\t\t}\n\t}, [])\n\n\treturn {\n\t\tmutate,\n\t\tmutateAsync,\n\t\tisLoading: state.isLoading,\n\t\terror: state.error,\n\t\treset,\n\t}\n}\n","import type { SyncStatusInfo } from '@korajs/sync'\nimport { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\nimport { useKoraContext } from '../context/kora-context'\n\nconst POLL_INTERVAL_MS = 500\n\n/** Default status returned when no sync engine is configured */\nconst OFFLINE_STATUS: SyncStatusInfo = Object.freeze({\n\tstatus: 'offline',\n\tpendingOperations: 0,\n\tlastSyncedAt: null,\n})\n\n/**\n * React hook for monitoring the sync engine's connection status.\n *\n * Polls the SyncEngine at ~500ms intervals and re-renders only when\n * the status actually changes. Returns a default offline status when\n * no sync engine is configured.\n *\n * @returns Current sync status information\n *\n * @example\n * ```typescript\n * const status = useSyncStatus()\n * // status.status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error'\n * // status.pendingOperations: number\n * // status.lastSyncedAt: number | null\n * ```\n */\nexport function useSyncStatus(): SyncStatusInfo {\n\tconst { syncEngine } = useKoraContext()\n\n\t// Cache the latest status snapshot for stable references\n\tconst snapshotRef = useRef<SyncStatusInfo>(OFFLINE_STATUS)\n\tconst serializedRef = useRef<string>(JSON.stringify(OFFLINE_STATUS))\n\n\tconst subscribe = useCallback(\n\t\t(onStoreChange: () => void): (() => void) => {\n\t\t\tif (!syncEngine) return () => {}\n\n\t\t\t// Poll the sync engine status at regular intervals\n\t\t\tconst intervalId = setInterval(() => {\n\t\t\t\tconst newStatus = syncEngine.getStatus()\n\t\t\t\tconst newSerialized = JSON.stringify(newStatus)\n\n\t\t\t\t// Only notify React if status actually changed\n\t\t\t\tif (newSerialized !== serializedRef.current) {\n\t\t\t\t\tsnapshotRef.current = newStatus\n\t\t\t\t\tserializedRef.current = newSerialized\n\t\t\t\t\tonStoreChange()\n\t\t\t\t}\n\t\t\t}, POLL_INTERVAL_MS)\n\n\t\t\t// Do an immediate check\n\t\t\tconst initialStatus = syncEngine.getStatus()\n\t\t\tconst initialSerialized = JSON.stringify(initialStatus)\n\t\t\tif (initialSerialized !== serializedRef.current) {\n\t\t\t\tsnapshotRef.current = initialStatus\n\t\t\t\tserializedRef.current = initialSerialized\n\t\t\t\tonStoreChange()\n\t\t\t}\n\n\t\t\treturn () => {\n\t\t\t\tclearInterval(intervalId)\n\t\t\t}\n\t\t},\n\t\t[syncEngine],\n\t)\n\n\tconst getSnapshot = useCallback((): SyncStatusInfo => {\n\t\treturn snapshotRef.current\n\t}, [])\n\n\t// Reset snapshot when syncEngine changes\n\tuseEffect(() => {\n\t\tif (!syncEngine) {\n\t\t\tsnapshotRef.current = OFFLINE_STATUS\n\t\t\tserializedRef.current = JSON.stringify(OFFLINE_STATUS)\n\t\t}\n\t}, [syncEngine])\n\n\treturn useSyncExternalStore(subscribe, getSnapshot)\n}\n","import type { CollectionAccessor } from '@korajs/store'\nimport { useMemo } from 'react'\nimport { useKoraContext } from '../context/kora-context'\n\n/**\n * React hook that returns a CollectionAccessor for the given collection name.\n * Convenience hook for accessing a collection without going through the store directly.\n *\n * @param name - The collection name (must match a collection in the schema)\n * @returns CollectionAccessor with insert, findById, update, delete, and where methods\n *\n * @example\n * ```typescript\n * const todos = useCollection('todos')\n * await todos.insert({ title: 'New todo' })\n * ```\n */\nexport function useCollection(name: string): CollectionAccessor {\n\tconst { store } = useKoraContext()\n\n\treturn useMemo(() => {\n\t\treturn store.collection(name)\n\t}, [store, name])\n}\n","import type { CollectionAccessor } from '@korajs/store'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport * as Y from 'yjs'\nimport { useKoraContext } from '../context/kora-context'\nimport type { UseRichTextResult } from '../types'\n\nconst LOAD_ORIGIN = 'kora-load'\nconst TEXT_KEY = 'content'\nconst COMPACT_AFTER_DELTAS = 20\n\n/**\n * Binds a richtext field to a shared Yjs document for editor integration.\n */\nexport function useRichText(\n\tcollectionName: string,\n\trecordId: string,\n\tfieldName: string,\n): UseRichTextResult {\n\tconst { store } = useKoraContext()\n\tconst collection = useMemo<CollectionAccessor>(() => store.collection(collectionName), [store, collectionName])\n\tconst [doc] = useState(() => new Y.Doc())\n\tconst [ready, setReady] = useState(false)\n\tconst [error, setError] = useState<Error | null>(null)\n\tconst [canUndo, setCanUndo] = useState(false)\n\tconst [canRedo, setCanRedo] = useState(false)\n\tconst baseUpdateRef = useRef<Uint8Array | null>(null)\n\tconst pendingDeltasRef = useRef<Uint8Array[]>([])\n\n\tconst text = useMemo(() => doc.getText(TEXT_KEY), [doc])\n\tconst undoManager = useMemo(() => new Y.UndoManager(text), [text])\n\n\tconst syncHistoryState = useCallback(() => {\n\t\tsetCanUndo(undoManager.undoStack.length > 0)\n\t\tsetCanRedo(undoManager.redoStack.length > 0)\n\t}, [undoManager])\n\n\tconst undo = useCallback(() => {\n\t\tundoManager.undo()\n\t\tsyncHistoryState()\n\t}, [syncHistoryState, undoManager])\n\n\tconst redo = useCallback(() => {\n\t\tundoManager.redo()\n\t\tsyncHistoryState()\n\t}, [syncHistoryState, undoManager])\n\n\tuseEffect(() => {\n\t\tlet disposed = false\n\n\t\tconst initialize = async (): Promise<void> => {\n\t\t\tsetReady(false)\n\t\t\tsetError(null)\n\n\t\t\ttry {\n\t\t\t\tconst record = await collection.findById(recordId)\n\t\t\t\tif (disposed) return\n\n\t\t\t\tdoc.transact(() => {\n\t\t\t\t\tconst target = doc.getText(TEXT_KEY)\n\t\t\t\t\ttarget.delete(0, target.length)\n\t\t\t\t}, LOAD_ORIGIN)\n\n\t\t\t\tconst encoded = encodeRichtextInput(record?.[fieldName])\n\t\t\t\tbaseUpdateRef.current = encoded\n\t\t\t\tpendingDeltasRef.current = []\n\t\t\t\tif (encoded) {\n\t\t\t\t\tY.applyUpdate(doc, encoded, LOAD_ORIGIN)\n\t\t\t\t}\n\n\t\t\t\tsetReady(true)\n\t\t\t} catch (cause) {\n\t\t\t\tif (disposed) return\n\t\t\t\tsetError(cause instanceof Error ? cause : new Error(String(cause)))\n\t\t\t}\n\t\t}\n\n\t\tconst persist = async (_update: Uint8Array, origin: unknown): Promise<void> => {\n\t\t\tsyncHistoryState()\n\t\t\tif (origin === LOAD_ORIGIN) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpendingDeltasRef.current.push(_update)\n\t\t\tconst snapshot = composeRichtextSnapshot(baseUpdateRef.current, pendingDeltasRef.current)\n\n\t\t\tif (pendingDeltasRef.current.length >= COMPACT_AFTER_DELTAS) {\n\t\t\t\tbaseUpdateRef.current = snapshot\n\t\t\t\tpendingDeltasRef.current = []\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait collection.update(recordId, {\n\t\t\t\t\t[fieldName]: snapshot,\n\t\t\t\t})\n\t\t\t} catch (cause) {\n\t\t\t\tif (!disposed) {\n\t\t\t\t\tsetError(cause instanceof Error ? cause : new Error(String(cause)))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdoc.on('update', persist)\n\t\tvoid initialize()\n\n\t\tsyncHistoryState()\n\n\t\treturn () => {\n\t\t\tdisposed = true\n\t\t\tdoc.off('update', persist)\n\t\t\tundoManager.destroy()\n\t\t\tbaseUpdateRef.current = null\n\t\t\tpendingDeltasRef.current = []\n\t\t}\n\t}, [collection, doc, fieldName, recordId, syncHistoryState, undoManager])\n\n\treturn { doc, text, undo, redo, canUndo, canRedo, ready, error }\n}\n\nfunction encodeRichtextInput(value: unknown): Uint8Array | null {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tif (typeof value === 'string') {\n\t\tconst doc = new Y.Doc()\n\t\tdoc.getText(TEXT_KEY).insert(0, value)\n\t\treturn Y.encodeStateAsUpdate(doc)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\t// Handle Node.js Buffer without requiring @types/node\n\tif (typeof globalThis !== 'undefined' && 'Buffer' in globalThis) {\n\t\tconst BufferCtor = (globalThis as Record<string, unknown>).Buffer as { isBuffer(v: unknown): v is { buffer: ArrayBuffer; byteOffset: number; byteLength: number } }\n\t\tif (BufferCtor.isBuffer(value)) {\n\t\t\treturn new Uint8Array(value.buffer, value.byteOffset, value.byteLength)\n\t\t}\n\t}\n\n\tthrow new Error('Richtext record value must be a string, Uint8Array, ArrayBuffer, or null.')\n}\n\nfunction composeRichtextSnapshot(base: Uint8Array | null, deltas: Uint8Array[]): Uint8Array {\n\tconst doc = new Y.Doc()\n\tif (base) {\n\t\tY.applyUpdate(doc, base)\n\t}\n\n\tfor (const delta of deltas) {\n\t\tY.applyUpdate(doc, delta)\n\t}\n\n\treturn Y.encodeStateAsUpdate(doc)\n}\n"],"mappings":"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","names":["import_react","EMPTY_ARRAY","import_react","import_react","import_react","import_react"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Store, CollectionRecord, QueryBuilder, CollectionAccessor } from '@korajs/store';
|
|
2
|
+
import { SyncEngine, SyncStatusInfo } from '@korajs/sync';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import * as Y from 'yjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Kora application instance returned by createApp().
|
|
8
|
+
* Defined here to avoid a hard dependency on the kora meta-package.
|
|
9
|
+
*/
|
|
10
|
+
interface KoraAppLike {
|
|
11
|
+
/** Resolves when the store is open and collections are ready. */
|
|
12
|
+
ready: Promise<void>;
|
|
13
|
+
/** Get the underlying Store instance. */
|
|
14
|
+
getStore(): Store;
|
|
15
|
+
/** Get the underlying SyncEngine instance. Null if sync not configured. */
|
|
16
|
+
getSyncEngine(): SyncEngine | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Value provided by KoraProvider via React context.
|
|
20
|
+
*/
|
|
21
|
+
interface KoraContextValue {
|
|
22
|
+
/** The local Kora store instance */
|
|
23
|
+
store: Store;
|
|
24
|
+
/** Optional sync engine for remote synchronization */
|
|
25
|
+
syncEngine: SyncEngine | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Props for the KoraProvider component.
|
|
29
|
+
*
|
|
30
|
+
* Accepts either an `app` instance (recommended, from createApp()) or
|
|
31
|
+
* explicit `store` + `syncEngine` props (advanced use case).
|
|
32
|
+
*/
|
|
33
|
+
interface KoraProviderProps {
|
|
34
|
+
/** A KoraApp instance from createApp(). Extracts store and syncEngine automatically. */
|
|
35
|
+
app?: KoraAppLike;
|
|
36
|
+
/** The local Kora store instance (alternative to app prop). */
|
|
37
|
+
store?: Store;
|
|
38
|
+
/** Optional sync engine for remote synchronization (used with store prop). */
|
|
39
|
+
syncEngine?: SyncEngine | null;
|
|
40
|
+
/** Fallback content to render while app.ready is resolving. Defaults to null. */
|
|
41
|
+
fallback?: ReactNode;
|
|
42
|
+
/** Child components */
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Options for the useQuery hook.
|
|
47
|
+
*/
|
|
48
|
+
interface UseQueryOptions {
|
|
49
|
+
/** Set to false to disable the subscription (query won't execute). Defaults to true. */
|
|
50
|
+
enabled?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Result from the useMutation hook.
|
|
54
|
+
*/
|
|
55
|
+
interface UseMutationResult<TData, TArgs extends unknown[]> {
|
|
56
|
+
/** Fire-and-forget mutation. Catches errors silently (sets error state). */
|
|
57
|
+
mutate: (...args: TArgs) => void;
|
|
58
|
+
/** Promise-returning mutation. Throws on error. */
|
|
59
|
+
mutateAsync: (...args: TArgs) => Promise<TData>;
|
|
60
|
+
/** Whether a mutation is currently in progress */
|
|
61
|
+
isLoading: boolean;
|
|
62
|
+
/** The last error that occurred, or null */
|
|
63
|
+
error: Error | null;
|
|
64
|
+
/** Reset isLoading and error state */
|
|
65
|
+
reset: () => void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Result from the useRichText hook.
|
|
69
|
+
*/
|
|
70
|
+
interface UseRichTextResult {
|
|
71
|
+
/** Shared Yjs document backing this field. */
|
|
72
|
+
doc: Y.Doc;
|
|
73
|
+
/** Y.Text instance to bind to editor integrations. */
|
|
74
|
+
text: Y.Text;
|
|
75
|
+
/** Undo local changes made to this richtext field. */
|
|
76
|
+
undo: () => void;
|
|
77
|
+
/** Redo previously undone local changes. */
|
|
78
|
+
redo: () => void;
|
|
79
|
+
/** True when undo can be applied. */
|
|
80
|
+
canUndo: boolean;
|
|
81
|
+
/** True when redo can be applied. */
|
|
82
|
+
canRedo: boolean;
|
|
83
|
+
/** True once the record field has been loaded into the Y.Doc. */
|
|
84
|
+
ready: boolean;
|
|
85
|
+
/** Last hook error (load/persist), if any. */
|
|
86
|
+
error: Error | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Provides Kora store and optional sync engine to all child components.
|
|
91
|
+
* Must wrap any component that uses Kora hooks (useQuery, useMutation, etc.).
|
|
92
|
+
*
|
|
93
|
+
* Accepts either an `app` prop (recommended) or explicit `store` + `syncEngine` props.
|
|
94
|
+
*
|
|
95
|
+
* When using the `app` prop, KoraProvider waits for `app.ready` before rendering
|
|
96
|
+
* children. A `fallback` prop can be provided to show content while initializing.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* // Recommended: pass the app object directly
|
|
101
|
+
* const app = createApp({ schema })
|
|
102
|
+
* <KoraProvider app={app}><App /></KoraProvider>
|
|
103
|
+
*
|
|
104
|
+
* // Advanced: pass store and syncEngine explicitly
|
|
105
|
+
* <KoraProvider store={store} syncEngine={syncEngine}><App /></KoraProvider>
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function KoraProvider({ app, store, syncEngine, fallback, children, }: KoraProviderProps): ReactNode;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* React hook for reactive queries against the local Kora store.
|
|
112
|
+
*
|
|
113
|
+
* Returns data synchronously from the local store — no loading spinners needed.
|
|
114
|
+
* Re-renders automatically when the query results change due to mutations.
|
|
115
|
+
* Uses `useSyncExternalStore` for React 18+ concurrent mode safety.
|
|
116
|
+
*
|
|
117
|
+
* The generic parameter `T` is inferred from the QueryBuilder, providing
|
|
118
|
+
* full type safety when used with typed collection accessors.
|
|
119
|
+
*
|
|
120
|
+
* @param query - A QueryBuilder instance (e.g., `app.todos.where({ done: false })`)
|
|
121
|
+
* @param options - Optional configuration (e.g., `{ enabled: false }` to skip the query)
|
|
122
|
+
* @returns Readonly array of matching records
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const todos = useQuery(app.todos.where({ completed: false }).orderBy('createdAt'))
|
|
127
|
+
* // todos is typed as readonly InferRecord<typeof todoFields>[]
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function useQuery<T = CollectionRecord>(query: QueryBuilder<T>, options?: UseQueryOptions): readonly T[];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* React hook for performing mutations against the local Kora store.
|
|
134
|
+
*
|
|
135
|
+
* Returns `mutate` for fire-and-forget usage (optimistic) and `mutateAsync`
|
|
136
|
+
* for when you need to await the result. Tracks loading and error state.
|
|
137
|
+
*
|
|
138
|
+
* @param mutationFn - An async function to execute (e.g., `app.todos.insert`)
|
|
139
|
+
* @returns Object with mutate, mutateAsync, isLoading, error, and reset
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* const { mutate } = useMutation(app.todos.insert)
|
|
144
|
+
* mutate({ title: 'New todo' }) // fire-and-forget
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
declare function useMutation<TData, TArgs extends unknown[]>(mutationFn: (...args: TArgs) => Promise<TData>): UseMutationResult<TData, TArgs>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* React hook for monitoring the sync engine's connection status.
|
|
151
|
+
*
|
|
152
|
+
* Polls the SyncEngine at ~500ms intervals and re-renders only when
|
|
153
|
+
* the status actually changes. Returns a default offline status when
|
|
154
|
+
* no sync engine is configured.
|
|
155
|
+
*
|
|
156
|
+
* @returns Current sync status information
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const status = useSyncStatus()
|
|
161
|
+
* // status.status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error'
|
|
162
|
+
* // status.pendingOperations: number
|
|
163
|
+
* // status.lastSyncedAt: number | null
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function useSyncStatus(): SyncStatusInfo;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* React hook that returns a CollectionAccessor for the given collection name.
|
|
170
|
+
* Convenience hook for accessing a collection without going through the store directly.
|
|
171
|
+
*
|
|
172
|
+
* @param name - The collection name (must match a collection in the schema)
|
|
173
|
+
* @returns CollectionAccessor with insert, findById, update, delete, and where methods
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* const todos = useCollection('todos')
|
|
178
|
+
* await todos.insert({ title: 'New todo' })
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function useCollection(name: string): CollectionAccessor;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Binds a richtext field to a shared Yjs document for editor integration.
|
|
185
|
+
*/
|
|
186
|
+
declare function useRichText(collectionName: string, recordId: string, fieldName: string): UseRichTextResult;
|
|
187
|
+
|
|
188
|
+
export { type KoraAppLike, type KoraContextValue, KoraProvider, type KoraProviderProps, type UseMutationResult, type UseQueryOptions, type UseRichTextResult, useCollection, useMutation, useQuery, useRichText, useSyncStatus };
|