@korajs/svelte 1.0.0-beta.11 → 1.0.0-beta.13

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.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { KoraError } from "@korajs/core";
3
3
  import { QueryStoreCache } from "@korajs/store";
4
4
  import { getContext, setContext } from "svelte";
5
- var koraContextKey = /* @__PURE__ */ Symbol("korajs-context");
6
- var koraAppContextKey = /* @__PURE__ */ Symbol("korajs-app");
5
+ var koraContextKey = /* @__PURE__ */ Symbol.for("@korajs/svelte:context");
6
+ var koraAppContextKey = /* @__PURE__ */ Symbol.for("@korajs/svelte:app");
7
7
  function setKoraContext(value) {
8
8
  setContext(koraContextKey, value);
9
9
  }
@@ -48,31 +48,119 @@ async function initKoraProvider(app) {
48
48
  }
49
49
 
50
50
  // src/stores/query-store.ts
51
- import { assertQueryReady } from "@korajs/store";
51
+ import { assertQueryReady, queryKey } from "@korajs/store";
52
52
  import { readable } from "svelte/store";
53
53
  var EMPTY_ARRAY = Object.freeze([]);
54
- function createQueryStore(query, options) {
55
- const { queryStoreCache } = getKoraContext();
56
- const enabled = options?.enabled !== false;
57
- return readable(EMPTY_ARRAY, (set) => {
58
- if (!enabled) {
59
- set(EMPTY_ARRAY);
60
- return () => {
61
- };
54
+ function isQueryBuilder(input) {
55
+ return typeof input.getDescriptor === "function";
56
+ }
57
+ function isReadable(input) {
58
+ return typeof input === "object" && input !== null && typeof input.subscribe === "function";
59
+ }
60
+ function readQueryError(queryStore) {
61
+ const channel = queryStore;
62
+ return typeof channel.getError === "function" ? channel.getError() : null;
63
+ }
64
+ function followQuery(cache, query, options, emit) {
65
+ let current = isQueryBuilder(query) ? query : null;
66
+ const enabledInput = options?.enabled;
67
+ let enabled = isReadable(enabledInput) ? true : enabledInput !== false;
68
+ let activeKey = null;
69
+ let release = null;
70
+ let state = {
71
+ data: EMPTY_ARRAY,
72
+ error: null,
73
+ ready: false
74
+ };
75
+ const publish = (next) => {
76
+ if (next.data === state.data && next.error === state.error && next.ready === state.ready) {
77
+ return;
62
78
  }
63
- assertQueryReady(query);
64
- const queryStore = queryStoreCache.getOrCreate(query);
65
- const unsubscribe = queryStore.subscribe(() => {
66
- set(queryStore.getSnapshot());
67
- });
68
- set(queryStore.getSnapshot());
69
- return () => {
79
+ if (next.error && next.error !== state.error) options?.onError?.(next.error);
80
+ state = next;
81
+ emit(state);
82
+ };
83
+ const apply = () => {
84
+ const key = enabled && current ? queryKey(current.getDescriptor()) : null;
85
+ if (key === activeKey) return;
86
+ release?.();
87
+ release = null;
88
+ activeKey = key;
89
+ if (!key || !current) {
90
+ publish({ data: EMPTY_ARRAY, error: null, ready: false });
91
+ return;
92
+ }
93
+ const builder = current;
94
+ assertQueryReady(builder);
95
+ const queryStore = cache.getOrCreate(builder);
96
+ const sync = () => {
97
+ const loaded = queryStore.hasSnapshot();
98
+ publish({
99
+ // A descriptor switch keeps the previous rows until the new query answers.
100
+ data: loaded ? queryStore.getSnapshot() : state.data,
101
+ error: readQueryError(queryStore),
102
+ ready: loaded
103
+ });
104
+ };
105
+ const unsubscribe = queryStore.subscribe(sync);
106
+ sync();
107
+ release = () => {
70
108
  unsubscribe();
71
- queryStoreCache.release(query);
109
+ cache.release(builder);
72
110
  };
73
- });
111
+ };
112
+ const stops = [];
113
+ if (!isQueryBuilder(query)) {
114
+ stops.push(
115
+ query.subscribe((next) => {
116
+ current = next ?? null;
117
+ apply();
118
+ })
119
+ );
120
+ }
121
+ if (isReadable(enabledInput)) {
122
+ stops.push(
123
+ enabledInput.subscribe((next) => {
124
+ enabled = next !== false;
125
+ apply();
126
+ })
127
+ );
128
+ }
129
+ apply();
130
+ return () => {
131
+ for (const stop of stops) stop();
132
+ release?.();
133
+ release = null;
134
+ activeKey = null;
135
+ };
136
+ }
137
+ function createQueryStore(query, options) {
138
+ const { queryStoreCache } = getKoraContext();
139
+ const reporting = {
140
+ ...options,
141
+ onError: options?.onError ?? ((failure) => {
142
+ reportUnhandled(failure);
143
+ })
144
+ };
145
+ return readable(
146
+ EMPTY_ARRAY,
147
+ (set) => followQuery(queryStoreCache, query, reporting, (state) => set(state.data))
148
+ );
149
+ }
150
+ function createQueryStateStore(query, options) {
151
+ const { queryStoreCache } = getKoraContext();
152
+ const initial = { data: EMPTY_ARRAY, error: null, ready: false };
153
+ return readable(
154
+ initial,
155
+ (set) => followQuery(queryStoreCache, query, options, set)
156
+ );
157
+ }
158
+ function reportUnhandled(failure) {
159
+ const host = globalThis;
160
+ host.console?.error("[Kora] createQueryStore failed:", failure);
74
161
  }
75
162
  var useQuery = createQueryStore;
163
+ var useQueryState = createQueryStateStore;
76
164
 
77
165
  // src/composables/use-mutation.ts
78
166
  import { createMutationController } from "@korajs/core/bindings";
@@ -287,6 +375,7 @@ export {
287
375
  applyPresence,
288
376
  createCollaboratorsStore,
289
377
  createMutation,
378
+ createQueryStateStore,
290
379
  createQueryStore,
291
380
  createRichTextBinding,
292
381
  createSyncStatusStore,
@@ -303,6 +392,7 @@ export {
303
392
  useMutation,
304
393
  usePresence,
305
394
  useQuery,
395
+ useQueryState,
306
396
  useRichText,
307
397
  useSyncStatus
308
398
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.ts","../src/stores/query-store.ts","../src/composables/use-mutation.ts","../src/composables/use-sync-status.ts","../src/composables/use-app.ts","../src/composables/use-collection.ts","../src/composables/use-rich-text.ts","../src/composables/use-presence.ts","../src/composables/use-collaborators.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport { QueryStoreCache } from '@korajs/store'\nimport { getContext, setContext } from 'svelte'\nimport type { KoraAppLike, KoraContextValue } from './types'\n\nconst koraContextKey = Symbol('korajs-context')\nconst koraAppContextKey = Symbol('korajs-app')\n\nexport function setKoraContext(value: KoraContextValue): void {\n\tsetContext(koraContextKey, value)\n}\n\nexport function getKoraContext(): KoraContextValue {\n\tconst context = getContext<KoraContextValue | undefined>(koraContextKey)\n\tif (!context) {\n\t\tthrow new KoraError(\n\t\t\t'Kora context missing. Wrap your app with <KoraProvider app={kora}> or <KoraStoreProvider store={store}>.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t\t{ fix: 'Wrap your app with <KoraProvider app={kora}> or <KoraStoreProvider store={store}>.' },\n\t\t)\n\t}\n\treturn context\n}\n\n/** @deprecated Use {@link setKoraContext} via {@link KoraProvider} or {@link KoraStoreProvider}. */\nexport function setKoraAppContext(koraApp: KoraAppLike): void {\n\tsetContext(koraAppContextKey, koraApp)\n}\n\n/** @deprecated Use {@link getKoraContext} and {@link getApp}. */\nexport function getKoraApp(): KoraAppLike {\n\tconst app = getContext<KoraAppLike | undefined>(koraAppContextKey)\n\tif (!app) {\n\t\tthrow new KoraError(\n\t\t\t'getKoraApp() requires setKoraAppContext(koraApp) on an ancestor.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t)\n\t}\n\treturn app\n}\n\n/**\n * Initialize Kora provider context after `app.ready`.\n *\n * @deprecated Prefer {@link KoraProvider} or {@link KoraStoreProvider} in root layout.\n */\nexport async function initKoraProvider(app: KoraAppLike): Promise<KoraContextValue> {\n\tawait app.ready\n\tconst queryStoreCache =\n\t\ttypeof app.getQueryStoreCache === 'function' ? app.getQueryStoreCache() : new QueryStoreCache()\n\tconst value: KoraContextValue = {\n\t\tstore: app.getStore(),\n\t\tsyncEngine: app.getSyncEngine(),\n\t\tapp,\n\t\tevents: app.events ?? null,\n\t\tsubscribeSyncStatus: app.sync?.subscribeStatus ?? null,\n\t\tqueryStoreCache,\n\t}\n\tsetKoraContext(value)\n\tsetKoraAppContext(app)\n\treturn value\n}\n","import type { CollectionRecord, QueryBuilder } from '@korajs/store'\nimport { assertQueryReady } from '@korajs/store'\nimport { type Readable, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\nimport type { UseQueryOptions } from '../types'\n\nconst EMPTY_ARRAY: readonly unknown[] = Object.freeze([])\n\n/**\n * Create a Svelte readable store for a static Kora query.\n *\n * For reactive filter changes, use {@link KoraQuery} which re-subscribes when the\n * query descriptor changes.\n */\nexport function createQueryStore<T = CollectionRecord>(\n\tquery: QueryBuilder<T>,\n\toptions?: UseQueryOptions,\n): Readable<readonly T[]> {\n\tconst { queryStoreCache } = getKoraContext()\n\tconst enabled = options?.enabled !== false\n\n\treturn readable<readonly T[]>(EMPTY_ARRAY as readonly T[], (set) => {\n\t\tif (!enabled) {\n\t\t\tset(EMPTY_ARRAY as readonly T[])\n\t\t\treturn () => {}\n\t\t}\n\n\t\tassertQueryReady(query as QueryBuilder<unknown>)\n\t\tconst queryStore = queryStoreCache.getOrCreate(query)\n\t\tconst unsubscribe = queryStore.subscribe(() => {\n\t\t\tset(queryStore.getSnapshot())\n\t\t})\n\t\tset(queryStore.getSnapshot())\n\n\t\treturn () => {\n\t\t\tunsubscribe()\n\t\t\tqueryStoreCache.release(query as QueryBuilder<unknown>)\n\t\t}\n\t})\n}\n\n/** Alias for {@link createQueryStore}. */\nexport const useQuery = createQueryStore\n","import { createMutationController } from '@korajs/core/bindings'\nimport { onDestroy } from 'svelte'\nimport type { UseMutationOptions, UseMutationResult } from '../types'\n\n/**\n * Create a mutation controller with optimistic update hooks.\n */\nexport function createMutation<TData, TArgs extends unknown[], TContext = void>(\n\tmutationFn: (...args: TArgs) => Promise<TData>,\n\toptions?: UseMutationOptions<TData, TArgs, TContext>,\n): UseMutationResult<TData, TArgs> {\n\tconst optionsRef = { current: options }\n\toptionsRef.current = options\n\n\tconst fnRef = { current: mutationFn }\n\tfnRef.current = mutationFn\n\n\tconst controller = createMutationController<TData, TArgs, TContext>({\n\t\tmutationFn: (...args) => fnRef.current(...args),\n\t\tresolveOptions: () => optionsRef.current,\n\t})\n\n\tconst loadingListeners = new Set<(value: boolean) => void>()\n\tconst errorListeners = new Set<(value: Error | null) => void>()\n\n\tconst unsubscribe = controller.subscribe(() => {\n\t\tconst snapshot = controller.getSnapshot()\n\t\tfor (const listener of loadingListeners) {\n\t\t\tlistener(snapshot.isLoading)\n\t\t}\n\t\tfor (const listener of errorListeners) {\n\t\t\tlistener(snapshot.error)\n\t\t}\n\t})\n\n\t// Dispose the controller when the owning component unmounts, matching the\n\t// React (`useController`) and Vue (`onScopeDispose`) lifecycles. Guarded so\n\t// `createMutation()` can still be called outside a component (manual usage),\n\t// where `onDestroy` is unavailable and the caller owns disposal.\n\ttry {\n\t\tonDestroy(() => {\n\t\t\tunsubscribe()\n\t\t\tcontroller.destroy()\n\t\t})\n\t} catch {\n\t\t// Not inside a component initialization — no lifecycle to hook into.\n\t}\n\n\treturn {\n\t\tmutate: (...args: TArgs) => controller.mutate(...args),\n\t\tmutateAsync: (...args: TArgs) => controller.mutateAsync(...args),\n\t\tsubscribeLoading: (fn) => {\n\t\t\tloadingListeners.add(fn)\n\t\t\tfn(controller.getSnapshot().isLoading)\n\t\t\treturn () => loadingListeners.delete(fn)\n\t\t},\n\t\tsubscribeError: (fn) => {\n\t\t\terrorListeners.add(fn)\n\t\t\tfn(controller.getSnapshot().error)\n\t\t\treturn () => errorListeners.delete(fn)\n\t\t},\n\t\tget loading() {\n\t\t\treturn controller.getSnapshot().isLoading\n\t\t},\n\t\tget isLoading() {\n\t\t\treturn controller.getSnapshot().isLoading\n\t\t},\n\t\tget error() {\n\t\t\treturn controller.getSnapshot().error\n\t\t},\n\t\treset: () => controller.reset(),\n\t}\n}\n\n/** Alias for {@link createMutation}. */\nexport const useMutation = createMutation\n","import { OFFLINE_SYNC_STATUS, createSyncStatusController } from '@korajs/sync'\nimport type { SyncStatusInfo } from '@korajs/sync'\nimport { type Readable, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\n\n/**\n * Readable store of sync engine status.\n */\nexport function createSyncStatusStore(): Readable<SyncStatusInfo> {\n\tconst { syncEngine, subscribeSyncStatus, events } = getKoraContext()\n\n\treturn readable<SyncStatusInfo>(OFFLINE_SYNC_STATUS, (set) => {\n\t\tconst controller = createSyncStatusController({\n\t\t\tsyncEngine,\n\t\t\tsubscribeSyncStatus,\n\t\t\tevents: subscribeSyncStatus ? null : events,\n\t\t})\n\t\tset(controller.getSnapshot())\n\t\tconst unsubscribe = controller.subscribe(() => {\n\t\t\tset(controller.getSnapshot())\n\t\t})\n\t\treturn () => {\n\t\t\tunsubscribe()\n\t\t\tcontroller.destroy()\n\t\t}\n\t})\n}\n\n/** Alias for {@link createSyncStatusStore}. */\nexport const useSyncStatus = createSyncStatusStore\n","import { getKoraContext } from '../context'\nimport type { KoraAppLike } from '../types'\n\nexport function getApp<T extends KoraAppLike = KoraAppLike>(): T {\n\tconst { app } = getKoraContext()\n\tif (!app) {\n\t\tthrow new Error(\n\t\t\t'getApp() requires <KoraProvider app={kora}> or <KoraStoreProvider store={store}> with a createApp() instance.',\n\t\t)\n\t}\n\treturn app as T\n}\n\n/** Alias for {@link getApp}. */\nexport const useApp = getApp\n","import type { CollectionAccessor } from '@korajs/store'\nimport { getKoraContext } from '../context'\n\nexport function getCollection(name: string): CollectionAccessor {\n\tconst { store } = getKoraContext()\n\treturn store.collection(name)\n}\n\n/** Alias for {@link getCollection}. */\nexport const useCollection = getCollection\n","import { asRichTextSyncEngine, createRichTextController } from '@korajs/store'\nimport type { AwarenessUser } from '@korajs/sync'\nimport { onDestroy } from 'svelte'\nimport { type Readable, get, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\nimport type { UseRichTextResult } from '../types'\n\nexport interface UseRichTextOptions {\n\tuser?: AwarenessUser\n\tuseDocChannel?: boolean\n}\n\n/**\n * Binds a richtext field to a shared Yjs document for editor integration.\n *\n * Must be called during component initialization. For reactive target changes,\n * use {@link KoraRichText} instead.\n */\nexport function createRichTextBinding(\n\tcollectionName: string,\n\trecordId: string,\n\tfieldName: string,\n\toptions?: UseRichTextOptions,\n): UseRichTextResult & { subscribe: Readable<UseRichTextResult>['subscribe'] } {\n\tconst { store, syncEngine } = getKoraContext()\n\n\tconst controller = createRichTextController({\n\t\tcollection: store.collection(collectionName),\n\t\tcollectionName,\n\t\trecordId,\n\t\tfieldName,\n\t\tstore,\n\t\tsyncEngine: asRichTextSyncEngine(syncEngine),\n\t\tuseDocChannel: options?.useDocChannel,\n\t\tuser: options?.user,\n\t})\n\n\tonDestroy(() => {\n\t\tcontroller.destroy()\n\t})\n\n\tconst resultStore = readable<UseRichTextResult>(buildResult(controller), (set) => {\n\t\tconst sync = (): void => {\n\t\t\tset(buildResult(controller))\n\t\t}\n\t\tsync()\n\t\treturn controller.subscribe(sync)\n\t})\n\n\treturn {\n\t\tget doc() {\n\t\t\treturn controller.doc\n\t\t},\n\t\tget text() {\n\t\t\treturn controller.text\n\t\t},\n\t\tundo: () => controller.undo(),\n\t\tredo: () => controller.redo(),\n\t\tget ready() {\n\t\t\treturn get(resultStore).ready\n\t\t},\n\t\tget error() {\n\t\t\treturn get(resultStore).error\n\t\t},\n\t\tget canUndo() {\n\t\t\treturn get(resultStore).canUndo\n\t\t},\n\t\tget canRedo() {\n\t\t\treturn get(resultStore).canRedo\n\t\t},\n\t\tget cursors() {\n\t\t\treturn get(resultStore).cursors\n\t\t},\n\t\tsetCursor: (anchor: number, head: number) => controller.setCursor(anchor, head),\n\t\tclearCursor: () => controller.clearCursor(),\n\t\tsubscribe: resultStore.subscribe,\n\t}\n}\n\n/** @alias createRichTextBinding */\nexport const useRichText = createRichTextBinding\n\nfunction buildResult(controller: ReturnType<typeof createRichTextController>): UseRichTextResult {\n\tconst snapshot = controller.getSnapshot()\n\treturn {\n\t\tdoc: controller.doc,\n\t\ttext: controller.text,\n\t\tundo: () => controller.undo(),\n\t\tredo: () => controller.redo(),\n\t\tready: snapshot.ready,\n\t\terror: snapshot.error,\n\t\tcanUndo: snapshot.canUndo,\n\t\tcanRedo: snapshot.canRedo,\n\t\tcursors: [...snapshot.cursors],\n\t\tsetCursor: (anchor, head) => controller.setCursor(anchor, head),\n\t\tclearCursor: () => controller.clearCursor(),\n\t}\n}\n","import { getKoraContext } from '../context'\n\n/**\n * Applies local collaborative presence. Returns a cleanup function.\n *\n * In Svelte components, call from an effect:\n * `$effect(() => applyPresence(user))`\n */\nexport function applyPresence(\n\tuser: { name: string; color: string; avatar?: string } | null,\n): () => void {\n\tconst { syncEngine } = getKoraContext()\n\tif (!syncEngine || !user?.name || !user?.color) {\n\t\treturn () => {}\n\t}\n\n\tconst awareness = syncEngine.getAwarenessManager()\n\tawareness.setLocalState({\n\t\tuser: {\n\t\t\tname: user.name,\n\t\t\tcolor: user.color,\n\t\t\tavatar: user.avatar,\n\t\t},\n\t})\n\n\treturn () => {\n\t\tawareness.setLocalState(null)\n\t}\n}\n\n/** Alias for {@link applyPresence} — use inside `$effect()` in `.svelte` files. */\nexport const usePresence = applyPresence\n","import type { AwarenessState } from '@korajs/sync'\nimport { subscribeRemoteAwarenessStates } from '@korajs/sync'\nimport { type Readable, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\n\n/**\n * Readable store of remote collaborators' awareness states.\n */\nexport function createCollaboratorsStore(): Readable<AwarenessState[]> {\n\tconst { syncEngine } = getKoraContext()\n\n\treturn readable<AwarenessState[]>([], (set) => {\n\t\tif (!syncEngine) {\n\t\t\tset([])\n\t\t\treturn () => {}\n\t\t}\n\n\t\tconst awareness = syncEngine.getAwarenessManager()\n\t\treturn subscribeRemoteAwarenessStates(awareness, set)\n\t})\n}\n\n/** Alias for {@link createCollaboratorsStore}. */\nexport const useCollaborators = createCollaboratorsStore\n"],"mappings":"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","names":["readable","readable","onDestroy","readable","onDestroy","readable","readable","readable"]}
1
+ {"version":3,"sources":["../src/context.ts","../src/stores/query-store.ts","../src/composables/use-mutation.ts","../src/composables/use-sync-status.ts","../src/composables/use-app.ts","../src/composables/use-collection.ts","../src/composables/use-rich-text.ts","../src/composables/use-presence.ts","../src/composables/use-collaborators.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\nimport { QueryStoreCache } from '@korajs/store'\nimport { getContext, setContext } from 'svelte'\nimport type { KoraAppLike, KoraContextValue } from './types'\n\n// Registered (Symbol.for) keys: the .svelte components are compiled from src/ by the app\n// (the \"svelte\" export condition) while helpers such as getCollection come from dist/, so\n// two copies of this module exist in every app. Module-local Symbol() keys made the\n// provider's context invisible to the helpers (\"Kora context missing\").\nconst koraContextKey = Symbol.for('@korajs/svelte:context')\nconst koraAppContextKey = Symbol.for('@korajs/svelte:app')\n\nexport function setKoraContext(value: KoraContextValue): void {\n\tsetContext(koraContextKey, value)\n}\n\nexport function getKoraContext(): KoraContextValue {\n\tconst context = getContext<KoraContextValue | undefined>(koraContextKey)\n\tif (!context) {\n\t\tthrow new KoraError(\n\t\t\t'Kora context missing. Wrap your app with <KoraProvider app={kora}> or <KoraStoreProvider store={store}>.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t\t{ fix: 'Wrap your app with <KoraProvider app={kora}> or <KoraStoreProvider store={store}>.' },\n\t\t)\n\t}\n\treturn context\n}\n\n/** @deprecated Use {@link setKoraContext} via {@link KoraProvider} or {@link KoraStoreProvider}. */\nexport function setKoraAppContext(koraApp: KoraAppLike): void {\n\tsetContext(koraAppContextKey, koraApp)\n}\n\n/** @deprecated Use {@link getKoraContext} and {@link getApp}. */\nexport function getKoraApp(): KoraAppLike {\n\tconst app = getContext<KoraAppLike | undefined>(koraAppContextKey)\n\tif (!app) {\n\t\tthrow new KoraError(\n\t\t\t'getKoraApp() requires setKoraAppContext(koraApp) on an ancestor.',\n\t\t\t'KORA_NOT_PROVIDED',\n\t\t)\n\t}\n\treturn app\n}\n\n/**\n * Initialize Kora provider context after `app.ready`.\n *\n * @deprecated Prefer {@link KoraProvider} or {@link KoraStoreProvider} in root layout.\n */\nexport async function initKoraProvider(app: KoraAppLike): Promise<KoraContextValue> {\n\tawait app.ready\n\tconst queryStoreCache =\n\t\ttypeof app.getQueryStoreCache === 'function' ? app.getQueryStoreCache() : new QueryStoreCache()\n\tconst value: KoraContextValue = {\n\t\tstore: app.getStore(),\n\t\tsyncEngine: app.getSyncEngine(),\n\t\tapp,\n\t\tevents: app.events ?? null,\n\t\tsubscribeSyncStatus: app.sync?.subscribeStatus ?? null,\n\t\tqueryStoreCache,\n\t}\n\tsetKoraContext(value)\n\tsetKoraAppContext(app)\n\treturn value\n}\n","import type { CollectionRecord, QueryBuilder, QueryStore, QueryStoreCache } from '@korajs/store'\nimport { assertQueryReady, queryKey } from '@korajs/store'\nimport { type Readable, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\nimport type { QueryState, UseQueryOptions } from '../types'\n\nconst EMPTY_ARRAY: readonly unknown[] = Object.freeze([])\n\n/** A query input: a builder, or a readable store of one (null disables). */\nexport type QueryInput<T> = QueryBuilder<T> | Readable<QueryBuilder<T> | null | undefined>\n\nfunction isQueryBuilder<T>(input: QueryInput<T>): input is QueryBuilder<T> {\n\treturn typeof (input as { getDescriptor?: unknown }).getDescriptor === 'function'\n}\n\nfunction isReadable<V>(input: V | Readable<V>): input is Readable<V> {\n\treturn (\n\t\ttypeof input === 'object' &&\n\t\tinput !== null &&\n\t\ttypeof (input as { subscribe?: unknown }).subscribe === 'function'\n\t)\n}\n\n/** The STORE-12 error channel of a QueryStore, read structurally (older store builds lack it). */\nfunction readQueryError(queryStore: QueryStore<unknown>): Error | null {\n\tconst channel = queryStore as unknown as { getError?: () => Error | null }\n\treturn typeof channel.getError === 'function' ? channel.getError() : null\n}\n\n/**\n * Follows the query and `enabled` inputs (values or readable stores), holding one\n * ref-counted QueryStore per descriptor and switching when the descriptor changes\n * (DX-7). Returns the teardown.\n */\nfunction followQuery<T>(\n\tcache: QueryStoreCache,\n\tquery: QueryInput<T>,\n\toptions: UseQueryOptions | undefined,\n\temit: (state: QueryState<T>) => void,\n): () => void {\n\tlet current: QueryBuilder<T> | null = isQueryBuilder(query) ? query : null\n\tconst enabledInput = options?.enabled\n\tlet enabled = isReadable(enabledInput) ? true : enabledInput !== false\n\tlet activeKey: string | null = null\n\tlet release: (() => void) | null = null\n\tlet state: QueryState<T> = {\n\t\tdata: EMPTY_ARRAY as readonly T[],\n\t\terror: null,\n\t\tready: false,\n\t}\n\n\tconst publish = (next: QueryState<T>): void => {\n\t\tif (next.data === state.data && next.error === state.error && next.ready === state.ready) {\n\t\t\treturn\n\t\t}\n\t\tif (next.error && next.error !== state.error) options?.onError?.(next.error)\n\t\tstate = next\n\t\temit(state)\n\t}\n\n\tconst apply = (): void => {\n\t\tconst key = enabled && current ? queryKey(current.getDescriptor()) : null\n\t\tif (key === activeKey) return\n\t\trelease?.()\n\t\trelease = null\n\t\tactiveKey = key\n\t\tif (!key || !current) {\n\t\t\tpublish({ data: EMPTY_ARRAY as readonly T[], error: null, ready: false })\n\t\t\treturn\n\t\t}\n\t\tconst builder = current\n\t\tassertQueryReady(builder as QueryBuilder<unknown>)\n\t\tconst queryStore = cache.getOrCreate(builder)\n\t\tconst sync = (): void => {\n\t\t\tconst loaded = queryStore.hasSnapshot()\n\t\t\tpublish({\n\t\t\t\t// A descriptor switch keeps the previous rows until the new query answers.\n\t\t\t\tdata: loaded ? queryStore.getSnapshot() : state.data,\n\t\t\t\terror: readQueryError(queryStore as QueryStore<unknown>),\n\t\t\t\tready: loaded,\n\t\t\t})\n\t\t}\n\t\tconst unsubscribe = queryStore.subscribe(sync)\n\t\tsync()\n\t\trelease = () => {\n\t\t\tunsubscribe()\n\t\t\tcache.release(builder as QueryBuilder<unknown>)\n\t\t}\n\t}\n\n\tconst stops: Array<() => void> = []\n\tif (!isQueryBuilder(query)) {\n\t\tstops.push(\n\t\t\tquery.subscribe((next) => {\n\t\t\t\tcurrent = next ?? null\n\t\t\t\tapply()\n\t\t\t}),\n\t\t)\n\t}\n\tif (isReadable(enabledInput)) {\n\t\tstops.push(\n\t\t\tenabledInput.subscribe((next) => {\n\t\t\t\tenabled = next !== false\n\t\t\t\tapply()\n\t\t\t}),\n\t\t)\n\t}\n\tapply()\n\n\treturn () => {\n\t\tfor (const stop of stops) stop()\n\t\trelease?.()\n\t\trelease = null\n\t\tactiveKey = null\n\t}\n}\n\n/**\n * Create a Svelte readable store of a Kora query's rows.\n *\n * The query and `enabled` may be plain values or readable stores: pass a store (for\n * example a `derived` of your filter) and the rows follow it, re-subscribing when the\n * query's descriptor changes. A failed query is reported through `options.onError`,\n * or `console.error` without one; use {@link createQueryStateStore} to render it.\n *\n * @param query - A query builder, or a readable store of one (null disables)\n * @param options - `enabled` (value or readable store; default true) and `onError`\n * @returns A readable store of the current rows\n *\n * @example\n * ```ts\n * const filter = writable(false)\n * const todos = createQueryStore(derived(filter, (done) => app.todos.where({ completed: done })))\n * ```\n */\nexport function createQueryStore<T = CollectionRecord>(\n\tquery: QueryInput<T>,\n\toptions?: UseQueryOptions,\n): Readable<readonly T[]> {\n\tconst { queryStoreCache } = getKoraContext()\n\tconst reporting: UseQueryOptions = {\n\t\t...options,\n\t\tonError:\n\t\t\toptions?.onError ??\n\t\t\t((failure: Error) => {\n\t\t\t\t// Never swallowed: without a handler the failure is at least logged.\n\t\t\t\treportUnhandled(failure)\n\t\t\t}),\n\t}\n\treturn readable<readonly T[]>(EMPTY_ARRAY as readonly T[], (set) =>\n\t\tfollowQuery(queryStoreCache, query, reporting, (state) => set(state.data)),\n\t)\n}\n\n/**\n * Like {@link createQueryStore}, but the store holds `{ data, error, ready }`.\n *\n * @param query - A query builder, or a readable store of one\n * @param options - `enabled` and `onError`\n * @returns A readable store of the query state\n */\nexport function createQueryStateStore<T = CollectionRecord>(\n\tquery: QueryInput<T>,\n\toptions?: UseQueryOptions,\n): Readable<QueryState<T>> {\n\tconst { queryStoreCache } = getKoraContext()\n\tconst initial: QueryState<T> = { data: EMPTY_ARRAY as readonly T[], error: null, ready: false }\n\treturn readable<QueryState<T>>(initial, (set) =>\n\t\tfollowQuery(queryStoreCache, query, options, set),\n\t)\n}\n\n/**\n * Logs a query failure nobody handles. Read through globalThis because this package\n * builds without DOM or Node typings.\n */\nfunction reportUnhandled(failure: Error): void {\n\tconst host = globalThis as { console?: { error(...args: unknown[]): void } }\n\thost.console?.error('[Kora] createQueryStore failed:', failure)\n}\n\n/** Alias for {@link createQueryStore}. */\nexport const useQuery = createQueryStore\n\n/** Alias for {@link createQueryStateStore}. */\nexport const useQueryState = createQueryStateStore\n","import { createMutationController } from '@korajs/core/bindings'\nimport { onDestroy } from 'svelte'\nimport type { UseMutationOptions, UseMutationResult } from '../types'\n\n/**\n * Create a mutation controller with optimistic update hooks.\n */\nexport function createMutation<TData, TArgs extends unknown[], TContext = void>(\n\tmutationFn: (...args: TArgs) => Promise<TData>,\n\toptions?: UseMutationOptions<TData, TArgs, TContext>,\n): UseMutationResult<TData, TArgs> {\n\tconst optionsRef = { current: options }\n\toptionsRef.current = options\n\n\tconst fnRef = { current: mutationFn }\n\tfnRef.current = mutationFn\n\n\tconst controller = createMutationController<TData, TArgs, TContext>({\n\t\tmutationFn: (...args) => fnRef.current(...args),\n\t\tresolveOptions: () => optionsRef.current,\n\t})\n\n\tconst loadingListeners = new Set<(value: boolean) => void>()\n\tconst errorListeners = new Set<(value: Error | null) => void>()\n\n\tconst unsubscribe = controller.subscribe(() => {\n\t\tconst snapshot = controller.getSnapshot()\n\t\tfor (const listener of loadingListeners) {\n\t\t\tlistener(snapshot.isLoading)\n\t\t}\n\t\tfor (const listener of errorListeners) {\n\t\t\tlistener(snapshot.error)\n\t\t}\n\t})\n\n\t// Dispose the controller when the owning component unmounts, matching the\n\t// React (`useController`) and Vue (`onScopeDispose`) lifecycles. Guarded so\n\t// `createMutation()` can still be called outside a component (manual usage),\n\t// where `onDestroy` is unavailable and the caller owns disposal.\n\ttry {\n\t\tonDestroy(() => {\n\t\t\tunsubscribe()\n\t\t\tcontroller.destroy()\n\t\t})\n\t} catch {\n\t\t// Not inside a component initialization — no lifecycle to hook into.\n\t}\n\n\treturn {\n\t\tmutate: (...args: TArgs) => controller.mutate(...args),\n\t\tmutateAsync: (...args: TArgs) => controller.mutateAsync(...args),\n\t\tsubscribeLoading: (fn) => {\n\t\t\tloadingListeners.add(fn)\n\t\t\tfn(controller.getSnapshot().isLoading)\n\t\t\treturn () => loadingListeners.delete(fn)\n\t\t},\n\t\tsubscribeError: (fn) => {\n\t\t\terrorListeners.add(fn)\n\t\t\tfn(controller.getSnapshot().error)\n\t\t\treturn () => errorListeners.delete(fn)\n\t\t},\n\t\tget loading() {\n\t\t\treturn controller.getSnapshot().isLoading\n\t\t},\n\t\tget isLoading() {\n\t\t\treturn controller.getSnapshot().isLoading\n\t\t},\n\t\tget error() {\n\t\t\treturn controller.getSnapshot().error\n\t\t},\n\t\treset: () => controller.reset(),\n\t}\n}\n\n/** Alias for {@link createMutation}. */\nexport const useMutation = createMutation\n","import { OFFLINE_SYNC_STATUS, createSyncStatusController } from '@korajs/sync'\nimport type { SyncStatusInfo } from '@korajs/sync'\nimport { type Readable, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\n\n/**\n * Readable store of sync engine status.\n */\nexport function createSyncStatusStore(): Readable<SyncStatusInfo> {\n\tconst { syncEngine, subscribeSyncStatus, events } = getKoraContext()\n\n\treturn readable<SyncStatusInfo>(OFFLINE_SYNC_STATUS, (set) => {\n\t\tconst controller = createSyncStatusController({\n\t\t\tsyncEngine,\n\t\t\tsubscribeSyncStatus,\n\t\t\tevents: subscribeSyncStatus ? null : events,\n\t\t})\n\t\tset(controller.getSnapshot())\n\t\tconst unsubscribe = controller.subscribe(() => {\n\t\t\tset(controller.getSnapshot())\n\t\t})\n\t\treturn () => {\n\t\t\tunsubscribe()\n\t\t\tcontroller.destroy()\n\t\t}\n\t})\n}\n\n/** Alias for {@link createSyncStatusStore}. */\nexport const useSyncStatus = createSyncStatusStore\n","import { getKoraContext } from '../context'\nimport type { KoraAppLike } from '../types'\n\nexport function getApp<T extends KoraAppLike = KoraAppLike>(): T {\n\tconst { app } = getKoraContext()\n\tif (!app) {\n\t\tthrow new Error(\n\t\t\t'getApp() requires <KoraProvider app={kora}> or <KoraStoreProvider store={store}> with a createApp() instance.',\n\t\t)\n\t}\n\treturn app as T\n}\n\n/** Alias for {@link getApp}. */\nexport const useApp = getApp\n","import type { CollectionAccessor } from '@korajs/store'\nimport { getKoraContext } from '../context'\n\nexport function getCollection(name: string): CollectionAccessor {\n\tconst { store } = getKoraContext()\n\treturn store.collection(name)\n}\n\n/** Alias for {@link getCollection}. */\nexport const useCollection = getCollection\n","import { asRichTextSyncEngine, createRichTextController } from '@korajs/store'\nimport type { AwarenessUser } from '@korajs/sync'\nimport { onDestroy } from 'svelte'\nimport { type Readable, get, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\nimport type { UseRichTextResult } from '../types'\n\nexport interface UseRichTextOptions {\n\tuser?: AwarenessUser\n\tuseDocChannel?: boolean\n}\n\n/**\n * Binds a richtext field to a shared Yjs document for editor integration.\n *\n * Must be called during component initialization. For reactive target changes,\n * use {@link KoraRichText} instead.\n */\nexport function createRichTextBinding(\n\tcollectionName: string,\n\trecordId: string,\n\tfieldName: string,\n\toptions?: UseRichTextOptions,\n): UseRichTextResult & { subscribe: Readable<UseRichTextResult>['subscribe'] } {\n\tconst { store, syncEngine } = getKoraContext()\n\n\tconst controller = createRichTextController({\n\t\tcollection: store.collection(collectionName),\n\t\tcollectionName,\n\t\trecordId,\n\t\tfieldName,\n\t\tstore,\n\t\tsyncEngine: asRichTextSyncEngine(syncEngine),\n\t\tuseDocChannel: options?.useDocChannel,\n\t\tuser: options?.user,\n\t})\n\n\tonDestroy(() => {\n\t\tcontroller.destroy()\n\t})\n\n\tconst resultStore = readable<UseRichTextResult>(buildResult(controller), (set) => {\n\t\tconst sync = (): void => {\n\t\t\tset(buildResult(controller))\n\t\t}\n\t\tsync()\n\t\treturn controller.subscribe(sync)\n\t})\n\n\treturn {\n\t\tget doc() {\n\t\t\treturn controller.doc\n\t\t},\n\t\tget text() {\n\t\t\treturn controller.text\n\t\t},\n\t\tundo: () => controller.undo(),\n\t\tredo: () => controller.redo(),\n\t\tget ready() {\n\t\t\treturn get(resultStore).ready\n\t\t},\n\t\tget error() {\n\t\t\treturn get(resultStore).error\n\t\t},\n\t\tget canUndo() {\n\t\t\treturn get(resultStore).canUndo\n\t\t},\n\t\tget canRedo() {\n\t\t\treturn get(resultStore).canRedo\n\t\t},\n\t\tget cursors() {\n\t\t\treturn get(resultStore).cursors\n\t\t},\n\t\tsetCursor: (anchor: number, head: number) => controller.setCursor(anchor, head),\n\t\tclearCursor: () => controller.clearCursor(),\n\t\tsubscribe: resultStore.subscribe,\n\t}\n}\n\n/** @alias createRichTextBinding */\nexport const useRichText = createRichTextBinding\n\nfunction buildResult(controller: ReturnType<typeof createRichTextController>): UseRichTextResult {\n\tconst snapshot = controller.getSnapshot()\n\treturn {\n\t\tdoc: controller.doc,\n\t\ttext: controller.text,\n\t\tundo: () => controller.undo(),\n\t\tredo: () => controller.redo(),\n\t\tready: snapshot.ready,\n\t\terror: snapshot.error,\n\t\tcanUndo: snapshot.canUndo,\n\t\tcanRedo: snapshot.canRedo,\n\t\tcursors: [...snapshot.cursors],\n\t\tsetCursor: (anchor, head) => controller.setCursor(anchor, head),\n\t\tclearCursor: () => controller.clearCursor(),\n\t}\n}\n","import { getKoraContext } from '../context'\n\n/**\n * Applies local collaborative presence. Returns a cleanup function.\n *\n * In Svelte components, call from an effect:\n * `$effect(() => applyPresence(user))`\n */\nexport function applyPresence(\n\tuser: { name: string; color: string; avatar?: string } | null,\n): () => void {\n\tconst { syncEngine } = getKoraContext()\n\tif (!syncEngine || !user?.name || !user?.color) {\n\t\treturn () => {}\n\t}\n\n\tconst awareness = syncEngine.getAwarenessManager()\n\tawareness.setLocalState({\n\t\tuser: {\n\t\t\tname: user.name,\n\t\t\tcolor: user.color,\n\t\t\tavatar: user.avatar,\n\t\t},\n\t})\n\n\treturn () => {\n\t\tawareness.setLocalState(null)\n\t}\n}\n\n/** Alias for {@link applyPresence} — use inside `$effect()` in `.svelte` files. */\nexport const usePresence = applyPresence\n","import type { AwarenessState } from '@korajs/sync'\nimport { subscribeRemoteAwarenessStates } from '@korajs/sync'\nimport { type Readable, readable } from 'svelte/store'\nimport { getKoraContext } from '../context'\n\n/**\n * Readable store of remote collaborators' awareness states.\n */\nexport function createCollaboratorsStore(): Readable<AwarenessState[]> {\n\tconst { syncEngine } = getKoraContext()\n\n\treturn readable<AwarenessState[]>([], (set) => {\n\t\tif (!syncEngine) {\n\t\t\tset([])\n\t\t\treturn () => {}\n\t\t}\n\n\t\tconst awareness = syncEngine.getAwarenessManager()\n\t\treturn subscribeRemoteAwarenessStates(awareness, set)\n\t})\n}\n\n/** Alias for {@link createCollaboratorsStore}. */\nexport const useCollaborators = createCollaboratorsStore\n"],"mappings":"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","names":["readable","readable","onDestroy","readable","onDestroy","readable","readable","readable"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/svelte",
3
- "version": "1.0.0-beta.11",
3
+ "version": "1.0.0-beta.13",
4
4
  "description": "Kora.js Svelte bindings for offline-first applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -38,19 +38,17 @@
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
- "src/components/KoraQuery.svelte",
42
- "src/components/KoraRichText.svelte",
43
- "src/components/KoraProvider.svelte",
44
- "src/components/KoraStoreProvider.svelte"
41
+ "src",
42
+ "!src/**/*.test.ts"
45
43
  ],
46
44
  "peerDependencies": {
47
45
  "svelte": "^4.0.0 || ^5.0.0"
48
46
  },
49
47
  "dependencies": {
50
48
  "yjs": "^13.6.30",
51
- "@korajs/core": "1.0.0-beta.11",
52
- "@korajs/store": "1.0.0-beta.11",
53
- "@korajs/sync": "1.0.0-beta.11"
49
+ "@korajs/core": "1.0.0-beta.13",
50
+ "@korajs/store": "1.0.0-beta.13",
51
+ "@korajs/sync": "1.0.0-beta.13"
54
52
  },
55
53
  "devDependencies": {
56
54
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { QueryStoreCache } from '@korajs/store'
3
+ import { setKoraAppContext, setKoraContext } from '../context'
4
+ import type { KoraAppLike } from '../types'
5
+
6
+ interface Props {
7
+ app: KoraAppLike
8
+ children?: import('svelte').Snippet
9
+ }
10
+
11
+ const { app, children }: Props = $props()
12
+
13
+ const fallbackQueryStoreCache = new QueryStoreCache()
14
+ const queryStoreCache =
15
+ typeof app.getQueryStoreCache === 'function' ? app.getQueryStoreCache() : fallbackQueryStoreCache
16
+
17
+ setKoraContext({
18
+ store: app.getStore(),
19
+ syncEngine: app.getSyncEngine(),
20
+ app,
21
+ events: app.events ?? null,
22
+ subscribeSyncStatus: app.sync?.subscribeStatus ?? null,
23
+ queryStoreCache,
24
+ })
25
+ setKoraAppContext(app)
26
+ </script>
27
+
28
+ {#if children}
29
+ {@render children()}
30
+ {/if}
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { QueryStoreCache } from '@korajs/store'
3
+ import { setKoraAppContext, setKoraContext } from '../context'
4
+ import type { KoraAppLike, KoraContextValue } from '../types'
5
+
6
+ interface Props {
7
+ store: KoraContextValue['store']
8
+ syncEngine: KoraContextValue['syncEngine']
9
+ app?: KoraContextValue['app']
10
+ events?: KoraContextValue['events']
11
+ subscribeSyncStatus?: KoraContextValue['subscribeSyncStatus']
12
+ children?: import('svelte').Snippet
13
+ }
14
+
15
+ const {
16
+ store,
17
+ syncEngine,
18
+ app = null,
19
+ events = null,
20
+ subscribeSyncStatus = null,
21
+ children,
22
+ }: Props = $props()
23
+
24
+ const fallbackQueryStoreCache = new QueryStoreCache()
25
+ const queryStoreCache =
26
+ app && typeof app.getQueryStoreCache === 'function'
27
+ ? app.getQueryStoreCache()
28
+ : fallbackQueryStoreCache
29
+
30
+ setKoraContext({
31
+ store,
32
+ syncEngine,
33
+ app,
34
+ events,
35
+ subscribeSyncStatus,
36
+ queryStoreCache,
37
+ })
38
+ if (app) {
39
+ setKoraAppContext(app)
40
+ }
41
+ </script>
42
+
43
+ {#if children}
44
+ {@render children()}
45
+ {/if}
@@ -0,0 +1,15 @@
1
+ import { getKoraContext } from '../context'
2
+ import type { KoraAppLike } from '../types'
3
+
4
+ export function getApp<T extends KoraAppLike = KoraAppLike>(): T {
5
+ const { app } = getKoraContext()
6
+ if (!app) {
7
+ throw new Error(
8
+ 'getApp() requires <KoraProvider app={kora}> or <KoraStoreProvider store={store}> with a createApp() instance.',
9
+ )
10
+ }
11
+ return app as T
12
+ }
13
+
14
+ /** Alias for {@link getApp}. */
15
+ export const useApp = getApp
@@ -0,0 +1,24 @@
1
+ import type { AwarenessState } from '@korajs/sync'
2
+ import { subscribeRemoteAwarenessStates } from '@korajs/sync'
3
+ import { type Readable, readable } from 'svelte/store'
4
+ import { getKoraContext } from '../context'
5
+
6
+ /**
7
+ * Readable store of remote collaborators' awareness states.
8
+ */
9
+ export function createCollaboratorsStore(): Readable<AwarenessState[]> {
10
+ const { syncEngine } = getKoraContext()
11
+
12
+ return readable<AwarenessState[]>([], (set) => {
13
+ if (!syncEngine) {
14
+ set([])
15
+ return () => {}
16
+ }
17
+
18
+ const awareness = syncEngine.getAwarenessManager()
19
+ return subscribeRemoteAwarenessStates(awareness, set)
20
+ })
21
+ }
22
+
23
+ /** Alias for {@link createCollaboratorsStore}. */
24
+ export const useCollaborators = createCollaboratorsStore
@@ -0,0 +1,10 @@
1
+ import type { CollectionAccessor } from '@korajs/store'
2
+ import { getKoraContext } from '../context'
3
+
4
+ export function getCollection(name: string): CollectionAccessor {
5
+ const { store } = getKoraContext()
6
+ return store.collection(name)
7
+ }
8
+
9
+ /** Alias for {@link getCollection}. */
10
+ export const useCollection = getCollection
@@ -0,0 +1,76 @@
1
+ import { createMutationController } from '@korajs/core/bindings'
2
+ import { onDestroy } from 'svelte'
3
+ import type { UseMutationOptions, UseMutationResult } from '../types'
4
+
5
+ /**
6
+ * Create a mutation controller with optimistic update hooks.
7
+ */
8
+ export function createMutation<TData, TArgs extends unknown[], TContext = void>(
9
+ mutationFn: (...args: TArgs) => Promise<TData>,
10
+ options?: UseMutationOptions<TData, TArgs, TContext>,
11
+ ): UseMutationResult<TData, TArgs> {
12
+ const optionsRef = { current: options }
13
+ optionsRef.current = options
14
+
15
+ const fnRef = { current: mutationFn }
16
+ fnRef.current = mutationFn
17
+
18
+ const controller = createMutationController<TData, TArgs, TContext>({
19
+ mutationFn: (...args) => fnRef.current(...args),
20
+ resolveOptions: () => optionsRef.current,
21
+ })
22
+
23
+ const loadingListeners = new Set<(value: boolean) => void>()
24
+ const errorListeners = new Set<(value: Error | null) => void>()
25
+
26
+ const unsubscribe = controller.subscribe(() => {
27
+ const snapshot = controller.getSnapshot()
28
+ for (const listener of loadingListeners) {
29
+ listener(snapshot.isLoading)
30
+ }
31
+ for (const listener of errorListeners) {
32
+ listener(snapshot.error)
33
+ }
34
+ })
35
+
36
+ // Dispose the controller when the owning component unmounts, matching the
37
+ // React (`useController`) and Vue (`onScopeDispose`) lifecycles. Guarded so
38
+ // `createMutation()` can still be called outside a component (manual usage),
39
+ // where `onDestroy` is unavailable and the caller owns disposal.
40
+ try {
41
+ onDestroy(() => {
42
+ unsubscribe()
43
+ controller.destroy()
44
+ })
45
+ } catch {
46
+ // Not inside a component initialization — no lifecycle to hook into.
47
+ }
48
+
49
+ return {
50
+ mutate: (...args: TArgs) => controller.mutate(...args),
51
+ mutateAsync: (...args: TArgs) => controller.mutateAsync(...args),
52
+ subscribeLoading: (fn) => {
53
+ loadingListeners.add(fn)
54
+ fn(controller.getSnapshot().isLoading)
55
+ return () => loadingListeners.delete(fn)
56
+ },
57
+ subscribeError: (fn) => {
58
+ errorListeners.add(fn)
59
+ fn(controller.getSnapshot().error)
60
+ return () => errorListeners.delete(fn)
61
+ },
62
+ get loading() {
63
+ return controller.getSnapshot().isLoading
64
+ },
65
+ get isLoading() {
66
+ return controller.getSnapshot().isLoading
67
+ },
68
+ get error() {
69
+ return controller.getSnapshot().error
70
+ },
71
+ reset: () => controller.reset(),
72
+ }
73
+ }
74
+
75
+ /** Alias for {@link createMutation}. */
76
+ export const useMutation = createMutation
@@ -0,0 +1,32 @@
1
+ import { getKoraContext } from '../context'
2
+
3
+ /**
4
+ * Applies local collaborative presence. Returns a cleanup function.
5
+ *
6
+ * In Svelte components, call from an effect:
7
+ * `$effect(() => applyPresence(user))`
8
+ */
9
+ export function applyPresence(
10
+ user: { name: string; color: string; avatar?: string } | null,
11
+ ): () => void {
12
+ const { syncEngine } = getKoraContext()
13
+ if (!syncEngine || !user?.name || !user?.color) {
14
+ return () => {}
15
+ }
16
+
17
+ const awareness = syncEngine.getAwarenessManager()
18
+ awareness.setLocalState({
19
+ user: {
20
+ name: user.name,
21
+ color: user.color,
22
+ avatar: user.avatar,
23
+ },
24
+ })
25
+
26
+ return () => {
27
+ awareness.setLocalState(null)
28
+ }
29
+ }
30
+
31
+ /** Alias for {@link applyPresence} — use inside `$effect()` in `.svelte` files. */
32
+ export const usePresence = applyPresence
@@ -0,0 +1,98 @@
1
+ import { asRichTextSyncEngine, createRichTextController } from '@korajs/store'
2
+ import type { AwarenessUser } from '@korajs/sync'
3
+ import { onDestroy } from 'svelte'
4
+ import { type Readable, get, readable } from 'svelte/store'
5
+ import { getKoraContext } from '../context'
6
+ import type { UseRichTextResult } from '../types'
7
+
8
+ export interface UseRichTextOptions {
9
+ user?: AwarenessUser
10
+ useDocChannel?: boolean
11
+ }
12
+
13
+ /**
14
+ * Binds a richtext field to a shared Yjs document for editor integration.
15
+ *
16
+ * Must be called during component initialization. For reactive target changes,
17
+ * use {@link KoraRichText} instead.
18
+ */
19
+ export function createRichTextBinding(
20
+ collectionName: string,
21
+ recordId: string,
22
+ fieldName: string,
23
+ options?: UseRichTextOptions,
24
+ ): UseRichTextResult & { subscribe: Readable<UseRichTextResult>['subscribe'] } {
25
+ const { store, syncEngine } = getKoraContext()
26
+
27
+ const controller = createRichTextController({
28
+ collection: store.collection(collectionName),
29
+ collectionName,
30
+ recordId,
31
+ fieldName,
32
+ store,
33
+ syncEngine: asRichTextSyncEngine(syncEngine),
34
+ useDocChannel: options?.useDocChannel,
35
+ user: options?.user,
36
+ })
37
+
38
+ onDestroy(() => {
39
+ controller.destroy()
40
+ })
41
+
42
+ const resultStore = readable<UseRichTextResult>(buildResult(controller), (set) => {
43
+ const sync = (): void => {
44
+ set(buildResult(controller))
45
+ }
46
+ sync()
47
+ return controller.subscribe(sync)
48
+ })
49
+
50
+ return {
51
+ get doc() {
52
+ return controller.doc
53
+ },
54
+ get text() {
55
+ return controller.text
56
+ },
57
+ undo: () => controller.undo(),
58
+ redo: () => controller.redo(),
59
+ get ready() {
60
+ return get(resultStore).ready
61
+ },
62
+ get error() {
63
+ return get(resultStore).error
64
+ },
65
+ get canUndo() {
66
+ return get(resultStore).canUndo
67
+ },
68
+ get canRedo() {
69
+ return get(resultStore).canRedo
70
+ },
71
+ get cursors() {
72
+ return get(resultStore).cursors
73
+ },
74
+ setCursor: (anchor: number, head: number) => controller.setCursor(anchor, head),
75
+ clearCursor: () => controller.clearCursor(),
76
+ subscribe: resultStore.subscribe,
77
+ }
78
+ }
79
+
80
+ /** @alias createRichTextBinding */
81
+ export const useRichText = createRichTextBinding
82
+
83
+ function buildResult(controller: ReturnType<typeof createRichTextController>): UseRichTextResult {
84
+ const snapshot = controller.getSnapshot()
85
+ return {
86
+ doc: controller.doc,
87
+ text: controller.text,
88
+ undo: () => controller.undo(),
89
+ redo: () => controller.redo(),
90
+ ready: snapshot.ready,
91
+ error: snapshot.error,
92
+ canUndo: snapshot.canUndo,
93
+ canRedo: snapshot.canRedo,
94
+ cursors: [...snapshot.cursors],
95
+ setCursor: (anchor, head) => controller.setCursor(anchor, head),
96
+ clearCursor: () => controller.clearCursor(),
97
+ }
98
+ }