@anfenn/dync 1.0.25 → 1.0.26

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.
@@ -1,156 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
- import { Dync } from '../index';
3
- import type { ApiFunctions, BatchSync, MutationEvent, SyncOptions, SyncState } from '../types';
4
- import type { StorageAdapter } from '../storage/types';
5
-
6
- export interface MakeDyncConfigPerTable {
7
- databaseName: string;
8
- syncApis: Record<string, ApiFunctions>;
9
- storageAdapter: StorageAdapter;
10
- options?: SyncOptions;
11
- }
12
-
13
- export interface MakeDyncConfigBatch {
14
- databaseName: string;
15
- batchSync: BatchSync;
16
- storageAdapter: StorageAdapter;
17
- options?: SyncOptions;
18
- }
19
-
20
- export type MakeDyncConfig = MakeDyncConfigPerTable | MakeDyncConfigBatch;
21
-
22
- export interface UseDyncValue<TStoreMap extends Record<string, any>> {
23
- db: Dync<TStoreMap>;
24
- syncState: SyncState;
25
- }
26
-
27
- /**
28
- * A hook returned by makeDync that observes database queries and automatically
29
- * re-runs them when data changes. The db instance is passed to the querier callback.
30
- *
31
- * @param querier - Function that receives db and returns query result
32
- * @param deps - Optional React dependency list for re-running the query
33
- * @param tables - Optional array of table names to watch for mutations, otherwise it's all tables
34
- * @returns The query result, or undefined if not yet available
35
- */
36
- export type BoundUseLiveQuery<TStoreMap extends Record<string, any>> = <T>(
37
- querier: (db: Dync<TStoreMap>) => Promise<T> | T,
38
- deps?: React.DependencyList,
39
- tables?: string[],
40
- ) => T | undefined;
41
-
42
- export interface MakeDyncResult<TStoreMap extends Record<string, any>> {
43
- // The Dync database instance
44
- db: Dync<TStoreMap>;
45
- // Hook to get db and syncState in components
46
- useDync: () => UseDyncValue<TStoreMap>;
47
- // Hook for live queries - db is passed to the querier callback
48
- useLiveQuery: BoundUseLiveQuery<TStoreMap>;
49
- }
50
-
51
- export function makeDync<TStoreMap extends Record<string, any> = Record<string, unknown>>(config: MakeDyncConfig): MakeDyncResult<TStoreMap> {
52
- // Determine which mode based on config shape
53
- const db =
54
- 'syncApis' in config
55
- ? new Dync<TStoreMap>(config.databaseName, config.syncApis, config.storageAdapter, config.options)
56
- : new Dync<TStoreMap>(config.databaseName, config.batchSync, config.storageAdapter, config.options);
57
-
58
- // Cache to provide referential stability (React requires stable references)
59
- // Updated when subscription fires OR when getSnapshot detects value changes
60
- let cachedState = db.sync.getState();
61
-
62
- const subscribe = (listener: () => void) =>
63
- db.sync.onStateChange((nextState) => {
64
- // Update cache immediately when notified, before React calls getSnapshot
65
- cachedState = nextState;
66
- listener();
67
- });
68
-
69
- // getSnapshot returns cached state, but also checks for changes that happened
70
- // before subscription was active (e.g., hydration during initial mount)
71
- const getSnapshot = () => {
72
- const fresh = db.sync.getState();
73
- if (JSON.stringify(fresh) !== JSON.stringify(cachedState)) {
74
- cachedState = fresh;
75
- }
76
- return cachedState;
77
- };
78
-
79
- const useDync = () => {
80
- const syncState = useSyncExternalStore<SyncState>(subscribe, getSnapshot, getSnapshot);
81
- return { db, syncState } as UseDyncValue<TStoreMap>;
82
- };
83
-
84
- // Create a bound useLiveQuery that passes db to the querier
85
- const boundUseLiveQuery: BoundUseLiveQuery<TStoreMap> = <T>(
86
- querier: (db: Dync<TStoreMap>) => Promise<T> | T,
87
- deps: React.DependencyList = [],
88
- tables?: string[],
89
- ): T | undefined => {
90
- return useLiveQueryImpl(db, querier, deps, tables);
91
- };
92
-
93
- return {
94
- db,
95
- useDync,
96
- useLiveQuery: boundUseLiveQuery,
97
- };
98
- }
99
-
100
- function useLiveQueryImpl<TStoreMap extends Record<string, any>, T>(
101
- db: Dync<TStoreMap>,
102
- querier: (db: Dync<TStoreMap>) => Promise<T> | T,
103
- deps: React.DependencyList = [],
104
- tables?: string[],
105
- ): T | undefined {
106
- const [result, setResult] = useState<T | undefined>(undefined);
107
- const [, setError] = useState<Error | null>(null);
108
- const isMountedRef = useRef(true);
109
- const queryVersionRef = useRef(0);
110
- const querierRef = useRef(querier);
111
- const tablesRef = useRef(tables);
112
-
113
- // Keep refs up to date
114
- querierRef.current = querier;
115
- tablesRef.current = tables;
116
-
117
- const runQuery = useCallback(async () => {
118
- const currentVersion = ++queryVersionRef.current;
119
- try {
120
- const queryResult = await querierRef.current(db);
121
- // Only update if still mounted and this is the latest query
122
- if (isMountedRef.current && currentVersion === queryVersionRef.current) {
123
- setResult(queryResult);
124
- setError(null);
125
- }
126
- } catch (err) {
127
- if (isMountedRef.current && currentVersion === queryVersionRef.current) {
128
- setError(err as Error);
129
- }
130
- }
131
- }, [db]);
132
-
133
- // Re-run query when deps change
134
- useEffect(() => {
135
- runQuery();
136
- }, [...deps, runQuery]);
137
-
138
- useEffect(() => {
139
- isMountedRef.current = true;
140
-
141
- // Subscribe to mutation events
142
- const unsubscribe = db.sync.onMutation((event: MutationEvent) => {
143
- // Only re-run if no tables filter specified, or if the mutation affects a watched table
144
- if (!tablesRef.current || tablesRef.current.includes(event.tableName)) {
145
- runQuery();
146
- }
147
- });
148
-
149
- return () => {
150
- isMountedRef.current = false;
151
- unsubscribe();
152
- };
153
- }, [db, runQuery]);
154
-
155
- return result;
156
- }