@promakeai/cli 0.0.5 → 0.0.6
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 +214 -135
- package/dist/registry/about-page.json +1 -1
- package/dist/registry/about-section.json +1 -1
- package/dist/registry/api.json +55 -0
- package/dist/registry/auth.json +70 -0
- package/dist/registry/bento-grid-section.json +1 -1
- package/dist/registry/blog-list-page.json +1 -1
- package/dist/registry/blog-section.json +1 -1
- package/dist/registry/cart-drawer.json +1 -1
- package/dist/registry/cart-page.json +3 -2
- package/dist/registry/category-section.json +1 -1
- package/dist/registry/checkout-page.json +3 -2
- package/dist/registry/contact-info-grid.json +1 -1
- package/dist/registry/contact-page-centered.json +1 -1
- package/dist/registry/contact-page-map-overlay.json +1 -1
- package/dist/registry/contact-page.json +1 -1
- package/dist/registry/cookies-page.json +1 -1
- package/dist/registry/cta-section.json +1 -1
- package/dist/registry/db.json +129 -0
- package/dist/registry/docs/cart-page.md +1 -0
- package/dist/registry/docs/checkout-page.md +1 -0
- package/dist/registry/docs/forgot-password-page.md +37 -0
- package/dist/registry/docs/header-ecommerce.md +1 -0
- package/dist/registry/docs/products-page.md +1 -0
- package/dist/registry/docs/register-page.md +39 -0
- package/dist/registry/ecommerce-core.json +1 -1
- package/dist/registry/empty-page.json +1 -1
- package/dist/registry/faq-categorized.json +1 -1
- package/dist/registry/faq-simple.json +1 -1
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/feature-section.json +1 -1
- package/dist/registry/featured-products.json +1 -1
- package/dist/registry/footer-detailed.json +1 -1
- package/dist/registry/footer-minimal.json +3 -3
- package/dist/registry/footer.json +1 -1
- package/dist/registry/forgot-password-page.json +49 -0
- package/dist/registry/header-ecommerce.json +3 -2
- package/dist/registry/header-mega.json +1 -1
- package/dist/registry/header-minimal.json +1 -1
- package/dist/registry/header-simple.json +1 -1
- package/dist/registry/hero-cta.json +1 -1
- package/dist/registry/hero-gradient.json +1 -1
- package/dist/registry/hero-profile.json +1 -1
- package/dist/registry/hero.json +1 -1
- package/dist/registry/index.json +3 -0
- package/dist/registry/orders-list-block.json +1 -1
- package/dist/registry/payment-success-block.json +1 -1
- package/dist/registry/post-detail-block.json +1 -1
- package/dist/registry/pricing-section.json +1 -1
- package/dist/registry/privacy-page.json +1 -1
- package/dist/registry/products-page.json +3 -2
- package/dist/registry/register-page.json +49 -0
- package/dist/registry/related-posts-block.json +1 -1
- package/dist/registry/terms-page.json +1 -1
- package/dist/registry/testimonials-carousel.json +1 -1
- package/dist/registry/testimonials-grid.json +1 -1
- package/package.json +1 -1
- package/template/src/App.tsx +3 -24
- package/template/src/components/Layout.tsx +0 -4
- package/template/src/index.css +1 -0
- package/template/src/lang/en/index.json +1 -28
- package/template/src/lang/tr/index.json +1 -28
- package/template/src/pages/Index.tsx +1 -102
- package/template/src/components/Footer.tsx +0 -100
- package/template/src/components/Header.tsx +0 -79
- package/template/src/components/Hero.tsx +0 -69
- package/template/src/modules/api/USAGE.md +0 -515
- package/template/src/modules/api/customer-client.ts +0 -20
- package/template/src/modules/api/get-error-message.ts +0 -18
- package/template/src/modules/api/validation/en.json +0 -29
- package/template/src/modules/api/validation/tr.json +0 -29
- package/template/src/modules/auth/USAGE.md +0 -248
- package/template/src/modules/auth/auth-header-menu.tsx +0 -123
- package/template/src/modules/auth/auth-store.ts +0 -57
- package/template/src/modules/auth/forgot-password-page.tsx +0 -371
- package/template/src/modules/auth/login-page.tsx +0 -183
- package/template/src/modules/auth/register-page.tsx +0 -252
- package/template/src/modules/auth/use-auth.ts +0 -273
- package/template/src/modules/db/adapters/IDataAdapter.ts +0 -26
- package/template/src/modules/db/adapters/SqliteAdapter.ts +0 -364
- package/template/src/modules/db/adapters/index.ts +0 -2
- package/template/src/modules/db/config.ts +0 -59
- package/template/src/modules/db/core/DataManager.ts +0 -125
- package/template/src/modules/db/core/types.ts +0 -101
- package/template/src/modules/db/index.ts +0 -42
- package/template/src/modules/db/react/QueryProvider.tsx +0 -16
- package/template/src/modules/db/react/index.ts +0 -23
- package/template/src/modules/db/react/queryClient.ts +0 -64
- package/template/src/modules/db/react/useRepository.ts +0 -400
- package/template/src/modules/db/utils/parsers.ts +0 -96
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// Provider
|
|
2
|
-
export { DBQueryProvider } from "./QueryProvider";
|
|
3
|
-
|
|
4
|
-
// Query client and utilities
|
|
5
|
-
export { queryClient, queryKeys, cacheUtils } from "./queryClient";
|
|
6
|
-
|
|
7
|
-
// Generic repository hooks
|
|
8
|
-
export {
|
|
9
|
-
useRepositoryQuery,
|
|
10
|
-
useRepositoryQueryOne,
|
|
11
|
-
useRepositoryQueryById,
|
|
12
|
-
useRepositoryPagination,
|
|
13
|
-
useRepositoryInfiniteQuery,
|
|
14
|
-
useRepositoryCreate,
|
|
15
|
-
useRepositoryUpdate,
|
|
16
|
-
useRepositoryDelete,
|
|
17
|
-
// Raw SQL hooks
|
|
18
|
-
useRawQuery,
|
|
19
|
-
useRawQueryOne,
|
|
20
|
-
} from "./useRepository";
|
|
21
|
-
|
|
22
|
-
// Types
|
|
23
|
-
export type { RepositoryQueryOptions } from "./useRepository";
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { QueryClient } from "@tanstack/react-query";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* React Query handles ALL caching, refetching, and invalidation
|
|
5
|
-
* No custom cache needed!
|
|
6
|
-
*/
|
|
7
|
-
export const queryClient = new QueryClient({
|
|
8
|
-
defaultOptions: {
|
|
9
|
-
queries: {
|
|
10
|
-
staleTime: 30 * 1000, // 30 seconds fresh
|
|
11
|
-
gcTime: 5 * 60 * 1000, // 5 minutes in cache (was cacheTime)
|
|
12
|
-
retry: 1,
|
|
13
|
-
refetchOnWindowFocus: false, // Don't auto-refetch on window focus
|
|
14
|
-
refetchOnReconnect: false, // Don't refetch on reconnect
|
|
15
|
-
refetchOnMount: false, // Don't refetch on component mount (prevent loops)
|
|
16
|
-
},
|
|
17
|
-
mutations: {
|
|
18
|
-
retry: 0,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Query key factory - for cache management
|
|
25
|
-
* React Query uses these keys to cache and invalidate queries
|
|
26
|
-
*/
|
|
27
|
-
export const queryKeys = {
|
|
28
|
-
all: (table: string) => [table] as const,
|
|
29
|
-
lists: (table: string) => [table, "list"] as const,
|
|
30
|
-
list: (table: string, options?: any) => [table, "list", options] as const,
|
|
31
|
-
details: (table: string) => [table, "detail"] as const,
|
|
32
|
-
detail: (table: string, id: number | string) =>
|
|
33
|
-
[table, "detail", id] as const,
|
|
34
|
-
paginated: (table: string, page: number, limit: number, options?: any) =>
|
|
35
|
-
[table, "paginated", page, limit, options] as const,
|
|
36
|
-
infinite: (table: string, limit: number, options?: any) =>
|
|
37
|
-
[table, "infinite", limit, options] as const,
|
|
38
|
-
count: (table: string, options?: any) => [table, "count", options] as const,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Manual cache utilities (rarely needed)
|
|
43
|
-
*/
|
|
44
|
-
export const cacheUtils = {
|
|
45
|
-
// Invalidate all queries for a table
|
|
46
|
-
invalidateTable: (table: string) => {
|
|
47
|
-
return queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
// Clear all cache
|
|
51
|
-
clearAll: () => {
|
|
52
|
-
return queryClient.clear();
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
// Get cached data
|
|
56
|
-
getCachedData: <T>(queryKey: any[]) => {
|
|
57
|
-
return queryClient.getQueryData<T>(queryKey);
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
// Set cached data manually
|
|
61
|
-
setCachedData: <T>(queryKey: any[], data: T) => {
|
|
62
|
-
return queryClient.setQueryData<T>(queryKey, data);
|
|
63
|
-
},
|
|
64
|
-
};
|
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useQuery,
|
|
3
|
-
useMutation,
|
|
4
|
-
useQueryClient,
|
|
5
|
-
useInfiniteQuery,
|
|
6
|
-
type UseQueryOptions,
|
|
7
|
-
type UseMutationOptions,
|
|
8
|
-
type UseInfiniteQueryOptions,
|
|
9
|
-
} from "@tanstack/react-query";
|
|
10
|
-
import { DataManager } from "../core/DataManager";
|
|
11
|
-
import { getAdapter } from "../config";
|
|
12
|
-
import { queryKeys } from "./queryClient";
|
|
13
|
-
import type { QueryOptions } from "../core/types";
|
|
14
|
-
|
|
15
|
-
// Singleton manager
|
|
16
|
-
let managerInstance: DataManager | null = null;
|
|
17
|
-
function getManager() {
|
|
18
|
-
if (!managerInstance) {
|
|
19
|
-
managerInstance = DataManager.getInstance(getAdapter());
|
|
20
|
-
}
|
|
21
|
-
return managerInstance;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// ==========================================
|
|
25
|
-
// QUERY HOOKS
|
|
26
|
-
// ==========================================
|
|
27
|
-
|
|
28
|
-
// Omit 'select' from QueryOptions to avoid conflict with React Query's select
|
|
29
|
-
export interface RepositoryQueryOptions<T> extends Omit<
|
|
30
|
-
QueryOptions,
|
|
31
|
-
"select"
|
|
32
|
-
> {
|
|
33
|
-
// SQL SELECT fields (renamed to avoid conflict)
|
|
34
|
-
selectFields?: string[];
|
|
35
|
-
|
|
36
|
-
// React Query options
|
|
37
|
-
enabled?: boolean;
|
|
38
|
-
staleTime?: number;
|
|
39
|
-
gcTime?: number;
|
|
40
|
-
refetchOnWindowFocus?: boolean;
|
|
41
|
-
refetchInterval?: number | false;
|
|
42
|
-
select?: (data: T[]) => any; // React Query data transformation
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Generic query hook - React Query handles all caching
|
|
47
|
-
* @example
|
|
48
|
-
* // Simple query
|
|
49
|
-
* const { data: posts, isLoading } = useRepositoryQuery('posts', {
|
|
50
|
-
* where: { published: 1 },
|
|
51
|
-
* orderBy: [{ field: 'created_at', direction: 'DESC' }],
|
|
52
|
-
* staleTime: 60000
|
|
53
|
-
* });
|
|
54
|
-
*
|
|
55
|
-
* // With JOIN
|
|
56
|
-
* const { data: posts } = useRepositoryQuery('posts', {
|
|
57
|
-
* selectFields: ['posts.*', 'c.name as category_name'],
|
|
58
|
-
* joins: [{
|
|
59
|
-
* type: 'INNER',
|
|
60
|
-
* table: 'post_categories',
|
|
61
|
-
* alias: 'pc',
|
|
62
|
-
* on: { leftField: 'posts.id', rightField: 'pc.post_id' }
|
|
63
|
-
* }],
|
|
64
|
-
* distinct: true
|
|
65
|
-
* });
|
|
66
|
-
*
|
|
67
|
-
* // With complex WHERE
|
|
68
|
-
* const { data: products } = useRepositoryQuery('products', {
|
|
69
|
-
* whereAdvanced: {
|
|
70
|
-
* type: 'AND',
|
|
71
|
-
* conditions: [
|
|
72
|
-
* { field: 'published', operator: '=', value: 1 },
|
|
73
|
-
* { type: 'OR', conditions: [
|
|
74
|
-
* { field: 'name', operator: 'LIKE', value: '%phone%' },
|
|
75
|
-
* { field: 'description', operator: 'LIKE', value: '%phone%' }
|
|
76
|
-
* ]}
|
|
77
|
-
* ]
|
|
78
|
-
* }
|
|
79
|
-
* });
|
|
80
|
-
*/
|
|
81
|
-
export function useRepositoryQuery<T = any>(
|
|
82
|
-
table: string,
|
|
83
|
-
options: RepositoryQueryOptions<T> = {},
|
|
84
|
-
queryOptions?: Omit<UseQueryOptions<T[], Error>, "queryKey" | "queryFn">,
|
|
85
|
-
) {
|
|
86
|
-
const manager = getManager();
|
|
87
|
-
const {
|
|
88
|
-
// QueryOptions fields
|
|
89
|
-
where,
|
|
90
|
-
limit,
|
|
91
|
-
offset,
|
|
92
|
-
orderBy,
|
|
93
|
-
include,
|
|
94
|
-
// New complex query fields
|
|
95
|
-
selectFields,
|
|
96
|
-
distinct,
|
|
97
|
-
joins,
|
|
98
|
-
whereAdvanced,
|
|
99
|
-
groupBy,
|
|
100
|
-
having,
|
|
101
|
-
// React Query options
|
|
102
|
-
select,
|
|
103
|
-
enabled,
|
|
104
|
-
staleTime,
|
|
105
|
-
gcTime,
|
|
106
|
-
refetchOnWindowFocus,
|
|
107
|
-
refetchInterval,
|
|
108
|
-
} = options;
|
|
109
|
-
|
|
110
|
-
const queryOpts: QueryOptions = {
|
|
111
|
-
where,
|
|
112
|
-
limit,
|
|
113
|
-
offset,
|
|
114
|
-
orderBy,
|
|
115
|
-
include,
|
|
116
|
-
select: selectFields,
|
|
117
|
-
distinct,
|
|
118
|
-
joins,
|
|
119
|
-
whereAdvanced,
|
|
120
|
-
groupBy,
|
|
121
|
-
having,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
return useQuery<T[], Error>({
|
|
125
|
-
queryKey: queryKeys.list(table, queryOpts),
|
|
126
|
-
queryFn: () => manager.query<T>(table, queryOpts),
|
|
127
|
-
select,
|
|
128
|
-
enabled,
|
|
129
|
-
staleTime,
|
|
130
|
-
gcTime,
|
|
131
|
-
refetchOnWindowFocus,
|
|
132
|
-
refetchInterval,
|
|
133
|
-
...queryOptions,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Query single record
|
|
139
|
-
* @example
|
|
140
|
-
* const { data: post } = useRepositoryQueryOne('posts', {
|
|
141
|
-
* where: { slug: 'my-post' }
|
|
142
|
-
* });
|
|
143
|
-
*/
|
|
144
|
-
export function useRepositoryQueryOne<T = any>(
|
|
145
|
-
table: string,
|
|
146
|
-
options: RepositoryQueryOptions<T> = {},
|
|
147
|
-
) {
|
|
148
|
-
const result = useRepositoryQuery<T>(table, { ...options, limit: 1 });
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
...result,
|
|
152
|
-
data: result.data?.[0] || null,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Query by ID - React Query caches by ID automatically
|
|
158
|
-
* @example
|
|
159
|
-
* const { data: post, isLoading } = useRepositoryQueryById('posts', postId);
|
|
160
|
-
*/
|
|
161
|
-
export function useRepositoryQueryById<T = any>(
|
|
162
|
-
table: string,
|
|
163
|
-
id: number | string | null | undefined,
|
|
164
|
-
options: Omit<UseQueryOptions<T | null, Error>, "queryKey" | "queryFn"> = {},
|
|
165
|
-
) {
|
|
166
|
-
const manager = getManager();
|
|
167
|
-
|
|
168
|
-
return useQuery<T | null, Error>({
|
|
169
|
-
queryKey: queryKeys.detail(table, id as any),
|
|
170
|
-
queryFn: () => manager.queryById<T>(table, id as any),
|
|
171
|
-
enabled: options.enabled !== false && id != null,
|
|
172
|
-
...options,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Paginated query - React Query caches each page
|
|
178
|
-
* @example
|
|
179
|
-
* const { data, totalPages, hasMore } = useRepositoryPagination('products', page, 20);
|
|
180
|
-
*/
|
|
181
|
-
export function useRepositoryPagination<T = any>(
|
|
182
|
-
table: string,
|
|
183
|
-
page: number = 1,
|
|
184
|
-
limit: number = 10,
|
|
185
|
-
options: QueryOptions = {},
|
|
186
|
-
) {
|
|
187
|
-
const manager = getManager();
|
|
188
|
-
|
|
189
|
-
return useQuery({
|
|
190
|
-
queryKey: queryKeys.paginated(table, page, limit, options),
|
|
191
|
-
queryFn: () => manager.paginate<T>(table, page, limit, options),
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Infinite query for infinite scroll / load more
|
|
197
|
-
* @example
|
|
198
|
-
* const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
|
199
|
-
* useRepositoryInfiniteQuery('posts', 20, {
|
|
200
|
-
* where: { published: 1 },
|
|
201
|
-
* orderBy: [{ field: 'created_at', direction: 'DESC' }]
|
|
202
|
-
* });
|
|
203
|
-
*
|
|
204
|
-
* // data.pages = [page1Data, page2Data, page3Data, ...]
|
|
205
|
-
* const allPosts = data?.pages.flatMap(page => page.data) ?? [];
|
|
206
|
-
*/
|
|
207
|
-
export function useRepositoryInfiniteQuery<T = any>(
|
|
208
|
-
table: string,
|
|
209
|
-
pageSize: number = 20,
|
|
210
|
-
options: QueryOptions = {},
|
|
211
|
-
queryOptions?: Omit<
|
|
212
|
-
UseInfiniteQueryOptions<
|
|
213
|
-
{ data: T[]; page: number; totalPages: number; hasMore: boolean },
|
|
214
|
-
Error
|
|
215
|
-
>,
|
|
216
|
-
"queryKey" | "queryFn" | "getNextPageParam" | "initialPageParam"
|
|
217
|
-
>,
|
|
218
|
-
) {
|
|
219
|
-
const manager = getManager();
|
|
220
|
-
|
|
221
|
-
return useInfiniteQuery({
|
|
222
|
-
queryKey: queryKeys.infinite(table, pageSize, options),
|
|
223
|
-
queryFn: ({ pageParam }) =>
|
|
224
|
-
manager.paginate<T>(table, pageParam as number, pageSize, options),
|
|
225
|
-
initialPageParam: 1,
|
|
226
|
-
getNextPageParam: (lastPage) => {
|
|
227
|
-
if (!lastPage.hasMore) return undefined;
|
|
228
|
-
return lastPage.page + 1;
|
|
229
|
-
},
|
|
230
|
-
...queryOptions,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ==========================================
|
|
235
|
-
// MUTATION HOOKS (Auto-invalidation via React Query)
|
|
236
|
-
// ==========================================
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Create mutation - React Query handles cache invalidation
|
|
240
|
-
* @example
|
|
241
|
-
* const { mutate: createPost } = useRepositoryCreate('posts', {
|
|
242
|
-
* onSuccess: () => toast.success('Created!')
|
|
243
|
-
* });
|
|
244
|
-
*/
|
|
245
|
-
export function useRepositoryCreate<T = any>(
|
|
246
|
-
table: string,
|
|
247
|
-
options: Omit<UseMutationOptions<T, Error, Partial<T>>, "mutationFn"> & {
|
|
248
|
-
invalidate?: string[];
|
|
249
|
-
} = {},
|
|
250
|
-
) {
|
|
251
|
-
const manager = getManager();
|
|
252
|
-
const queryClient = useQueryClient();
|
|
253
|
-
const { invalidate = [table], ...mutationOptions } = options;
|
|
254
|
-
|
|
255
|
-
return useMutation<T, Error, Partial<T>>({
|
|
256
|
-
mutationFn: (data) => manager.create<T>(table, data),
|
|
257
|
-
onSuccess: () => {
|
|
258
|
-
// React Query automatically invalidates and refetches
|
|
259
|
-
invalidate.forEach((t) => {
|
|
260
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.all(t) });
|
|
261
|
-
});
|
|
262
|
-
},
|
|
263
|
-
...mutationOptions,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Update mutation - Optimistic update via React Query
|
|
269
|
-
* @example
|
|
270
|
-
* const { mutate: updatePost } = useRepositoryUpdate('posts', {
|
|
271
|
-
* onSuccess: () => toast.success('Updated!')
|
|
272
|
-
* });
|
|
273
|
-
* updatePost({ id: 1, data: { title: 'New Title' } });
|
|
274
|
-
*/
|
|
275
|
-
export function useRepositoryUpdate<T = any>(
|
|
276
|
-
table: string,
|
|
277
|
-
options: Omit<
|
|
278
|
-
UseMutationOptions<T, Error, { id: number | string; data: Partial<T> }>,
|
|
279
|
-
"mutationFn"
|
|
280
|
-
> = {},
|
|
281
|
-
) {
|
|
282
|
-
const manager = getManager();
|
|
283
|
-
const queryClient = useQueryClient();
|
|
284
|
-
|
|
285
|
-
return useMutation<T, Error, { id: number | string; data: Partial<T> }>({
|
|
286
|
-
mutationFn: ({ id, data }) => manager.update<T>(table, id, data),
|
|
287
|
-
onSuccess: (data, variables) => {
|
|
288
|
-
// Invalidate list queries
|
|
289
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });
|
|
290
|
-
|
|
291
|
-
// Update detail cache optimistically
|
|
292
|
-
queryClient.setQueryData(queryKeys.detail(table, variables.id), data);
|
|
293
|
-
},
|
|
294
|
-
...options,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Delete mutation
|
|
300
|
-
* @example
|
|
301
|
-
* const { mutate: deletePost } = useRepositoryDelete('posts', {
|
|
302
|
-
* onSuccess: () => toast.success('Deleted!')
|
|
303
|
-
* });
|
|
304
|
-
* deletePost(postId);
|
|
305
|
-
*/
|
|
306
|
-
export function useRepositoryDelete(
|
|
307
|
-
table: string,
|
|
308
|
-
options: Omit<
|
|
309
|
-
UseMutationOptions<boolean, Error, number | string>,
|
|
310
|
-
"mutationFn"
|
|
311
|
-
> = {},
|
|
312
|
-
) {
|
|
313
|
-
const manager = getManager();
|
|
314
|
-
const queryClient = useQueryClient();
|
|
315
|
-
|
|
316
|
-
return useMutation<boolean, Error, number | string>({
|
|
317
|
-
mutationFn: (id) => manager.delete(table, id),
|
|
318
|
-
onSuccess: (_data, id) => {
|
|
319
|
-
// Invalidate and remove from cache
|
|
320
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.all(table) });
|
|
321
|
-
queryClient.removeQueries({ queryKey: queryKeys.detail(table, id) });
|
|
322
|
-
},
|
|
323
|
-
...options,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ==========================================
|
|
328
|
-
// RAW SQL QUERY HOOKS
|
|
329
|
-
// ==========================================
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Raw SQL query hook - for complex queries that can't be expressed with QueryOptions
|
|
333
|
-
* @example
|
|
334
|
-
* // Complex JOIN query
|
|
335
|
-
* const { data: posts } = useRawQuery<Post>(
|
|
336
|
-
* ['posts-with-categories', categorySlug],
|
|
337
|
-
* `SELECT DISTINCT p.*, c.name as category_name
|
|
338
|
-
* FROM posts p
|
|
339
|
-
* JOIN post_categories pc ON p.id = pc.post_id
|
|
340
|
-
* JOIN blog_categories c ON pc.category_id = c.id
|
|
341
|
-
* WHERE c.slug = ? AND p.published = 1
|
|
342
|
-
* ORDER BY p.published_at DESC`,
|
|
343
|
-
* [categorySlug]
|
|
344
|
-
* );
|
|
345
|
-
*
|
|
346
|
-
* // Aggregation query
|
|
347
|
-
* const { data: stats } = useRawQuery<{total: number, avg: number}>(
|
|
348
|
-
* ['product-stats'],
|
|
349
|
-
* `SELECT COUNT(*) as total, AVG(price) as avg FROM products WHERE published = 1`
|
|
350
|
-
* );
|
|
351
|
-
*/
|
|
352
|
-
export function useRawQuery<T = any>(
|
|
353
|
-
queryKey: any[],
|
|
354
|
-
sql: string,
|
|
355
|
-
params?: any[],
|
|
356
|
-
options?: Omit<UseQueryOptions<T[], Error>, "queryKey" | "queryFn">,
|
|
357
|
-
) {
|
|
358
|
-
const manager = getManager();
|
|
359
|
-
|
|
360
|
-
return useQuery<T[], Error>({
|
|
361
|
-
queryKey: ["raw", ...queryKey],
|
|
362
|
-
queryFn: () => manager.raw<T>(sql, params),
|
|
363
|
-
...options,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Raw SQL query hook for single result - aggregations, single lookups
|
|
369
|
-
* @example
|
|
370
|
-
* // Get price range
|
|
371
|
-
* const { data: priceRange } = useRawQueryOne<{min: number, max: number}>(
|
|
372
|
-
* ['price-range'],
|
|
373
|
-
* `SELECT MIN(price) as min, MAX(price) as max FROM products WHERE published = 1`
|
|
374
|
-
* );
|
|
375
|
-
*
|
|
376
|
-
* // Get single post with category
|
|
377
|
-
* const { data: post } = useRawQueryOne<Post>(
|
|
378
|
-
* ['post-detail', slug],
|
|
379
|
-
* `SELECT p.*, c.name as category_name
|
|
380
|
-
* FROM posts p
|
|
381
|
-
* LEFT JOIN post_categories pc ON p.id = pc.post_id
|
|
382
|
-
* LEFT JOIN blog_categories c ON pc.category_id = c.id
|
|
383
|
-
* WHERE p.slug = ?`,
|
|
384
|
-
* [slug]
|
|
385
|
-
* );
|
|
386
|
-
*/
|
|
387
|
-
export function useRawQueryOne<T = any>(
|
|
388
|
-
queryKey: any[],
|
|
389
|
-
sql: string,
|
|
390
|
-
params?: any[],
|
|
391
|
-
options?: Omit<UseQueryOptions<T | null, Error>, "queryKey" | "queryFn">,
|
|
392
|
-
) {
|
|
393
|
-
const manager = getManager();
|
|
394
|
-
|
|
395
|
-
return useQuery<T | null, Error>({
|
|
396
|
-
queryKey: ["raw", ...queryKey],
|
|
397
|
-
queryFn: () => manager.rawOne<T>(sql, params),
|
|
398
|
-
...options,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database field parsers - Client-side utilities
|
|
3
|
-
* NO automatic parsing - client decides what to parse and when
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Parse comma-separated string to array
|
|
8
|
-
* @example "tag1,tag2,tag3" -> ["tag1", "tag2", "tag3"]
|
|
9
|
-
*/
|
|
10
|
-
export const parseCommaSeparatedString = (value: string): string[] => {
|
|
11
|
-
if (!value || typeof value !== "string") return [];
|
|
12
|
-
return value
|
|
13
|
-
.split(",")
|
|
14
|
-
.map((item) => item.trim())
|
|
15
|
-
.filter(Boolean);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Parse JSON string to array
|
|
20
|
-
* @example '["img1.jpg","img2.jpg"]' -> ["img1.jpg", "img2.jpg"]
|
|
21
|
-
*/
|
|
22
|
-
export const parseJSONStringToArray = (value: string): string[] => {
|
|
23
|
-
if (!value || typeof value !== "string") return [];
|
|
24
|
-
try {
|
|
25
|
-
const parsed = JSON.parse(value);
|
|
26
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
27
|
-
} catch (e) {
|
|
28
|
-
console.warn("Failed to parse JSON array:", value);
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Smart array parser - tries JSON first, falls back to comma-separated
|
|
35
|
-
* @example '["a","b"]' -> ["a", "b"] OR "a,b" -> ["a", "b"]
|
|
36
|
-
*/
|
|
37
|
-
export const parseStringToArray = (value: any): string[] => {
|
|
38
|
-
if (!value) return [];
|
|
39
|
-
if (Array.isArray(value)) return value;
|
|
40
|
-
if (typeof value !== "string") return [];
|
|
41
|
-
|
|
42
|
-
// Try JSON first
|
|
43
|
-
if (value.trim().startsWith("[")) {
|
|
44
|
-
const jsonResult = parseJSONStringToArray(value);
|
|
45
|
-
if (jsonResult.length > 0) return jsonResult;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Fall back to comma-separated
|
|
49
|
-
return parseCommaSeparatedString(value);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Parse JSON string to object
|
|
54
|
-
* @example '{"key":"value"}' -> {key: "value"}
|
|
55
|
-
*/
|
|
56
|
-
export const parseJSONString = <T = any>(
|
|
57
|
-
value: any,
|
|
58
|
-
defaultValue: T | null = null,
|
|
59
|
-
): T | null => {
|
|
60
|
-
if (!value) return defaultValue;
|
|
61
|
-
if (typeof value === "object") return value; // Already parsed
|
|
62
|
-
if (typeof value !== "string") return defaultValue;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
return JSON.parse(value);
|
|
66
|
-
} catch (e) {
|
|
67
|
-
console.warn("Failed to parse JSON:", value);
|
|
68
|
-
return defaultValue;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Parse SQLite boolean (0/1) to JavaScript boolean
|
|
74
|
-
* @example 1 -> true, 0 -> false
|
|
75
|
-
*/
|
|
76
|
-
export const parseSQLiteBoolean = (value: any): boolean => {
|
|
77
|
-
if (typeof value === "boolean") return value;
|
|
78
|
-
if (typeof value === "number") return value !== 0;
|
|
79
|
-
if (typeof value === "string") {
|
|
80
|
-
const lower = value.toLowerCase();
|
|
81
|
-
return lower === "true" || lower === "1" || lower === "yes";
|
|
82
|
-
}
|
|
83
|
-
return Boolean(value);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Parse number safely with default fallback
|
|
88
|
-
* @example "123" -> 123, "invalid" -> 0 (or provided default)
|
|
89
|
-
*/
|
|
90
|
-
export const parseNumberSafe = (
|
|
91
|
-
value: any,
|
|
92
|
-
defaultValue: number = 0,
|
|
93
|
-
): number => {
|
|
94
|
-
const num = Number(value);
|
|
95
|
-
return isNaN(num) ? defaultValue : num;
|
|
96
|
-
};
|