@korajs/core 0.4.0 → 0.6.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.
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/bindings/index.ts
21
+ var bindings_exports = {};
22
+ __export(bindings_exports, {
23
+ createMutationController: () => createMutationController
24
+ });
25
+ module.exports = __toCommonJS(bindings_exports);
26
+
27
+ // src/bindings/create-mutation-controller.ts
28
+ function createMutationController(options) {
29
+ let snapshot = { isLoading: false, error: null };
30
+ const listeners = /* @__PURE__ */ new Set();
31
+ let disposed = false;
32
+ let inFlight = 0;
33
+ const notify = () => {
34
+ for (const listener of listeners) {
35
+ listener();
36
+ }
37
+ options.onStateChange?.(snapshot);
38
+ };
39
+ const setSnapshot = (next) => {
40
+ snapshot = next;
41
+ notify();
42
+ };
43
+ const getSnapshot = () => snapshot;
44
+ const subscribe = (listener) => {
45
+ listeners.add(listener);
46
+ return () => {
47
+ listeners.delete(listener);
48
+ };
49
+ };
50
+ const reset = () => {
51
+ if (disposed) return;
52
+ setSnapshot({ isLoading: false, error: null });
53
+ };
54
+ const mutateAsync = async (...args) => {
55
+ if (disposed) {
56
+ throw new Error("Mutation controller is destroyed");
57
+ }
58
+ const opts = options.resolveOptions?.() ?? options.options;
59
+ let context;
60
+ inFlight++;
61
+ setSnapshot({ isLoading: true, error: null });
62
+ try {
63
+ if (opts?.onMutate) {
64
+ context = await opts.onMutate(...args);
65
+ }
66
+ const result = await options.mutationFn(...args);
67
+ inFlight = Math.max(0, inFlight - 1);
68
+ setSnapshot({ isLoading: inFlight > 0, error: null });
69
+ opts?.onSuccess?.(result, ...args);
70
+ opts?.onSettled?.(result, null, ...args);
71
+ return result;
72
+ } catch (err) {
73
+ const error = err instanceof Error ? err : new Error(String(err));
74
+ if (context !== void 0 && opts?.onRollback) {
75
+ await opts.onRollback(context, ...args);
76
+ }
77
+ inFlight = Math.max(0, inFlight - 1);
78
+ setSnapshot({ isLoading: inFlight > 0, error });
79
+ opts?.onError?.(error, ...args);
80
+ opts?.onSettled?.(void 0, error, ...args);
81
+ throw error;
82
+ }
83
+ };
84
+ const mutate = (...args) => {
85
+ void mutateAsync(...args).catch(() => {
86
+ });
87
+ };
88
+ const destroy = () => {
89
+ disposed = true;
90
+ listeners.clear();
91
+ };
92
+ return {
93
+ getSnapshot,
94
+ subscribe,
95
+ mutate,
96
+ mutateAsync,
97
+ reset,
98
+ destroy
99
+ };
100
+ }
101
+ // Annotate the CommonJS export names for ESM import in node:
102
+ 0 && (module.exports = {
103
+ createMutationController
104
+ });
105
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bindings/index.ts","../../src/bindings/create-mutation-controller.ts"],"sourcesContent":["export type {\n\tAuthSyncBinding,\n\tKoraAppLike,\n\tKoraBindingSyncBridge,\n\tKoraBindingSyncStatus,\n\tKoraContextValue,\n\tUseMutationOptions,\n\tUseMutationResultBase,\n\tUseQueryOptions,\n} from './types'\nexport {\n\tcreateMutationController,\n\ttype CreateMutationControllerOptions,\n\ttype MutationController,\n\ttype MutationControllerState,\n} from './create-mutation-controller'\n","import type { UseMutationOptions } from './types'\n\nexport interface MutationControllerState {\n\tisLoading: boolean\n\terror: Error | null\n}\n\nexport interface MutationController<TData, TArgs extends unknown[], TContext = void> {\n\tgetSnapshot(): MutationControllerState\n\tsubscribe(listener: () => void): () => void\n\tmutate(...args: TArgs): void\n\tmutateAsync(...args: TArgs): Promise<TData>\n\treset(): void\n\tdestroy(): void\n}\n\nexport interface CreateMutationControllerOptions<TData, TArgs extends unknown[], TContext = void> {\n\tmutationFn: (...args: TArgs) => Promise<TData>\n\toptions?: UseMutationOptions<TData, TArgs, TContext>\n\t/** Reads latest options on each mutation (for framework hooks with ref-backed options). */\n\tresolveOptions?: () => UseMutationOptions<TData, TArgs, TContext> | undefined\n\tonStateChange?: (state: MutationControllerState) => void\n}\n\n/**\n * Framework-agnostic mutation runner with optimistic lifecycle hooks.\n */\nexport function createMutationController<TData, TArgs extends unknown[], TContext = void>(\n\toptions: CreateMutationControllerOptions<TData, TArgs, TContext>,\n): MutationController<TData, TArgs, TContext> {\n\tlet snapshot: MutationControllerState = { isLoading: false, error: null }\n\tconst listeners = new Set<() => void>()\n\tlet disposed = false\n\tlet inFlight = 0\n\n\tconst notify = (): void => {\n\t\tfor (const listener of listeners) {\n\t\t\tlistener()\n\t\t}\n\t\toptions.onStateChange?.(snapshot)\n\t}\n\n\tconst setSnapshot = (next: MutationControllerState): void => {\n\t\tsnapshot = next\n\t\tnotify()\n\t}\n\n\tconst getSnapshot = (): MutationControllerState => snapshot\n\n\tconst subscribe = (listener: () => void): (() => void) => {\n\t\tlisteners.add(listener)\n\t\treturn () => {\n\t\t\tlisteners.delete(listener)\n\t\t}\n\t}\n\n\tconst reset = (): void => {\n\t\tif (disposed) return\n\t\tsetSnapshot({ isLoading: false, error: null })\n\t}\n\n\tconst mutateAsync = async (...args: TArgs): Promise<TData> => {\n\t\tif (disposed) {\n\t\t\tthrow new Error('Mutation controller is destroyed')\n\t\t}\n\n\t\tconst opts = options.resolveOptions?.() ?? options.options\n\t\tlet context: TContext | undefined\n\n\t\tinFlight++\n\t\tsetSnapshot({ isLoading: true, error: null })\n\n\t\ttry {\n\t\t\tif (opts?.onMutate) {\n\t\t\t\tcontext = await opts.onMutate(...args)\n\t\t\t}\n\n\t\t\tconst result = await options.mutationFn(...args)\n\t\t\tinFlight = Math.max(0, inFlight - 1)\n\t\t\tsetSnapshot({ isLoading: inFlight > 0, error: null })\n\t\t\topts?.onSuccess?.(result, ...args)\n\t\t\topts?.onSettled?.(result, null, ...args)\n\t\t\treturn result\n\t\t} catch (err) {\n\t\t\tconst error = err instanceof Error ? err : new Error(String(err))\n\n\t\t\tif (context !== undefined && opts?.onRollback) {\n\t\t\t\tawait opts.onRollback(context, ...args)\n\t\t\t}\n\n\t\t\tinFlight = Math.max(0, inFlight - 1)\n\t\t\tsetSnapshot({ isLoading: inFlight > 0, error })\n\t\t\topts?.onError?.(error, ...args)\n\t\t\topts?.onSettled?.(undefined, error, ...args)\n\t\t\tthrow error\n\t\t}\n\t}\n\n\tconst mutate = (...args: TArgs): void => {\n\t\tvoid mutateAsync(...args).catch(() => {})\n\t}\n\n\tconst destroy = (): void => {\n\t\tdisposed = true\n\t\tlisteners.clear()\n\t}\n\n\treturn {\n\t\tgetSnapshot,\n\t\tsubscribe,\n\t\tmutate,\n\t\tmutateAsync,\n\t\treset,\n\t\tdestroy,\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC2BO,SAAS,yBACf,SAC6C;AAC7C,MAAI,WAAoC,EAAE,WAAW,OAAO,OAAO,KAAK;AACxE,QAAM,YAAY,oBAAI,IAAgB;AACtC,MAAI,WAAW;AACf,MAAI,WAAW;AAEf,QAAM,SAAS,MAAY;AAC1B,eAAW,YAAY,WAAW;AACjC,eAAS;AAAA,IACV;AACA,YAAQ,gBAAgB,QAAQ;AAAA,EACjC;AAEA,QAAM,cAAc,CAAC,SAAwC;AAC5D,eAAW;AACX,WAAO;AAAA,EACR;AAEA,QAAM,cAAc,MAA+B;AAEnD,QAAM,YAAY,CAAC,aAAuC;AACzD,cAAU,IAAI,QAAQ;AACtB,WAAO,MAAM;AACZ,gBAAU,OAAO,QAAQ;AAAA,IAC1B;AAAA,EACD;AAEA,QAAM,QAAQ,MAAY;AACzB,QAAI,SAAU;AACd,gBAAY,EAAE,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,EAC9C;AAEA,QAAM,cAAc,UAAU,SAAgC;AAC7D,QAAI,UAAU;AACb,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACnD;AAEA,UAAM,OAAO,QAAQ,iBAAiB,KAAK,QAAQ;AACnD,QAAI;AAEJ;AACA,gBAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAE5C,QAAI;AACH,UAAI,MAAM,UAAU;AACnB,kBAAU,MAAM,KAAK,SAAS,GAAG,IAAI;AAAA,MACtC;AAEA,YAAM,SAAS,MAAM,QAAQ,WAAW,GAAG,IAAI;AAC/C,iBAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AACnC,kBAAY,EAAE,WAAW,WAAW,GAAG,OAAO,KAAK,CAAC;AACpD,YAAM,YAAY,QAAQ,GAAG,IAAI;AACjC,YAAM,YAAY,QAAQ,MAAM,GAAG,IAAI;AACvC,aAAO;AAAA,IACR,SAAS,KAAK;AACb,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAEhE,UAAI,YAAY,UAAa,MAAM,YAAY;AAC9C,cAAM,KAAK,WAAW,SAAS,GAAG,IAAI;AAAA,MACvC;AAEA,iBAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AACnC,kBAAY,EAAE,WAAW,WAAW,GAAG,MAAM,CAAC;AAC9C,YAAM,UAAU,OAAO,GAAG,IAAI;AAC9B,YAAM,YAAY,QAAW,OAAO,GAAG,IAAI;AAC3C,YAAM;AAAA,IACP;AAAA,EACD;AAEA,QAAM,SAAS,IAAI,SAAsB;AACxC,SAAK,YAAY,GAAG,IAAI,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzC;AAEA,QAAM,UAAU,MAAY;AAC3B,eAAW;AACX,cAAU,MAAM;AAAA,EACjB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}
@@ -0,0 +1,101 @@
1
+ import { p as KoraEventEmitter } from '../events-D-FWfFg8.cjs';
2
+ import { S as ScopeMap } from '../build-scope-map-B1CcncZN.cjs';
3
+
4
+ /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
+ interface KoraBindingSyncStatus {
6
+ status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch';
7
+ pendingOperations: number;
8
+ lastSyncedAt: number | null;
9
+ lastSuccessfulPush: number | null;
10
+ lastSuccessfulPull: number | null;
11
+ conflicts: number;
12
+ }
13
+ /** Minimal sync bridge surface exposed on `createApp().sync`. */
14
+ interface KoraBindingSyncBridge {
15
+ subscribeStatus(listener: (status: KoraBindingSyncStatus) => void): () => void;
16
+ }
17
+ /**
18
+ * Structural type for a Kora application instance from `createApp()`.
19
+ * Framework packages specialize `TStore` and `TSyncEngine` with concrete types.
20
+ */
21
+ interface KoraAppLike<TStore = unknown, TSyncEngine = unknown, TQueryCache = unknown> {
22
+ ready: Promise<void>;
23
+ events?: KoraEventEmitter;
24
+ sync?: KoraBindingSyncBridge | null;
25
+ getStore(): TStore;
26
+ getSyncEngine(): TSyncEngine | null;
27
+ getQueryStoreCache(): TQueryCache;
28
+ }
29
+ /** Value provided by framework `KoraProvider` implementations. */
30
+ interface KoraContextValue<TStore = unknown, TSyncEngine = unknown, TQueryCache = unknown> {
31
+ store: TStore;
32
+ syncEngine: TSyncEngine | null;
33
+ app: KoraAppLike<TStore, TSyncEngine, TQueryCache> | null;
34
+ events: KoraEventEmitter | null;
35
+ subscribeSyncStatus: KoraBindingSyncBridge['subscribeStatus'] | null;
36
+ queryStoreCache: TQueryCache;
37
+ }
38
+ /** Options for reactive query bindings (`useQuery`, `createQueryStore`, etc.). */
39
+ interface UseQueryOptions {
40
+ /** When false, the query subscription is disabled. Defaults to true. */
41
+ enabled?: boolean;
42
+ }
43
+ /** Optimistic mutation lifecycle callbacks shared across framework bindings. */
44
+ interface UseMutationOptions<TData, TArgs extends unknown[], TContext = void> {
45
+ onMutate?: (...args: TArgs) => TContext | Promise<TContext>;
46
+ onRollback?: (context: TContext, ...args: TArgs) => void | Promise<void>;
47
+ onSuccess?: (data: TData, ...args: TArgs) => void;
48
+ onError?: (error: Error, ...args: TArgs) => void;
49
+ onSettled?: (data: TData | undefined, error: Error | null, ...args: TArgs) => void;
50
+ }
51
+ /** Common mutation result surface (React uses plain values; Vue/Svelte extend this). */
52
+ interface UseMutationResultBase<TData, TArgs extends unknown[]> {
53
+ mutate: (...args: TArgs) => void;
54
+ mutateAsync: (...args: TArgs) => Promise<TData>;
55
+ reset: () => void;
56
+ }
57
+ /**
58
+ * Pre-built auth binding for `createApp({ sync: { authClient } })`.
59
+ * Created by `createKoraAuthSync()` in `@korajs/auth`.
60
+ */
61
+ interface AuthSyncBinding {
62
+ /** Returns the access token for sync handshake (empty string when signed out). */
63
+ auth: () => Promise<{
64
+ token: string;
65
+ }>;
66
+ /** Builds a scope map from the current token and schema. */
67
+ resolveScopeMap?: () => Promise<ScopeMap | undefined>;
68
+ /**
69
+ * Returns the device-bound sync node id from the token `dev` claim.
70
+ * Separate from the user id (`sub`).
71
+ */
72
+ resolveNodeId?: () => Promise<string | undefined>;
73
+ /** Notifies when auth state changes so sync can refresh scope or reconnect. */
74
+ subscribe?: (listener: () => void) => () => void;
75
+ }
76
+
77
+ interface MutationControllerState {
78
+ isLoading: boolean;
79
+ error: Error | null;
80
+ }
81
+ interface MutationController<TData, TArgs extends unknown[], TContext = void> {
82
+ getSnapshot(): MutationControllerState;
83
+ subscribe(listener: () => void): () => void;
84
+ mutate(...args: TArgs): void;
85
+ mutateAsync(...args: TArgs): Promise<TData>;
86
+ reset(): void;
87
+ destroy(): void;
88
+ }
89
+ interface CreateMutationControllerOptions<TData, TArgs extends unknown[], TContext = void> {
90
+ mutationFn: (...args: TArgs) => Promise<TData>;
91
+ options?: UseMutationOptions<TData, TArgs, TContext>;
92
+ /** Reads latest options on each mutation (for framework hooks with ref-backed options). */
93
+ resolveOptions?: () => UseMutationOptions<TData, TArgs, TContext> | undefined;
94
+ onStateChange?: (state: MutationControllerState) => void;
95
+ }
96
+ /**
97
+ * Framework-agnostic mutation runner with optimistic lifecycle hooks.
98
+ */
99
+ declare function createMutationController<TData, TArgs extends unknown[], TContext = void>(options: CreateMutationControllerOptions<TData, TArgs, TContext>): MutationController<TData, TArgs, TContext>;
100
+
101
+ export { type AuthSyncBinding, type CreateMutationControllerOptions, type KoraAppLike, type KoraBindingSyncBridge, type KoraBindingSyncStatus, type KoraContextValue, type MutationController, type MutationControllerState, type UseMutationOptions, type UseMutationResultBase, type UseQueryOptions, createMutationController };
@@ -0,0 +1,101 @@
1
+ import { p as KoraEventEmitter } from '../events-D-FWfFg8.js';
2
+ import { S as ScopeMap } from '../build-scope-map-BmgAT82h.js';
3
+
4
+ /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
+ interface KoraBindingSyncStatus {
6
+ status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch';
7
+ pendingOperations: number;
8
+ lastSyncedAt: number | null;
9
+ lastSuccessfulPush: number | null;
10
+ lastSuccessfulPull: number | null;
11
+ conflicts: number;
12
+ }
13
+ /** Minimal sync bridge surface exposed on `createApp().sync`. */
14
+ interface KoraBindingSyncBridge {
15
+ subscribeStatus(listener: (status: KoraBindingSyncStatus) => void): () => void;
16
+ }
17
+ /**
18
+ * Structural type for a Kora application instance from `createApp()`.
19
+ * Framework packages specialize `TStore` and `TSyncEngine` with concrete types.
20
+ */
21
+ interface KoraAppLike<TStore = unknown, TSyncEngine = unknown, TQueryCache = unknown> {
22
+ ready: Promise<void>;
23
+ events?: KoraEventEmitter;
24
+ sync?: KoraBindingSyncBridge | null;
25
+ getStore(): TStore;
26
+ getSyncEngine(): TSyncEngine | null;
27
+ getQueryStoreCache(): TQueryCache;
28
+ }
29
+ /** Value provided by framework `KoraProvider` implementations. */
30
+ interface KoraContextValue<TStore = unknown, TSyncEngine = unknown, TQueryCache = unknown> {
31
+ store: TStore;
32
+ syncEngine: TSyncEngine | null;
33
+ app: KoraAppLike<TStore, TSyncEngine, TQueryCache> | null;
34
+ events: KoraEventEmitter | null;
35
+ subscribeSyncStatus: KoraBindingSyncBridge['subscribeStatus'] | null;
36
+ queryStoreCache: TQueryCache;
37
+ }
38
+ /** Options for reactive query bindings (`useQuery`, `createQueryStore`, etc.). */
39
+ interface UseQueryOptions {
40
+ /** When false, the query subscription is disabled. Defaults to true. */
41
+ enabled?: boolean;
42
+ }
43
+ /** Optimistic mutation lifecycle callbacks shared across framework bindings. */
44
+ interface UseMutationOptions<TData, TArgs extends unknown[], TContext = void> {
45
+ onMutate?: (...args: TArgs) => TContext | Promise<TContext>;
46
+ onRollback?: (context: TContext, ...args: TArgs) => void | Promise<void>;
47
+ onSuccess?: (data: TData, ...args: TArgs) => void;
48
+ onError?: (error: Error, ...args: TArgs) => void;
49
+ onSettled?: (data: TData | undefined, error: Error | null, ...args: TArgs) => void;
50
+ }
51
+ /** Common mutation result surface (React uses plain values; Vue/Svelte extend this). */
52
+ interface UseMutationResultBase<TData, TArgs extends unknown[]> {
53
+ mutate: (...args: TArgs) => void;
54
+ mutateAsync: (...args: TArgs) => Promise<TData>;
55
+ reset: () => void;
56
+ }
57
+ /**
58
+ * Pre-built auth binding for `createApp({ sync: { authClient } })`.
59
+ * Created by `createKoraAuthSync()` in `@korajs/auth`.
60
+ */
61
+ interface AuthSyncBinding {
62
+ /** Returns the access token for sync handshake (empty string when signed out). */
63
+ auth: () => Promise<{
64
+ token: string;
65
+ }>;
66
+ /** Builds a scope map from the current token and schema. */
67
+ resolveScopeMap?: () => Promise<ScopeMap | undefined>;
68
+ /**
69
+ * Returns the device-bound sync node id from the token `dev` claim.
70
+ * Separate from the user id (`sub`).
71
+ */
72
+ resolveNodeId?: () => Promise<string | undefined>;
73
+ /** Notifies when auth state changes so sync can refresh scope or reconnect. */
74
+ subscribe?: (listener: () => void) => () => void;
75
+ }
76
+
77
+ interface MutationControllerState {
78
+ isLoading: boolean;
79
+ error: Error | null;
80
+ }
81
+ interface MutationController<TData, TArgs extends unknown[], TContext = void> {
82
+ getSnapshot(): MutationControllerState;
83
+ subscribe(listener: () => void): () => void;
84
+ mutate(...args: TArgs): void;
85
+ mutateAsync(...args: TArgs): Promise<TData>;
86
+ reset(): void;
87
+ destroy(): void;
88
+ }
89
+ interface CreateMutationControllerOptions<TData, TArgs extends unknown[], TContext = void> {
90
+ mutationFn: (...args: TArgs) => Promise<TData>;
91
+ options?: UseMutationOptions<TData, TArgs, TContext>;
92
+ /** Reads latest options on each mutation (for framework hooks with ref-backed options). */
93
+ resolveOptions?: () => UseMutationOptions<TData, TArgs, TContext> | undefined;
94
+ onStateChange?: (state: MutationControllerState) => void;
95
+ }
96
+ /**
97
+ * Framework-agnostic mutation runner with optimistic lifecycle hooks.
98
+ */
99
+ declare function createMutationController<TData, TArgs extends unknown[], TContext = void>(options: CreateMutationControllerOptions<TData, TArgs, TContext>): MutationController<TData, TArgs, TContext>;
100
+
101
+ export { type AuthSyncBinding, type CreateMutationControllerOptions, type KoraAppLike, type KoraBindingSyncBridge, type KoraBindingSyncStatus, type KoraContextValue, type MutationController, type MutationControllerState, type UseMutationOptions, type UseMutationResultBase, type UseQueryOptions, createMutationController };
@@ -0,0 +1,78 @@
1
+ // src/bindings/create-mutation-controller.ts
2
+ function createMutationController(options) {
3
+ let snapshot = { isLoading: false, error: null };
4
+ const listeners = /* @__PURE__ */ new Set();
5
+ let disposed = false;
6
+ let inFlight = 0;
7
+ const notify = () => {
8
+ for (const listener of listeners) {
9
+ listener();
10
+ }
11
+ options.onStateChange?.(snapshot);
12
+ };
13
+ const setSnapshot = (next) => {
14
+ snapshot = next;
15
+ notify();
16
+ };
17
+ const getSnapshot = () => snapshot;
18
+ const subscribe = (listener) => {
19
+ listeners.add(listener);
20
+ return () => {
21
+ listeners.delete(listener);
22
+ };
23
+ };
24
+ const reset = () => {
25
+ if (disposed) return;
26
+ setSnapshot({ isLoading: false, error: null });
27
+ };
28
+ const mutateAsync = async (...args) => {
29
+ if (disposed) {
30
+ throw new Error("Mutation controller is destroyed");
31
+ }
32
+ const opts = options.resolveOptions?.() ?? options.options;
33
+ let context;
34
+ inFlight++;
35
+ setSnapshot({ isLoading: true, error: null });
36
+ try {
37
+ if (opts?.onMutate) {
38
+ context = await opts.onMutate(...args);
39
+ }
40
+ const result = await options.mutationFn(...args);
41
+ inFlight = Math.max(0, inFlight - 1);
42
+ setSnapshot({ isLoading: inFlight > 0, error: null });
43
+ opts?.onSuccess?.(result, ...args);
44
+ opts?.onSettled?.(result, null, ...args);
45
+ return result;
46
+ } catch (err) {
47
+ const error = err instanceof Error ? err : new Error(String(err));
48
+ if (context !== void 0 && opts?.onRollback) {
49
+ await opts.onRollback(context, ...args);
50
+ }
51
+ inFlight = Math.max(0, inFlight - 1);
52
+ setSnapshot({ isLoading: inFlight > 0, error });
53
+ opts?.onError?.(error, ...args);
54
+ opts?.onSettled?.(void 0, error, ...args);
55
+ throw error;
56
+ }
57
+ };
58
+ const mutate = (...args) => {
59
+ void mutateAsync(...args).catch(() => {
60
+ });
61
+ };
62
+ const destroy = () => {
63
+ disposed = true;
64
+ listeners.clear();
65
+ };
66
+ return {
67
+ getSnapshot,
68
+ subscribe,
69
+ mutate,
70
+ mutateAsync,
71
+ reset,
72
+ destroy
73
+ };
74
+ }
75
+ export {
76
+ createMutationController
77
+ };
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bindings/create-mutation-controller.ts"],"sourcesContent":["import type { UseMutationOptions } from './types'\n\nexport interface MutationControllerState {\n\tisLoading: boolean\n\terror: Error | null\n}\n\nexport interface MutationController<TData, TArgs extends unknown[], TContext = void> {\n\tgetSnapshot(): MutationControllerState\n\tsubscribe(listener: () => void): () => void\n\tmutate(...args: TArgs): void\n\tmutateAsync(...args: TArgs): Promise<TData>\n\treset(): void\n\tdestroy(): void\n}\n\nexport interface CreateMutationControllerOptions<TData, TArgs extends unknown[], TContext = void> {\n\tmutationFn: (...args: TArgs) => Promise<TData>\n\toptions?: UseMutationOptions<TData, TArgs, TContext>\n\t/** Reads latest options on each mutation (for framework hooks with ref-backed options). */\n\tresolveOptions?: () => UseMutationOptions<TData, TArgs, TContext> | undefined\n\tonStateChange?: (state: MutationControllerState) => void\n}\n\n/**\n * Framework-agnostic mutation runner with optimistic lifecycle hooks.\n */\nexport function createMutationController<TData, TArgs extends unknown[], TContext = void>(\n\toptions: CreateMutationControllerOptions<TData, TArgs, TContext>,\n): MutationController<TData, TArgs, TContext> {\n\tlet snapshot: MutationControllerState = { isLoading: false, error: null }\n\tconst listeners = new Set<() => void>()\n\tlet disposed = false\n\tlet inFlight = 0\n\n\tconst notify = (): void => {\n\t\tfor (const listener of listeners) {\n\t\t\tlistener()\n\t\t}\n\t\toptions.onStateChange?.(snapshot)\n\t}\n\n\tconst setSnapshot = (next: MutationControllerState): void => {\n\t\tsnapshot = next\n\t\tnotify()\n\t}\n\n\tconst getSnapshot = (): MutationControllerState => snapshot\n\n\tconst subscribe = (listener: () => void): (() => void) => {\n\t\tlisteners.add(listener)\n\t\treturn () => {\n\t\t\tlisteners.delete(listener)\n\t\t}\n\t}\n\n\tconst reset = (): void => {\n\t\tif (disposed) return\n\t\tsetSnapshot({ isLoading: false, error: null })\n\t}\n\n\tconst mutateAsync = async (...args: TArgs): Promise<TData> => {\n\t\tif (disposed) {\n\t\t\tthrow new Error('Mutation controller is destroyed')\n\t\t}\n\n\t\tconst opts = options.resolveOptions?.() ?? options.options\n\t\tlet context: TContext | undefined\n\n\t\tinFlight++\n\t\tsetSnapshot({ isLoading: true, error: null })\n\n\t\ttry {\n\t\t\tif (opts?.onMutate) {\n\t\t\t\tcontext = await opts.onMutate(...args)\n\t\t\t}\n\n\t\t\tconst result = await options.mutationFn(...args)\n\t\t\tinFlight = Math.max(0, inFlight - 1)\n\t\t\tsetSnapshot({ isLoading: inFlight > 0, error: null })\n\t\t\topts?.onSuccess?.(result, ...args)\n\t\t\topts?.onSettled?.(result, null, ...args)\n\t\t\treturn result\n\t\t} catch (err) {\n\t\t\tconst error = err instanceof Error ? err : new Error(String(err))\n\n\t\t\tif (context !== undefined && opts?.onRollback) {\n\t\t\t\tawait opts.onRollback(context, ...args)\n\t\t\t}\n\n\t\t\tinFlight = Math.max(0, inFlight - 1)\n\t\t\tsetSnapshot({ isLoading: inFlight > 0, error })\n\t\t\topts?.onError?.(error, ...args)\n\t\t\topts?.onSettled?.(undefined, error, ...args)\n\t\t\tthrow error\n\t\t}\n\t}\n\n\tconst mutate = (...args: TArgs): void => {\n\t\tvoid mutateAsync(...args).catch(() => {})\n\t}\n\n\tconst destroy = (): void => {\n\t\tdisposed = true\n\t\tlisteners.clear()\n\t}\n\n\treturn {\n\t\tgetSnapshot,\n\t\tsubscribe,\n\t\tmutate,\n\t\tmutateAsync,\n\t\treset,\n\t\tdestroy,\n\t}\n}\n"],"mappings":";AA2BO,SAAS,yBACf,SAC6C;AAC7C,MAAI,WAAoC,EAAE,WAAW,OAAO,OAAO,KAAK;AACxE,QAAM,YAAY,oBAAI,IAAgB;AACtC,MAAI,WAAW;AACf,MAAI,WAAW;AAEf,QAAM,SAAS,MAAY;AAC1B,eAAW,YAAY,WAAW;AACjC,eAAS;AAAA,IACV;AACA,YAAQ,gBAAgB,QAAQ;AAAA,EACjC;AAEA,QAAM,cAAc,CAAC,SAAwC;AAC5D,eAAW;AACX,WAAO;AAAA,EACR;AAEA,QAAM,cAAc,MAA+B;AAEnD,QAAM,YAAY,CAAC,aAAuC;AACzD,cAAU,IAAI,QAAQ;AACtB,WAAO,MAAM;AACZ,gBAAU,OAAO,QAAQ;AAAA,IAC1B;AAAA,EACD;AAEA,QAAM,QAAQ,MAAY;AACzB,QAAI,SAAU;AACd,gBAAY,EAAE,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,EAC9C;AAEA,QAAM,cAAc,UAAU,SAAgC;AAC7D,QAAI,UAAU;AACb,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACnD;AAEA,UAAM,OAAO,QAAQ,iBAAiB,KAAK,QAAQ;AACnD,QAAI;AAEJ;AACA,gBAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAE5C,QAAI;AACH,UAAI,MAAM,UAAU;AACnB,kBAAU,MAAM,KAAK,SAAS,GAAG,IAAI;AAAA,MACtC;AAEA,YAAM,SAAS,MAAM,QAAQ,WAAW,GAAG,IAAI;AAC/C,iBAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AACnC,kBAAY,EAAE,WAAW,WAAW,GAAG,OAAO,KAAK,CAAC;AACpD,YAAM,YAAY,QAAQ,GAAG,IAAI;AACjC,YAAM,YAAY,QAAQ,MAAM,GAAG,IAAI;AACvC,aAAO;AAAA,IACR,SAAS,KAAK;AACb,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAEhE,UAAI,YAAY,UAAa,MAAM,YAAY;AAC9C,cAAM,KAAK,WAAW,SAAS,GAAG,IAAI;AAAA,MACvC;AAEA,iBAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AACnC,kBAAY,EAAE,WAAW,WAAW,GAAG,MAAM,CAAC;AAC9C,YAAM,UAAU,OAAO,GAAG,IAAI;AAC9B,YAAM,YAAY,QAAW,OAAO,GAAG,IAAI;AAC3C,YAAM;AAAA,IACP;AAAA,EACD;AAEA,QAAM,SAAS,IAAI,SAAsB;AACxC,SAAK,YAAY,GAAG,IAAI,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzC;AAEA,QAAM,UAAU,MAAY;AAC3B,eAAW;AACX,cAAU,MAAM;AAAA,EACjB;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;","names":[]}
@@ -0,0 +1,33 @@
1
+ import { S as SchemaDefinition } from './events-D-FWfFg8.cjs';
2
+
3
+ /**
4
+ * Per-collection scope map: `{ collectionName: { field: value, ... } }`
5
+ *
6
+ * Used to filter which operations a client should receive during sync.
7
+ */
8
+ type ScopeMap = Record<string, Record<string, unknown>>;
9
+ /**
10
+ * Build a per-collection scope map from the schema's scope declarations
11
+ * and the client's flat scope values.
12
+ *
13
+ * Supports both legacy `collection.scope` arrays and declarative `schema.sync`
14
+ * rules (`sync: { todos: { where: { userId: true } } }`).
15
+ *
16
+ * When `schema.sync` is present, only collections with sync rules or legacy
17
+ * scope fields are included in the result. Other collections are omitted so
18
+ * sync engines treat them as out of scope (partial sync).
19
+ *
20
+ * @param schema - The schema definition with scope declarations
21
+ * @param scopeValues - Flat key-value scope values from the client
22
+ * @returns A per-collection scope map
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // Schema declares: sales.scope = ['orgId', 'storeId']
27
+ * // Client provides: { orgId: 'org-123', storeId: 'store-456' }
28
+ * // Result: { sales: { orgId: 'org-123', storeId: 'store-456' }, products: {} }
29
+ * ```
30
+ */
31
+ declare function buildScopeMap(schema: SchemaDefinition, scopeValues: Record<string, unknown>): ScopeMap;
32
+
33
+ export { type ScopeMap as S, buildScopeMap as b };
@@ -0,0 +1,33 @@
1
+ import { S as SchemaDefinition } from './events-D-FWfFg8.js';
2
+
3
+ /**
4
+ * Per-collection scope map: `{ collectionName: { field: value, ... } }`
5
+ *
6
+ * Used to filter which operations a client should receive during sync.
7
+ */
8
+ type ScopeMap = Record<string, Record<string, unknown>>;
9
+ /**
10
+ * Build a per-collection scope map from the schema's scope declarations
11
+ * and the client's flat scope values.
12
+ *
13
+ * Supports both legacy `collection.scope` arrays and declarative `schema.sync`
14
+ * rules (`sync: { todos: { where: { userId: true } } }`).
15
+ *
16
+ * When `schema.sync` is present, only collections with sync rules or legacy
17
+ * scope fields are included in the result. Other collections are omitted so
18
+ * sync engines treat them as out of scope (partial sync).
19
+ *
20
+ * @param schema - The schema definition with scope declarations
21
+ * @param scopeValues - Flat key-value scope values from the client
22
+ * @returns A per-collection scope map
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // Schema declares: sales.scope = ['orgId', 'storeId']
27
+ * // Client provides: { orgId: 'org-123', storeId: 'store-456' }
28
+ * // Result: { sales: { orgId: 'org-123', storeId: 'store-456' }, products: {} }
29
+ * ```
30
+ */
31
+ declare function buildScopeMap(schema: SchemaDefinition, scopeValues: Record<string, unknown>): ScopeMap;
32
+
33
+ export { type ScopeMap as S, buildScopeMap as b };
@@ -1,3 +1,20 @@
1
+ // src/errors/error-fixes.ts
2
+ var KORA_ERROR_FIX_SUGGESTIONS = {
3
+ SCHEMA_VALIDATION: "Review your defineSchema() definition: every collection needs at least one field and a positive version.",
4
+ OPERATION_ERROR: "Check the operation payload matches your schema field types and required fields.",
5
+ MERGE_CONFLICT: "Add a custom resolver for the conflicting field in defineSchema(), or adjust constraint onConflict rules.",
6
+ SYNC_ERROR: "Verify the sync server URL, auth token, and that the server accepts your schema version.",
7
+ STORAGE_ERROR: "Confirm the storage adapter is supported in this environment and the database path is writable.",
8
+ CLOCK_DRIFT: "Sync device wall-clock time (NTP). Kora refuses new timestamps when drift exceeds five minutes.",
9
+ PERSISTENCE_ERROR: "IndexedDB persistence failed; check browser storage settings and available disk quota.",
10
+ MISSING_WORKER_URL: "Pass store.workerUrl pointing to sqlite-wasm-worker.js (see create-kora-app templates).",
11
+ INVALID_SYNC_URL: "Use ws:// or wss:// for WebSocket transport, or http(s):// for HTTP long-polling.",
12
+ APP_NOT_READY: "Await app.ready before querying or mutating collections, or wrap the tree in <KoraProvider app={app}>."
13
+ };
14
+ function getKoraErrorFix(code) {
15
+ return KORA_ERROR_FIX_SUGGESTIONS[code];
16
+ }
17
+
1
18
  // src/errors/errors.ts
2
19
  var KoraError = class extends Error {
3
20
  constructor(message, code, context) {
@@ -8,6 +25,16 @@ var KoraError = class extends Error {
8
25
  }
9
26
  code;
10
27
  context;
28
+ /**
29
+ * Actionable hint for resolving this error (from registry or error context).
30
+ */
31
+ get fix() {
32
+ const fromContext = this.context?.fix;
33
+ if (typeof fromContext === "string" && fromContext.length > 0) {
34
+ return fromContext;
35
+ }
36
+ return getKoraErrorFix(this.code);
37
+ }
11
38
  };
12
39
  var SchemaValidationError = class extends KoraError {
13
40
  constructor(message, context) {
@@ -49,6 +76,14 @@ var StorageError = class extends KoraError {
49
76
  this.name = "StorageError";
50
77
  }
51
78
  };
79
+ var AppNotReadyError = class extends KoraError {
80
+ constructor(detail) {
81
+ super(detail, "APP_NOT_READY", {
82
+ fix: "Await app.ready, or wrap your UI in <KoraProvider app={app}> before calling useQuery()."
83
+ });
84
+ this.name = "AppNotReadyError";
85
+ }
86
+ };
52
87
  var ClockDriftError = class extends KoraError {
53
88
  constructor(currentHlcTime, physicalTime) {
54
89
  const driftSeconds = Math.round((currentHlcTime - physicalTime) / 1e3);
@@ -105,6 +140,7 @@ var HybridLogicalClock = class {
105
140
  */
106
141
  receive(remote) {
107
142
  const physicalTime = this.timeSource.now();
143
+ const wasColdStart = this.wallTime === 0;
108
144
  if (physicalTime > this.wallTime && physicalTime > remote.wallTime) {
109
145
  this.wallTime = physicalTime;
110
146
  this.logical = 0;
@@ -116,7 +152,9 @@ var HybridLogicalClock = class {
116
152
  } else {
117
153
  this.logical++;
118
154
  }
119
- this.checkDrift(physicalTime);
155
+ if (!wasColdStart) {
156
+ this.checkDrift(physicalTime);
157
+ }
120
158
  return { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId };
121
159
  }
122
160
  /**
@@ -184,8 +222,11 @@ async function computeOperationId(input, timestamp) {
184
222
  return bufferToHex(hashBuffer);
185
223
  }
186
224
  function canonicalize(obj) {
187
- if (obj === null || obj === void 0) {
188
- return JSON.stringify(obj);
225
+ if (obj === null) {
226
+ return "null";
227
+ }
228
+ if (obj === void 0) {
229
+ return "null";
189
230
  }
190
231
  if (typeof obj !== "object") {
191
232
  return JSON.stringify(obj);
@@ -197,7 +238,7 @@ function canonicalize(obj) {
197
238
  const keys = Object.keys(obj).sort();
198
239
  const pairs = keys.map((key) => {
199
240
  const value = obj[key];
200
- return `${JSON.stringify(key)}:${canonicalize(value)}`;
241
+ return `${JSON.stringify(key)}:${canonicalize(value === void 0 ? null : value)}`;
201
242
  });
202
243
  return `{${pairs.join(",")}}`;
203
244
  }
@@ -318,6 +359,7 @@ function isValidOperation(value) {
318
359
  }
319
360
  function deepFreeze(obj) {
320
361
  if (typeof obj !== "object" || obj === null) return obj;
362
+ if (ArrayBuffer.isView(obj)) return obj;
321
363
  Object.freeze(obj);
322
364
  for (const value of Object.values(obj)) {
323
365
  if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
@@ -446,12 +488,15 @@ var MinHeap = class {
446
488
  };
447
489
 
448
490
  export {
491
+ KORA_ERROR_FIX_SUGGESTIONS,
492
+ getKoraErrorFix,
449
493
  KoraError,
450
494
  SchemaValidationError,
451
495
  OperationError,
452
496
  MergeConflictError,
453
497
  SyncError,
454
498
  StorageError,
499
+ AppNotReadyError,
455
500
  ClockDriftError,
456
501
  HybridLogicalClock,
457
502
  computeOperationId,
@@ -462,4 +507,4 @@ export {
462
507
  isValidOperation,
463
508
  topologicalSort
464
509
  };
465
- //# sourceMappingURL=chunk-CFP5YFXC.js.map
510
+ //# sourceMappingURL=chunk-3VIOZT7D.js.map