mpb-localkit 1.3.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/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +853 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +404 -0
- package/dist/core/index.js +1780 -0
- package/dist/core/index.js.map +1 -0
- package/dist/react/index.d.ts +90 -0
- package/dist/react/index.js +230 -0
- package/dist/react/index.js.map +1 -0
- package/dist/svelte/index.d.ts +60 -0
- package/dist/svelte/index.js +151 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/vue/index.d.ts +97 -0
- package/dist/vue/index.js +133 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +120 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { useQueryClient, useQuery, useMutation, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
+
import { useSyncExternalStore, useMemo, useEffect, useCallback, useState } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/react/useCollection.ts
|
|
6
|
+
|
|
7
|
+
// src/core/query.ts
|
|
8
|
+
var localkitKeys = {
|
|
9
|
+
all: ["localkit"],
|
|
10
|
+
collection: (name) => ["localkit", name],
|
|
11
|
+
collectionQuery: (name, opts) => ["localkit", name, JSON.stringify(opts)]
|
|
12
|
+
};
|
|
13
|
+
function subscribeToCollection(collection, queryClient) {
|
|
14
|
+
const queryKey = localkitKeys.collection(collection.name);
|
|
15
|
+
if (!("subscribe" in collection)) {
|
|
16
|
+
return () => {
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const obs = collection;
|
|
20
|
+
return obs.subscribe(() => {
|
|
21
|
+
void queryClient.invalidateQueries({ queryKey });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/react/useCollection.ts
|
|
26
|
+
function useCollection(collection, options) {
|
|
27
|
+
const queryClient = useQueryClient();
|
|
28
|
+
const queryKey = options ? localkitKeys.collectionQuery(collection.name, options) : localkitKeys.collection(collection.name);
|
|
29
|
+
const query = useQuery({
|
|
30
|
+
queryKey,
|
|
31
|
+
queryFn: () => collection.findMany(options)
|
|
32
|
+
});
|
|
33
|
+
const invalidate = () => queryClient.invalidateQueries({ queryKey: localkitKeys.collection(collection.name) });
|
|
34
|
+
const createMutation = useMutation({
|
|
35
|
+
mutationFn: (input) => collection.create(input),
|
|
36
|
+
onSuccess: invalidate
|
|
37
|
+
});
|
|
38
|
+
const updateMutation = useMutation({
|
|
39
|
+
mutationFn: ({ id, input }) => collection.update(id, input),
|
|
40
|
+
onSuccess: invalidate
|
|
41
|
+
});
|
|
42
|
+
const deleteMutation = useMutation({
|
|
43
|
+
mutationFn: (id) => collection.delete(id),
|
|
44
|
+
onSuccess: invalidate
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
data: query.data ?? [],
|
|
48
|
+
isLoading: query.isLoading,
|
|
49
|
+
error: query.error,
|
|
50
|
+
create: (input) => createMutation.mutateAsync(input),
|
|
51
|
+
update: (id, input) => updateMutation.mutateAsync({ id, input }),
|
|
52
|
+
remove: (id) => deleteMutation.mutateAsync(id)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/core/storage/query.ts
|
|
57
|
+
function isCondition(value) {
|
|
58
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
59
|
+
const keys = Object.keys(value);
|
|
60
|
+
return keys.some((k) => k === "$gt" || k === "$lt" || k === "$gte" || k === "$lte" || k === "$in");
|
|
61
|
+
}
|
|
62
|
+
function matchesWhere(doc, where) {
|
|
63
|
+
for (const key of Object.keys(where)) {
|
|
64
|
+
const condition = where[key];
|
|
65
|
+
const docValue = doc[key];
|
|
66
|
+
if (isCondition(condition)) {
|
|
67
|
+
if (condition.$gt != null && !(docValue > condition.$gt)) return false;
|
|
68
|
+
if (condition.$lt != null && !(docValue < condition.$lt)) return false;
|
|
69
|
+
if (condition.$gte != null && !(docValue >= condition.$gte)) return false;
|
|
70
|
+
if (condition.$lte != null && !(docValue <= condition.$lte)) return false;
|
|
71
|
+
if (condition.$in !== void 0 && !condition.$in.includes(docValue)) return false;
|
|
72
|
+
} else {
|
|
73
|
+
if (docValue !== condition) return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
function applySort(docs, sort) {
|
|
79
|
+
const entries = Object.entries(sort);
|
|
80
|
+
if (entries.length === 0) return docs;
|
|
81
|
+
return [...docs].sort((a, b) => {
|
|
82
|
+
for (const [field, direction] of entries) {
|
|
83
|
+
const av = a[field];
|
|
84
|
+
const bv = b[field];
|
|
85
|
+
if (av === bv) continue;
|
|
86
|
+
const cmp = av < bv ? -1 : 1;
|
|
87
|
+
return direction === "asc" ? cmp : -cmp;
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function applyQuery(docs, options) {
|
|
93
|
+
let result = docs;
|
|
94
|
+
if (options.where) {
|
|
95
|
+
const where = options.where;
|
|
96
|
+
result = result.filter((doc) => matchesWhere(doc, where));
|
|
97
|
+
}
|
|
98
|
+
if (options.sort) {
|
|
99
|
+
result = applySort(result, options.sort);
|
|
100
|
+
}
|
|
101
|
+
const offset = options.offset ?? 0;
|
|
102
|
+
if (offset > 0) {
|
|
103
|
+
result = result.slice(offset);
|
|
104
|
+
}
|
|
105
|
+
if (options.limit !== void 0) {
|
|
106
|
+
result = result.slice(0, options.limit);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/react/useCollectionStore.ts
|
|
112
|
+
function useCollectionStore(collection, options) {
|
|
113
|
+
const snapshot = useSyncExternalStore(
|
|
114
|
+
collection.subscribe,
|
|
115
|
+
collection.getSnapshot,
|
|
116
|
+
() => []
|
|
117
|
+
);
|
|
118
|
+
const optionsKey = options !== void 0 ? JSON.stringify(options) : void 0;
|
|
119
|
+
return useMemo(() => {
|
|
120
|
+
if (!options) return snapshot;
|
|
121
|
+
return applyQuery([...snapshot], options);
|
|
122
|
+
}, [snapshot, optionsKey]);
|
|
123
|
+
}
|
|
124
|
+
var AUTH_KEY = ["localkit", "auth"];
|
|
125
|
+
function useAuth(auth) {
|
|
126
|
+
const queryClient = useQueryClient();
|
|
127
|
+
const query = useQuery({
|
|
128
|
+
queryKey: AUTH_KEY,
|
|
129
|
+
queryFn: () => Promise.resolve(auth.currentUser())
|
|
130
|
+
});
|
|
131
|
+
const invalidateAuth = () => queryClient.invalidateQueries({ queryKey: AUTH_KEY });
|
|
132
|
+
const signUpMut = useMutation({
|
|
133
|
+
mutationFn: (credentials) => auth.signUp(credentials),
|
|
134
|
+
onSuccess: invalidateAuth
|
|
135
|
+
});
|
|
136
|
+
const signInMut = useMutation({
|
|
137
|
+
mutationFn: (credentials) => auth.signIn(credentials),
|
|
138
|
+
onSuccess: invalidateAuth
|
|
139
|
+
});
|
|
140
|
+
const signOutMut = useMutation({
|
|
141
|
+
mutationFn: () => auth.signOut(),
|
|
142
|
+
onSuccess: invalidateAuth
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
user: query.data ?? null,
|
|
146
|
+
isLoading: query.isLoading,
|
|
147
|
+
error: query.error ?? signUpMut.error ?? signInMut.error ?? signOutMut.error,
|
|
148
|
+
signUp: (credentials) => signUpMut.mutateAsync(credentials).then(() => {
|
|
149
|
+
}),
|
|
150
|
+
signIn: (credentials) => signInMut.mutateAsync(credentials).then(() => {
|
|
151
|
+
}),
|
|
152
|
+
signOut: () => signOutMut.mutateAsync().then(() => {
|
|
153
|
+
})
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var SYNC_KEY = ["localkit", "sync"];
|
|
157
|
+
function isAppWithSync(source) {
|
|
158
|
+
return "syncStore" in source;
|
|
159
|
+
}
|
|
160
|
+
function useSync(source) {
|
|
161
|
+
const queryClient = useQueryClient();
|
|
162
|
+
const isApp = isAppWithSync(source);
|
|
163
|
+
const query = useQuery({
|
|
164
|
+
queryKey: SYNC_KEY,
|
|
165
|
+
queryFn: () => {
|
|
166
|
+
if (isApp) {
|
|
167
|
+
const s = source.syncStore.getSnapshot();
|
|
168
|
+
return Promise.resolve({ status: s.status, lastSyncAt: s.lastSyncAt ?? 0 });
|
|
169
|
+
}
|
|
170
|
+
const engine = source;
|
|
171
|
+
return Promise.resolve({ status: engine.getStatus(), lastSyncAt: engine.getLastSyncAt() });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (isApp) {
|
|
176
|
+
const appSource = source;
|
|
177
|
+
return appSource.syncStore.subscribe(() => {
|
|
178
|
+
const s = appSource.syncStore.getSnapshot();
|
|
179
|
+
queryClient.setQueryData(SYNC_KEY, { status: s.status, lastSyncAt: s.lastSyncAt ?? 0 });
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
const engine = source;
|
|
183
|
+
const onStart = () => queryClient.setQueryData(SYNC_KEY, { status: "syncing", lastSyncAt: engine.getLastSyncAt() });
|
|
184
|
+
const onComplete = () => queryClient.setQueryData(SYNC_KEY, { status: "idle", lastSyncAt: engine.getLastSyncAt() });
|
|
185
|
+
const onError = () => queryClient.setQueryData(SYNC_KEY, { status: engine.getStatus(), lastSyncAt: engine.getLastSyncAt() });
|
|
186
|
+
engine.on("sync:start", onStart);
|
|
187
|
+
engine.on("sync:complete", onComplete);
|
|
188
|
+
engine.on("sync:error", onError);
|
|
189
|
+
return () => {
|
|
190
|
+
engine.off("sync:start", onStart);
|
|
191
|
+
engine.off("sync:complete", onComplete);
|
|
192
|
+
engine.off("sync:error", onError);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}, [source, queryClient, isApp]);
|
|
196
|
+
const sync = useCallback(async () => {
|
|
197
|
+
if (isApp) {
|
|
198
|
+
const appSource = source;
|
|
199
|
+
await appSource.sync.push();
|
|
200
|
+
await appSource.sync.pull();
|
|
201
|
+
} else {
|
|
202
|
+
await source.sync();
|
|
203
|
+
}
|
|
204
|
+
await queryClient.invalidateQueries({ queryKey: SYNC_KEY });
|
|
205
|
+
}, [source, queryClient, isApp]);
|
|
206
|
+
return {
|
|
207
|
+
status: query.data?.status ?? "idle",
|
|
208
|
+
lastSyncAt: query.data?.lastSyncAt ?? 0,
|
|
209
|
+
sync
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function LocalKitProvider({
|
|
213
|
+
children,
|
|
214
|
+
queryClient,
|
|
215
|
+
collections = []
|
|
216
|
+
}) {
|
|
217
|
+
const [defaultClient] = useState(() => new QueryClient());
|
|
218
|
+
const client = queryClient ?? defaultClient;
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
const unsubs = collections.map((col) => subscribeToCollection(col, client));
|
|
221
|
+
return () => {
|
|
222
|
+
for (const unsub of unsubs) unsub();
|
|
223
|
+
};
|
|
224
|
+
}, [client, collections]);
|
|
225
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, { client, children });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { LocalKitProvider, useAuth, useCollection, useCollectionStore, useSync };
|
|
229
|
+
//# sourceMappingURL=index.js.map
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/query.ts","../../src/react/useCollection.ts","../../src/core/storage/query.ts","../../src/react/useCollectionStore.ts","../../src/react/useAuth.ts","../../src/react/useSync.ts","../../src/react/provider.tsx"],"names":["useQueryClient","useQuery","useMutation","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import type { QueryOptions } from './storage/query.js'\nimport type { WithMeta } from './schema/types.js'\nimport type { Collection } from './client/collection.js'\n\n/** Minimal QueryClient interface — avoids a hard dep on @tanstack/query-core */\ninterface MinimalQueryClient {\n invalidateQueries(opts: { queryKey: readonly unknown[] }): Promise<void>\n}\n\nexport const localkitKeys = {\n all: ['localkit'] as const,\n collection: (name: string) => ['localkit', name] as const,\n collectionQuery: (name: string, opts: unknown) =>\n ['localkit', name, JSON.stringify(opts)] as const,\n}\n\nexport function collectionQueryOptions<T>(\n collection: Collection<T>,\n queryOptions?: QueryOptions<T>,\n): {\n queryKey: readonly unknown[]\n queryFn: () => Promise<WithMeta<T>[]>\n} {\n const queryKey = queryOptions\n ? localkitKeys.collectionQuery(collection.name, queryOptions)\n : localkitKeys.collection(collection.name)\n\n return {\n queryKey,\n queryFn: () => collection.findMany(queryOptions),\n }\n}\n\nexport function subscribeToCollection<T>(\n collection: Collection<T>,\n queryClient: MinimalQueryClient,\n): () => void {\n const queryKey = localkitKeys.collection(collection.name)\n\n if (!('subscribe' in collection)) {\n return () => {}\n }\n\n const obs = collection as Collection<T> & {\n subscribe(listener: () => void): () => void\n }\n\n return obs.subscribe(() => {\n void queryClient.invalidateQueries({ queryKey })\n })\n}\n","import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport type { ObservableCollection } from '../core/client/collection.js'\nimport { localkitKeys } from '../core/query.js'\nimport type { QueryOptions } from '../core/storage/query.js'\nimport type { WithMeta } from '../core/schema/types.js'\nimport type { UseCollectionResult } from './types.js'\n\n/**\n * React hook for reading and mutating an LocalKit collection.\n * Uses @tanstack/react-query for caching and reactivity.\n *\n * @example\n * const { data: brews, create } = useCollection(app.brews)\n * const { data: ipas } = useCollection(app.brews, { where: { style: 'IPA' } })\n */\nexport function useCollection<T>(\n collection: ObservableCollection<T>,\n options?: QueryOptions<T>,\n): UseCollectionResult<T> {\n const queryClient = useQueryClient()\n const queryKey = options\n ? localkitKeys.collectionQuery(collection.name, options)\n : localkitKeys.collection(collection.name)\n\n const query = useQuery({\n queryKey,\n queryFn: () => collection.findMany(options),\n })\n\n const invalidate = () =>\n queryClient.invalidateQueries({ queryKey: localkitKeys.collection(collection.name) })\n\n const createMutation = useMutation({\n mutationFn: (input: T) => collection.create(input),\n onSuccess: invalidate,\n })\n\n const updateMutation = useMutation({\n mutationFn: ({ id, input }: { id: string; input: Partial<T> }) =>\n collection.update(id, input),\n onSuccess: invalidate,\n })\n\n const deleteMutation = useMutation({\n mutationFn: (id: string) => collection.delete(id),\n onSuccess: invalidate,\n })\n\n return {\n data: (query.data ?? []) as WithMeta<T>[],\n isLoading: query.isLoading,\n error: query.error as Error | null,\n create: (input: T) => createMutation.mutateAsync(input),\n update: (id: string, input: Partial<T>) => updateMutation.mutateAsync({ id, input }),\n remove: (id: string) => deleteMutation.mutateAsync(id),\n }\n}\n","export interface QueryOptions<T> {\n where?: WhereClause<T>\n sort?: Partial<Record<keyof T, 'asc' | 'desc'>>\n limit?: number\n offset?: number\n}\n\nexport type WhereClause<T> = {\n [K in keyof T]?: T[K] | { $gt?: T[K]; $lt?: T[K]; $gte?: T[K]; $lte?: T[K]; $in?: T[K][] }\n}\n\ntype FieldCondition<V> = { $gt?: V; $lt?: V; $gte?: V; $lte?: V; $in?: V[] }\n\nfunction isCondition<V>(value: unknown): value is FieldCondition<V> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false\n const keys = Object.keys(value)\n return keys.some(k => k === '$gt' || k === '$lt' || k === '$gte' || k === '$lte' || k === '$in')\n}\n\nexport function matchesWhere<T>(doc: T, where: WhereClause<T>): boolean {\n for (const key of Object.keys(where) as Array<keyof T>) {\n const condition = where[key]\n const docValue = doc[key]\n\n if (isCondition<T[typeof key]>(condition)) {\n if (condition.$gt != null && !(docValue > condition.$gt)) return false\n if (condition.$lt != null && !(docValue < condition.$lt)) return false\n if (condition.$gte != null && !(docValue >= condition.$gte)) return false\n if (condition.$lte != null && !(docValue <= condition.$lte)) return false\n if (condition.$in !== undefined && !condition.$in.includes(docValue)) return false\n } else {\n if (docValue !== condition) return false\n }\n }\n return true\n}\n\nexport function applySort<T>(docs: T[], sort: Partial<Record<keyof T, 'asc' | 'desc'>>): T[] {\n const entries = Object.entries(sort) as Array<[keyof T, 'asc' | 'desc']>\n if (entries.length === 0) return docs\n\n return [...docs].sort((a, b) => {\n for (const [field, direction] of entries) {\n const av = a[field]\n const bv = b[field]\n if (av === bv) continue\n const cmp = av < bv ? -1 : 1\n return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\nexport function applyQuery<T>(docs: T[], options: QueryOptions<T>): T[] {\n let result = docs\n\n if (options.where) {\n const where = options.where\n result = result.filter(doc => matchesWhere(doc, where))\n }\n\n if (options.sort) {\n result = applySort(result, options.sort)\n }\n\n const offset = options.offset ?? 0\n if (offset > 0) {\n result = result.slice(offset)\n }\n\n if (options.limit !== undefined) {\n result = result.slice(0, options.limit)\n }\n\n return result\n}\n","import { useSyncExternalStore, useMemo } from 'react'\nimport type { ObservableCollection } from '../core/client/collection.js'\nimport type { QueryOptions } from '../core/storage/query.js'\nimport { applyQuery } from '../core/storage/query.js'\nimport type { WithMeta } from '../core/schema/types.js'\n\n/**\n * Zero-dependency React hook for reading an OfflineKit collection.\n * Uses React's built-in useSyncExternalStore for reactivity — no TanStack Query required.\n * Returns a read-only array that updates automatically when documents change.\n *\n * For mutations, use collection.create/update/delete directly.\n *\n * @example\n * const todos = useCollectionStore(app.todos)\n * const ipas = useCollectionStore(app.brews, { where: { style: 'IPA' } })\n */\nexport function useCollectionStore<T>(\n collection: ObservableCollection<T>,\n options?: QueryOptions<T>,\n): ReadonlyArray<WithMeta<T>> {\n const snapshot = useSyncExternalStore(\n collection.subscribe,\n collection.getSnapshot,\n (): ReadonlyArray<WithMeta<T>> => [],\n )\n\n // Serialize options to a stable string so inline object literals don't break memoization.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const optionsKey = options !== undefined ? JSON.stringify(options) : undefined\n\n return useMemo(() => {\n if (!options) return snapshot as unknown as WithMeta<T>[]\n return applyQuery([...snapshot] as WithMeta<T>[], options) as WithMeta<T>[]\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [snapshot, optionsKey])\n}\n","import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport type { AuthAdapter } from '../core/auth/types.js'\nimport type { UseAuthResult } from './types.js'\n\nconst AUTH_KEY = ['localkit', 'auth'] as const\n\n/**\n * React hook for LocalKit auth (signUp, signIn, signOut).\n * Uses @tanstack/react-query for state management.\n *\n * @example\n * const { user, signIn } = useAuth(app.auth)\n */\nexport function useAuth(auth: AuthAdapter): UseAuthResult {\n const queryClient = useQueryClient()\n\n const query = useQuery({\n queryKey: AUTH_KEY,\n queryFn: () => Promise.resolve(auth.currentUser()),\n })\n\n const invalidateAuth = () => queryClient.invalidateQueries({ queryKey: AUTH_KEY })\n\n const signUpMut = useMutation({\n mutationFn: (credentials: { email: string; password: string }) => auth.signUp(credentials),\n onSuccess: invalidateAuth,\n })\n\n const signInMut = useMutation({\n mutationFn: (credentials: { email: string; password: string }) => auth.signIn(credentials),\n onSuccess: invalidateAuth,\n })\n\n const signOutMut = useMutation({\n mutationFn: () => auth.signOut(),\n onSuccess: invalidateAuth,\n })\n\n return {\n user: query.data ?? null,\n isLoading: query.isLoading,\n error: (query.error ?? signUpMut.error ?? signInMut.error ?? signOutMut.error) as Error | null,\n signUp: (credentials) => signUpMut.mutateAsync(credentials).then(() => {}),\n signIn: (credentials) => signInMut.mutateAsync(credentials).then(() => {}),\n signOut: () => signOutMut.mutateAsync().then(() => {}),\n }\n}\n","import { useCallback, useEffect } from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { SyncEngine } from '../core/sync/engine.js'\nimport type { SyncStore } from '../core/client/events.js'\nimport type { SyncAPI } from '../core/client/types.js'\nimport type { UseSyncResult } from './types.js'\n\nconst SYNC_KEY = ['localkit', 'sync'] as const\n\ninterface AppWithSync {\n sync: SyncAPI\n syncStore: SyncStore\n}\n\nfunction isAppWithSync(source: SyncEngine | AppWithSync): source is AppWithSync {\n return 'syncStore' in source\n}\n\n/**\n * React hook for observing and triggering LocalKit sync.\n * Accepts either a raw SyncEngine or an App instance (created by createApp).\n *\n * @example\n * const { status, lastSyncAt, sync } = useSync(app)\n * const { status, lastSyncAt, sync } = useSync(syncEngine)\n */\nexport function useSync(source: SyncEngine | AppWithSync): UseSyncResult {\n const queryClient = useQueryClient()\n const isApp = isAppWithSync(source)\n\n const query = useQuery({\n queryKey: SYNC_KEY,\n queryFn: () => {\n if (isApp) {\n const s = (source as AppWithSync).syncStore.getSnapshot()\n return Promise.resolve({ status: s.status, lastSyncAt: s.lastSyncAt ?? 0 })\n }\n const engine = source as SyncEngine\n return Promise.resolve({ status: engine.getStatus(), lastSyncAt: engine.getLastSyncAt() })\n },\n })\n\n useEffect(() => {\n if (isApp) {\n const appSource = source as AppWithSync\n return appSource.syncStore.subscribe(() => {\n const s = appSource.syncStore.getSnapshot()\n queryClient.setQueryData(SYNC_KEY, { status: s.status, lastSyncAt: s.lastSyncAt ?? 0 })\n })\n } else {\n const engine = source as SyncEngine\n const onStart = () =>\n queryClient.setQueryData(SYNC_KEY, { status: 'syncing', lastSyncAt: engine.getLastSyncAt() })\n const onComplete = () =>\n queryClient.setQueryData(SYNC_KEY, { status: 'idle', lastSyncAt: engine.getLastSyncAt() })\n const onError = () =>\n queryClient.setQueryData(SYNC_KEY, { status: engine.getStatus(), lastSyncAt: engine.getLastSyncAt() })\n\n engine.on('sync:start', onStart)\n engine.on('sync:complete', onComplete)\n engine.on('sync:error', onError)\n\n return () => {\n engine.off('sync:start', onStart)\n engine.off('sync:complete', onComplete)\n engine.off('sync:error', onError)\n }\n }\n }, [source, queryClient, isApp])\n\n const sync = useCallback(async (): Promise<void> => {\n if (isApp) {\n const appSource = source as AppWithSync\n await appSource.sync.push()\n await appSource.sync.pull()\n } else {\n await (source as SyncEngine).sync()\n }\n await queryClient.invalidateQueries({ queryKey: SYNC_KEY })\n }, [source, queryClient, isApp])\n\n return {\n status: query.data?.status ?? 'idle',\n lastSyncAt: query.data?.lastSyncAt ?? 0,\n sync,\n }\n}\n","import { useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport type { ObservableCollection } from '../core/client/collection.js'\nimport { subscribeToCollection } from '../core/query.js'\n\ninterface LocalKitProviderProps {\n children: ReactNode\n queryClient?: QueryClient\n collections?: ObservableCollection<unknown>[]\n}\n\nexport function LocalKitProvider({\n children,\n queryClient,\n collections = [],\n}: LocalKitProviderProps) {\n const [defaultClient] = useState(() => new QueryClient())\n const client = queryClient ?? defaultClient\n\n useEffect(() => {\n const unsubs = collections.map(col => subscribeToCollection(col, client))\n return () => {\n for (const unsub of unsubs) unsub()\n }\n }, [client, collections])\n\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { U as User, W as WithMeta, S as SyncStatus, O as ObservableCollection, Q as QueryOptions, d as SyncEngine } from '../collection-CBG-_MiM.js';
|
|
2
|
+
import { A as AuthStore$1 } from '../events-hGI5qde5.js';
|
|
3
|
+
export { c as collectionQueryOptions, l as localkitKeys, s as subscribeToCollection } from '../query-BgIfgMSp.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
interface CollectionStore<T> {
|
|
7
|
+
subscribe(run: (value: WithMeta<T>[]) => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
interface AuthStore {
|
|
10
|
+
subscribe(run: (value: {
|
|
11
|
+
user: User | null;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
}) => void): () => void;
|
|
14
|
+
}
|
|
15
|
+
interface SyncStore {
|
|
16
|
+
subscribe(run: (value: {
|
|
17
|
+
status: SyncStatus;
|
|
18
|
+
lastSyncAt: number;
|
|
19
|
+
}) => void): () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wraps an ObservableCollection into a Svelte-compatible readable store.
|
|
24
|
+
* The store value is updated whenever the collection changes.
|
|
25
|
+
* Supports optional query filtering (where, sort, limit, offset).
|
|
26
|
+
*
|
|
27
|
+
* For full TanStack Query integration, install @tanstack/svelte-query and use
|
|
28
|
+
* `collectionQueryOptions()` with `createQuery()` instead:
|
|
29
|
+
* @example
|
|
30
|
+
* import { collectionQueryOptions } from 'localkit/svelte'
|
|
31
|
+
* import { createQuery } from '@tanstack/svelte-query'
|
|
32
|
+
* const query = createQuery(collectionQueryOptions(app.todos, { where: { done: false } }))
|
|
33
|
+
* // $query.data — reactively updated, cache-backed array
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const todos = createCollectionStore(app.todos, { where: { done: false }, sort: { title: 'asc' } })
|
|
37
|
+
* // In Svelte: $todos — reactively updated filtered array
|
|
38
|
+
*/
|
|
39
|
+
declare function createCollectionStore<T>(collection: ObservableCollection<T>, queryOptions?: QueryOptions<T>): CollectionStore<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Wraps an AuthStore observable into a Svelte-compatible readable store.
|
|
42
|
+
* Reflects the current user and loading state, updating automatically
|
|
43
|
+
* when the auth state changes (sign-in, sign-out, etc.).
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const auth = createAuthStore(app.auth)
|
|
47
|
+
* // In Svelte: $auth.user
|
|
48
|
+
*/
|
|
49
|
+
declare function createAuthStore(auth: AuthStore$1): AuthStore;
|
|
50
|
+
/**
|
|
51
|
+
* Wraps a SyncEngine into a Svelte-compatible readable store.
|
|
52
|
+
* Reflects sync status and last sync timestamp.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const sync = createSyncStore(app.sync)
|
|
56
|
+
* // In Svelte: $sync.status
|
|
57
|
+
*/
|
|
58
|
+
declare function createSyncStore(engine: SyncEngine): SyncStore;
|
|
59
|
+
|
|
60
|
+
export { type AuthStore, type CollectionStore, type SyncStore, createAuthStore, createCollectionStore, createSyncStore };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/core/storage/query.ts
|
|
2
|
+
function isCondition(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4
|
+
const keys = Object.keys(value);
|
|
5
|
+
return keys.some((k) => k === "$gt" || k === "$lt" || k === "$gte" || k === "$lte" || k === "$in");
|
|
6
|
+
}
|
|
7
|
+
function matchesWhere(doc, where) {
|
|
8
|
+
for (const key of Object.keys(where)) {
|
|
9
|
+
const condition = where[key];
|
|
10
|
+
const docValue = doc[key];
|
|
11
|
+
if (isCondition(condition)) {
|
|
12
|
+
if (condition.$gt != null && !(docValue > condition.$gt)) return false;
|
|
13
|
+
if (condition.$lt != null && !(docValue < condition.$lt)) return false;
|
|
14
|
+
if (condition.$gte != null && !(docValue >= condition.$gte)) return false;
|
|
15
|
+
if (condition.$lte != null && !(docValue <= condition.$lte)) return false;
|
|
16
|
+
if (condition.$in !== void 0 && !condition.$in.includes(docValue)) return false;
|
|
17
|
+
} else {
|
|
18
|
+
if (docValue !== condition) return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function applySort(docs, sort) {
|
|
24
|
+
const entries = Object.entries(sort);
|
|
25
|
+
if (entries.length === 0) return docs;
|
|
26
|
+
return [...docs].sort((a, b) => {
|
|
27
|
+
for (const [field, direction] of entries) {
|
|
28
|
+
const av = a[field];
|
|
29
|
+
const bv = b[field];
|
|
30
|
+
if (av === bv) continue;
|
|
31
|
+
const cmp = av < bv ? -1 : 1;
|
|
32
|
+
return direction === "asc" ? cmp : -cmp;
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function applyQuery(docs, options) {
|
|
38
|
+
let result = docs;
|
|
39
|
+
if (options.where) {
|
|
40
|
+
const where = options.where;
|
|
41
|
+
result = result.filter((doc) => matchesWhere(doc, where));
|
|
42
|
+
}
|
|
43
|
+
if (options.sort) {
|
|
44
|
+
result = applySort(result, options.sort);
|
|
45
|
+
}
|
|
46
|
+
const offset = options.offset ?? 0;
|
|
47
|
+
if (offset > 0) {
|
|
48
|
+
result = result.slice(offset);
|
|
49
|
+
}
|
|
50
|
+
if (options.limit !== void 0) {
|
|
51
|
+
result = result.slice(0, options.limit);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/svelte/stores.ts
|
|
57
|
+
function createCollectionStore(collection, queryOptions) {
|
|
58
|
+
const getFiltered = () => {
|
|
59
|
+
const snapshot = [...collection.getSnapshot()];
|
|
60
|
+
return queryOptions ? applyQuery(snapshot, queryOptions) : snapshot;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
subscribe(run) {
|
|
64
|
+
run(getFiltered());
|
|
65
|
+
const unsub = collection.subscribe(() => {
|
|
66
|
+
run(getFiltered());
|
|
67
|
+
});
|
|
68
|
+
return unsub;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createAuthStore(auth) {
|
|
73
|
+
return {
|
|
74
|
+
subscribe(run) {
|
|
75
|
+
const snap = auth.getSnapshot();
|
|
76
|
+
run({ user: snap.user, isLoading: snap.isLoading });
|
|
77
|
+
return auth.subscribe(() => {
|
|
78
|
+
const s = auth.getSnapshot();
|
|
79
|
+
run({ user: s.user, isLoading: s.isLoading });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function createSyncStore(engine) {
|
|
85
|
+
let current = {
|
|
86
|
+
status: engine.getStatus(),
|
|
87
|
+
lastSyncAt: engine.getLastSyncAt()
|
|
88
|
+
};
|
|
89
|
+
const subs = /* @__PURE__ */ new Set();
|
|
90
|
+
const notify = () => {
|
|
91
|
+
for (const run of subs) run(current);
|
|
92
|
+
};
|
|
93
|
+
const onStart = () => {
|
|
94
|
+
current = { ...current, status: "syncing" };
|
|
95
|
+
notify();
|
|
96
|
+
};
|
|
97
|
+
const onComplete = () => {
|
|
98
|
+
current = { status: "idle", lastSyncAt: engine.getLastSyncAt() };
|
|
99
|
+
notify();
|
|
100
|
+
};
|
|
101
|
+
const onError = () => {
|
|
102
|
+
current = { ...current, status: engine.getStatus() };
|
|
103
|
+
notify();
|
|
104
|
+
};
|
|
105
|
+
engine.on("sync:start", onStart);
|
|
106
|
+
engine.on("sync:complete", onComplete);
|
|
107
|
+
engine.on("sync:error", onError);
|
|
108
|
+
return {
|
|
109
|
+
subscribe(run) {
|
|
110
|
+
subs.add(run);
|
|
111
|
+
run(current);
|
|
112
|
+
return () => {
|
|
113
|
+
subs.delete(run);
|
|
114
|
+
if (subs.size === 0) {
|
|
115
|
+
engine.off("sync:start", onStart);
|
|
116
|
+
engine.off("sync:complete", onComplete);
|
|
117
|
+
engine.off("sync:error", onError);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/core/query.ts
|
|
125
|
+
var localkitKeys = {
|
|
126
|
+
all: ["localkit"],
|
|
127
|
+
collection: (name) => ["localkit", name],
|
|
128
|
+
collectionQuery: (name, opts) => ["localkit", name, JSON.stringify(opts)]
|
|
129
|
+
};
|
|
130
|
+
function collectionQueryOptions(collection, queryOptions) {
|
|
131
|
+
const queryKey = queryOptions ? localkitKeys.collectionQuery(collection.name, queryOptions) : localkitKeys.collection(collection.name);
|
|
132
|
+
return {
|
|
133
|
+
queryKey,
|
|
134
|
+
queryFn: () => collection.findMany(queryOptions)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function subscribeToCollection(collection, queryClient) {
|
|
138
|
+
const queryKey = localkitKeys.collection(collection.name);
|
|
139
|
+
if (!("subscribe" in collection)) {
|
|
140
|
+
return () => {
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const obs = collection;
|
|
144
|
+
return obs.subscribe(() => {
|
|
145
|
+
void queryClient.invalidateQueries({ queryKey });
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { collectionQueryOptions, createAuthStore, createCollectionStore, createSyncStore, localkitKeys, subscribeToCollection };
|
|
150
|
+
//# sourceMappingURL=index.js.map
|
|
151
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/storage/query.ts","../../src/svelte/stores.ts","../../src/core/query.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface QueryOptions<T> {\n where?: WhereClause<T>\n sort?: Partial<Record<keyof T, 'asc' | 'desc'>>\n limit?: number\n offset?: number\n}\n\nexport type WhereClause<T> = {\n [K in keyof T]?: T[K] | { $gt?: T[K]; $lt?: T[K]; $gte?: T[K]; $lte?: T[K]; $in?: T[K][] }\n}\n\ntype FieldCondition<V> = { $gt?: V; $lt?: V; $gte?: V; $lte?: V; $in?: V[] }\n\nfunction isCondition<V>(value: unknown): value is FieldCondition<V> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false\n const keys = Object.keys(value)\n return keys.some(k => k === '$gt' || k === '$lt' || k === '$gte' || k === '$lte' || k === '$in')\n}\n\nexport function matchesWhere<T>(doc: T, where: WhereClause<T>): boolean {\n for (const key of Object.keys(where) as Array<keyof T>) {\n const condition = where[key]\n const docValue = doc[key]\n\n if (isCondition<T[typeof key]>(condition)) {\n if (condition.$gt != null && !(docValue > condition.$gt)) return false\n if (condition.$lt != null && !(docValue < condition.$lt)) return false\n if (condition.$gte != null && !(docValue >= condition.$gte)) return false\n if (condition.$lte != null && !(docValue <= condition.$lte)) return false\n if (condition.$in !== undefined && !condition.$in.includes(docValue)) return false\n } else {\n if (docValue !== condition) return false\n }\n }\n return true\n}\n\nexport function applySort<T>(docs: T[], sort: Partial<Record<keyof T, 'asc' | 'desc'>>): T[] {\n const entries = Object.entries(sort) as Array<[keyof T, 'asc' | 'desc']>\n if (entries.length === 0) return docs\n\n return [...docs].sort((a, b) => {\n for (const [field, direction] of entries) {\n const av = a[field]\n const bv = b[field]\n if (av === bv) continue\n const cmp = av < bv ? -1 : 1\n return direction === 'asc' ? cmp : -cmp\n }\n return 0\n })\n}\n\nexport function applyQuery<T>(docs: T[], options: QueryOptions<T>): T[] {\n let result = docs\n\n if (options.where) {\n const where = options.where\n result = result.filter(doc => matchesWhere(doc, where))\n }\n\n if (options.sort) {\n result = applySort(result, options.sort)\n }\n\n const offset = options.offset ?? 0\n if (offset > 0) {\n result = result.slice(offset)\n }\n\n if (options.limit !== undefined) {\n result = result.slice(0, options.limit)\n }\n\n return result\n}\n","import type { WithMeta } from '../core/schema/types.js'\nimport type { ObservableCollection } from '../core/client/collection.js'\nimport type { AuthStore as AuthStoreObservable } from '../core/client/events.js'\nimport type { SyncEngine } from '../core/sync/engine.js'\nimport type { SyncStatus } from '../core/sync/types.js'\nimport type { QueryOptions } from '../core/storage/query.js'\nimport { applyQuery } from '../core/storage/query.js'\nimport type { CollectionStore, AuthStore, SyncStore } from './types.js'\n\n/**\n * Wraps an ObservableCollection into a Svelte-compatible readable store.\n * The store value is updated whenever the collection changes.\n * Supports optional query filtering (where, sort, limit, offset).\n *\n * For full TanStack Query integration, install @tanstack/svelte-query and use\n * `collectionQueryOptions()` with `createQuery()` instead:\n * @example\n * import { collectionQueryOptions } from 'localkit/svelte'\n * import { createQuery } from '@tanstack/svelte-query'\n * const query = createQuery(collectionQueryOptions(app.todos, { where: { done: false } }))\n * // $query.data — reactively updated, cache-backed array\n *\n * @example\n * const todos = createCollectionStore(app.todos, { where: { done: false }, sort: { title: 'asc' } })\n * // In Svelte: $todos — reactively updated filtered array\n */\nexport function createCollectionStore<T>(\n collection: ObservableCollection<T>,\n queryOptions?: QueryOptions<T>,\n): CollectionStore<T> {\n const getFiltered = (): WithMeta<T>[] => {\n const snapshot = [...collection.getSnapshot()] as WithMeta<T>[]\n return queryOptions ? applyQuery(snapshot, queryOptions) as WithMeta<T>[] : snapshot\n }\n\n return {\n subscribe(run: (value: WithMeta<T>[]) => void): () => void {\n // Emit current filtered snapshot immediately\n run(getFiltered())\n\n // Subscribe to collection changes; re-emit filtered snapshot on each notification\n const unsub = collection.subscribe(() => {\n run(getFiltered())\n })\n\n return unsub\n },\n }\n}\n\n/**\n * Wraps an AuthStore observable into a Svelte-compatible readable store.\n * Reflects the current user and loading state, updating automatically\n * when the auth state changes (sign-in, sign-out, etc.).\n *\n * @example\n * const auth = createAuthStore(app.auth)\n * // In Svelte: $auth.user\n */\nexport function createAuthStore(auth: AuthStoreObservable): AuthStore {\n return {\n subscribe(run) {\n const snap = auth.getSnapshot()\n run({ user: snap.user, isLoading: snap.isLoading })\n return auth.subscribe(() => {\n const s = auth.getSnapshot()\n run({ user: s.user, isLoading: s.isLoading })\n })\n },\n }\n}\n\n/**\n * Wraps a SyncEngine into a Svelte-compatible readable store.\n * Reflects sync status and last sync timestamp.\n *\n * @example\n * const sync = createSyncStore(app.sync)\n * // In Svelte: $sync.status\n */\nexport function createSyncStore(engine: SyncEngine): SyncStore {\n type SyncState = { status: SyncStatus; lastSyncAt: number }\n\n let current: SyncState = {\n status: engine.getStatus(),\n lastSyncAt: engine.getLastSyncAt(),\n }\n\n const subs = new Set<(value: SyncState) => void>()\n\n const notify = (): void => {\n for (const run of subs) run(current)\n }\n\n const onStart = (): void => {\n current = { ...current, status: 'syncing' }\n notify()\n }\n\n const onComplete = (): void => {\n current = { status: 'idle', lastSyncAt: engine.getLastSyncAt() }\n notify()\n }\n\n const onError = (): void => {\n current = { ...current, status: engine.getStatus() }\n notify()\n }\n\n engine.on('sync:start', onStart)\n engine.on('sync:complete', onComplete)\n engine.on('sync:error', onError)\n\n return {\n subscribe(run: (value: SyncState) => void): () => void {\n subs.add(run)\n run(current)\n return () => {\n subs.delete(run)\n if (subs.size === 0) {\n engine.off('sync:start', onStart)\n engine.off('sync:complete', onComplete)\n engine.off('sync:error', onError)\n }\n }\n },\n }\n}\n","import type { QueryOptions } from './storage/query.js'\nimport type { WithMeta } from './schema/types.js'\nimport type { Collection } from './client/collection.js'\n\n/** Minimal QueryClient interface — avoids a hard dep on @tanstack/query-core */\ninterface MinimalQueryClient {\n invalidateQueries(opts: { queryKey: readonly unknown[] }): Promise<void>\n}\n\nexport const localkitKeys = {\n all: ['localkit'] as const,\n collection: (name: string) => ['localkit', name] as const,\n collectionQuery: (name: string, opts: unknown) =>\n ['localkit', name, JSON.stringify(opts)] as const,\n}\n\nexport function collectionQueryOptions<T>(\n collection: Collection<T>,\n queryOptions?: QueryOptions<T>,\n): {\n queryKey: readonly unknown[]\n queryFn: () => Promise<WithMeta<T>[]>\n} {\n const queryKey = queryOptions\n ? localkitKeys.collectionQuery(collection.name, queryOptions)\n : localkitKeys.collection(collection.name)\n\n return {\n queryKey,\n queryFn: () => collection.findMany(queryOptions),\n }\n}\n\nexport function subscribeToCollection<T>(\n collection: Collection<T>,\n queryClient: MinimalQueryClient,\n): () => void {\n const queryKey = localkitKeys.collection(collection.name)\n\n if (!('subscribe' in collection)) {\n return () => {}\n }\n\n const obs = collection as Collection<T> & {\n subscribe(listener: () => void): () => void\n }\n\n return obs.subscribe(() => {\n void queryClient.invalidateQueries({ queryKey })\n })\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { A as AuthAdapter, U as User, O as ObservableCollection, Q as QueryOptions, W as WithMeta, d as SyncEngine, S as SyncStatus } from '../collection-CBG-_MiM.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reactive wrapper around an {@link ObservableCollection} for Vue.
|
|
7
|
+
* Automatically subscribes to collection changes and returns a reactive `data` ref.
|
|
8
|
+
*
|
|
9
|
+
* @param collection - The observable collection to subscribe to.
|
|
10
|
+
* @param options - Optional query options (where, sort, limit, offset).
|
|
11
|
+
* @returns An object with a reactive `data` array and an `isLoading` flag.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```vue
|
|
15
|
+
* <script setup>
|
|
16
|
+
* const { data: todos } = useCollection(app.todos, {
|
|
17
|
+
* where: { done: { $eq: false } },
|
|
18
|
+
* sort: { field: 'title', direction: 'asc' },
|
|
19
|
+
* })
|
|
20
|
+
* </script>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function useCollection<T>(collection: ObservableCollection<T>, options?: QueryOptions<T>): {
|
|
24
|
+
data: Ref<WithMeta<T>[]>;
|
|
25
|
+
isLoading: Ref<boolean>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Reactive auth state for Vue. Wraps an {@link AuthAdapter} and exposes
|
|
29
|
+
* reactive `user` and `isLoading` refs, plus `signUp`, `signIn`, and `signOut` methods.
|
|
30
|
+
*
|
|
31
|
+
* @param auth - The auth adapter to wrap.
|
|
32
|
+
* @returns Reactive auth state and action methods.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```vue
|
|
36
|
+
* <script setup>
|
|
37
|
+
* const { user, isLoading, signIn, signOut } = useAuth(app.auth)
|
|
38
|
+
* </script>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function useAuth(auth: AuthAdapter): {
|
|
42
|
+
user: Ref<User | null>;
|
|
43
|
+
isLoading: Ref<boolean>;
|
|
44
|
+
signUp(credentials: {
|
|
45
|
+
email: string;
|
|
46
|
+
password: string;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
signIn(credentials: {
|
|
49
|
+
email: string;
|
|
50
|
+
password: string;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
signOut(): Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Reactive sync state for Vue. Subscribes to sync engine events and exposes
|
|
56
|
+
* reactive `status` and `lastSyncAt` refs, plus a `sync()` trigger.
|
|
57
|
+
*
|
|
58
|
+
* @param engine - The {@link SyncEngine} instance to observe.
|
|
59
|
+
* @returns Reactive sync state and a `sync` function to trigger a sync cycle.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```vue
|
|
63
|
+
* <script setup>
|
|
64
|
+
* const { status, lastSyncAt, sync } = useSync(syncEngine)
|
|
65
|
+
* </script>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare function useSync(engine: SyncEngine): {
|
|
69
|
+
status: Ref<SyncStatus>;
|
|
70
|
+
lastSyncAt: Ref<number>;
|
|
71
|
+
sync(): Promise<void>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
interface UseCollectionReturn<T> {
|
|
75
|
+
data: Ref<WithMeta<T>[]>;
|
|
76
|
+
isLoading: Ref<boolean>;
|
|
77
|
+
}
|
|
78
|
+
interface UseAuthReturn {
|
|
79
|
+
user: Ref<User | null>;
|
|
80
|
+
isLoading: Ref<boolean>;
|
|
81
|
+
signUp(credentials: {
|
|
82
|
+
email: string;
|
|
83
|
+
password: string;
|
|
84
|
+
}): Promise<void>;
|
|
85
|
+
signIn(credentials: {
|
|
86
|
+
email: string;
|
|
87
|
+
password: string;
|
|
88
|
+
}): Promise<void>;
|
|
89
|
+
signOut(): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
interface UseSyncReturn {
|
|
92
|
+
status: Ref<SyncStatus>;
|
|
93
|
+
lastSyncAt: Ref<number>;
|
|
94
|
+
sync(): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { type UseAuthReturn, type UseCollectionReturn, type UseSyncReturn, useAuth, useCollection, useSync };
|