@fluxbase/sdk-react 0.0.1-rc.2
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/CHANGELOG.md +67 -0
- package/README-ADMIN.md +1076 -0
- package/README.md +178 -0
- package/dist/index.d.mts +606 -0
- package/dist/index.d.ts +606 -0
- package/dist/index.js +992 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +926 -0
- package/dist/index.mjs.map +1 -0
- package/examples/AdminDashboard.tsx +513 -0
- package/examples/README.md +163 -0
- package/package.json +52 -0
- package/src/context.tsx +33 -0
- package/src/index.ts +113 -0
- package/src/use-admin-auth.ts +168 -0
- package/src/use-admin-hooks.ts +309 -0
- package/src/use-api-keys.ts +174 -0
- package/src/use-auth.ts +146 -0
- package/src/use-query.ts +165 -0
- package/src/use-realtime.ts +161 -0
- package/src/use-rpc.ts +109 -0
- package/src/use-storage.ts +257 -0
- package/src/use-users.ts +191 -0
- package/tsconfig.json +24 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +11 -0
- package/typedoc.json +35 -0
package/src/use-query.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database query hooks for Fluxbase SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useQuery, useMutation, useQueryClient, type UseQueryOptions } from '@tanstack/react-query'
|
|
6
|
+
import { useFluxbaseClient } from './context'
|
|
7
|
+
import type { QueryBuilder } from '@fluxbase/sdk'
|
|
8
|
+
|
|
9
|
+
export interface UseFluxbaseQueryOptions<T> extends Omit<UseQueryOptions<T[], Error>, 'queryKey' | 'queryFn'> {
|
|
10
|
+
/**
|
|
11
|
+
* Custom query key. If not provided, will use table name and filters.
|
|
12
|
+
*/
|
|
13
|
+
queryKey?: unknown[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook to execute a database query
|
|
18
|
+
* @param buildQuery - Function that builds and returns the query
|
|
19
|
+
* @param options - React Query options
|
|
20
|
+
*/
|
|
21
|
+
export function useFluxbaseQuery<T = any>(
|
|
22
|
+
buildQuery: (client: ReturnType<typeof useFluxbaseClient>) => QueryBuilder<T>,
|
|
23
|
+
options?: UseFluxbaseQueryOptions<T>
|
|
24
|
+
) {
|
|
25
|
+
const client = useFluxbaseClient()
|
|
26
|
+
|
|
27
|
+
// Build a stable query key
|
|
28
|
+
const queryKey = options?.queryKey || ['fluxbase', 'query', buildQuery.toString()]
|
|
29
|
+
|
|
30
|
+
return useQuery({
|
|
31
|
+
queryKey,
|
|
32
|
+
queryFn: async () => {
|
|
33
|
+
const query = buildQuery(client)
|
|
34
|
+
const { data, error } = await query.execute()
|
|
35
|
+
|
|
36
|
+
if (error) {
|
|
37
|
+
throw error
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (Array.isArray(data) ? data : data ? [data] : []) as T[]
|
|
41
|
+
},
|
|
42
|
+
...options,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hook for table queries with a simpler API
|
|
48
|
+
* @param table - Table name
|
|
49
|
+
* @param buildQuery - Function to build the query
|
|
50
|
+
*/
|
|
51
|
+
export function useTable<T = any>(
|
|
52
|
+
table: string,
|
|
53
|
+
buildQuery?: (query: QueryBuilder<T>) => QueryBuilder<T>,
|
|
54
|
+
options?: UseFluxbaseQueryOptions<T>
|
|
55
|
+
) {
|
|
56
|
+
const client = useFluxbaseClient()
|
|
57
|
+
|
|
58
|
+
return useFluxbaseQuery(
|
|
59
|
+
(client) => {
|
|
60
|
+
const query = client.from<T>(table)
|
|
61
|
+
return buildQuery ? buildQuery(query) : query
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
...options,
|
|
65
|
+
queryKey: options?.queryKey || ['fluxbase', 'table', table, buildQuery?.toString()],
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Hook to insert data into a table
|
|
72
|
+
*/
|
|
73
|
+
export function useInsert<T = any>(table: string) {
|
|
74
|
+
const client = useFluxbaseClient()
|
|
75
|
+
const queryClient = useQueryClient()
|
|
76
|
+
|
|
77
|
+
return useMutation({
|
|
78
|
+
mutationFn: async (data: Partial<T> | Partial<T>[]) => {
|
|
79
|
+
const query = client.from<T>(table)
|
|
80
|
+
const { data: result, error } = await query.insert(data as Partial<T>)
|
|
81
|
+
|
|
82
|
+
if (error) {
|
|
83
|
+
throw error
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
},
|
|
88
|
+
onSuccess: () => {
|
|
89
|
+
// Invalidate all queries for this table
|
|
90
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Hook to update data in a table
|
|
97
|
+
*/
|
|
98
|
+
export function useUpdate<T = any>(table: string) {
|
|
99
|
+
const client = useFluxbaseClient()
|
|
100
|
+
const queryClient = useQueryClient()
|
|
101
|
+
|
|
102
|
+
return useMutation({
|
|
103
|
+
mutationFn: async (params: { data: Partial<T>; buildQuery: (query: QueryBuilder<T>) => QueryBuilder<T> }) => {
|
|
104
|
+
const query = client.from<T>(table)
|
|
105
|
+
const builtQuery = params.buildQuery(query)
|
|
106
|
+
const { data: result, error } = await builtQuery.update(params.data)
|
|
107
|
+
|
|
108
|
+
if (error) {
|
|
109
|
+
throw error
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return result
|
|
113
|
+
},
|
|
114
|
+
onSuccess: () => {
|
|
115
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Hook to upsert data into a table
|
|
122
|
+
*/
|
|
123
|
+
export function useUpsert<T = any>(table: string) {
|
|
124
|
+
const client = useFluxbaseClient()
|
|
125
|
+
const queryClient = useQueryClient()
|
|
126
|
+
|
|
127
|
+
return useMutation({
|
|
128
|
+
mutationFn: async (data: Partial<T> | Partial<T>[]) => {
|
|
129
|
+
const query = client.from<T>(table)
|
|
130
|
+
const { data: result, error } = await query.upsert(data as Partial<T>)
|
|
131
|
+
|
|
132
|
+
if (error) {
|
|
133
|
+
throw error
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result
|
|
137
|
+
},
|
|
138
|
+
onSuccess: () => {
|
|
139
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Hook to delete data from a table
|
|
146
|
+
*/
|
|
147
|
+
export function useDelete<T = any>(table: string) {
|
|
148
|
+
const client = useFluxbaseClient()
|
|
149
|
+
const queryClient = useQueryClient()
|
|
150
|
+
|
|
151
|
+
return useMutation({
|
|
152
|
+
mutationFn: async (buildQuery: (query: QueryBuilder<T>) => QueryBuilder<T>) => {
|
|
153
|
+
const query = client.from<T>(table)
|
|
154
|
+
const builtQuery = buildQuery(query)
|
|
155
|
+
const { error } = await builtQuery.delete()
|
|
156
|
+
|
|
157
|
+
if (error) {
|
|
158
|
+
throw error
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
onSuccess: () => {
|
|
162
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'table', table] })
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime subscription hooks for Fluxbase SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useEffect, useRef } from 'react'
|
|
6
|
+
import { useQueryClient } from '@tanstack/react-query'
|
|
7
|
+
import { useFluxbaseClient } from './context'
|
|
8
|
+
import type { RealtimeCallback, RealtimeChangePayload } from '@fluxbase/sdk'
|
|
9
|
+
|
|
10
|
+
export interface UseRealtimeOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The channel name (e.g., 'table:public.products')
|
|
13
|
+
*/
|
|
14
|
+
channel: string
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Event type to listen for ('INSERT', 'UPDATE', 'DELETE', or '*' for all)
|
|
18
|
+
*/
|
|
19
|
+
event?: 'INSERT' | 'UPDATE' | 'DELETE' | '*'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Callback function when an event is received
|
|
23
|
+
*/
|
|
24
|
+
callback?: RealtimeCallback
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether to automatically invalidate queries for the table
|
|
28
|
+
* Default: true
|
|
29
|
+
*/
|
|
30
|
+
autoInvalidate?: boolean
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Custom query key to invalidate (if autoInvalidate is true)
|
|
34
|
+
* Default: ['fluxbase', 'table', tableName]
|
|
35
|
+
*/
|
|
36
|
+
invalidateKey?: unknown[]
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether the subscription is enabled
|
|
40
|
+
* Default: true
|
|
41
|
+
*/
|
|
42
|
+
enabled?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hook to subscribe to realtime changes for a channel
|
|
47
|
+
*/
|
|
48
|
+
export function useRealtime(options: UseRealtimeOptions) {
|
|
49
|
+
const client = useFluxbaseClient()
|
|
50
|
+
const queryClient = useQueryClient()
|
|
51
|
+
const channelRef = useRef<ReturnType<typeof client.realtime.channel> | null>(null)
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
channel: channelName,
|
|
55
|
+
event = '*',
|
|
56
|
+
callback,
|
|
57
|
+
autoInvalidate = true,
|
|
58
|
+
invalidateKey,
|
|
59
|
+
enabled = true,
|
|
60
|
+
} = options
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!enabled) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create channel and subscribe
|
|
68
|
+
const channel = client.realtime.channel(channelName)
|
|
69
|
+
channelRef.current = channel
|
|
70
|
+
|
|
71
|
+
const handleChange = (payload: RealtimeChangePayload) => {
|
|
72
|
+
// Call user callback
|
|
73
|
+
if (callback) {
|
|
74
|
+
callback(payload)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Auto-invalidate queries if enabled
|
|
78
|
+
if (autoInvalidate) {
|
|
79
|
+
// Extract table name from channel (e.g., 'table:public.products' -> 'public.products')
|
|
80
|
+
const tableName = channelName.replace(/^table:/, '')
|
|
81
|
+
|
|
82
|
+
const key = invalidateKey || ['fluxbase', 'table', tableName]
|
|
83
|
+
queryClient.invalidateQueries({ queryKey: key })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
channel.on(event, handleChange).subscribe()
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
channel.unsubscribe()
|
|
91
|
+
channelRef.current = null
|
|
92
|
+
}
|
|
93
|
+
}, [client, channelName, event, callback, autoInvalidate, invalidateKey, queryClient, enabled])
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
channel: channelRef.current,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hook to subscribe to a table's changes
|
|
102
|
+
* @param table - Table name (with optional schema, e.g., 'public.products')
|
|
103
|
+
* @param options - Subscription options
|
|
104
|
+
*/
|
|
105
|
+
export function useTableSubscription(
|
|
106
|
+
table: string,
|
|
107
|
+
options?: Omit<UseRealtimeOptions, 'channel'>
|
|
108
|
+
) {
|
|
109
|
+
return useRealtime({
|
|
110
|
+
...options,
|
|
111
|
+
channel: `table:${table}`,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Hook to subscribe to INSERT events on a table
|
|
117
|
+
*/
|
|
118
|
+
export function useTableInserts(
|
|
119
|
+
table: string,
|
|
120
|
+
callback: (payload: RealtimeChangePayload) => void,
|
|
121
|
+
options?: Omit<UseRealtimeOptions, 'channel' | 'event' | 'callback'>
|
|
122
|
+
) {
|
|
123
|
+
return useRealtime({
|
|
124
|
+
...options,
|
|
125
|
+
channel: `table:${table}`,
|
|
126
|
+
event: 'INSERT',
|
|
127
|
+
callback,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Hook to subscribe to UPDATE events on a table
|
|
133
|
+
*/
|
|
134
|
+
export function useTableUpdates(
|
|
135
|
+
table: string,
|
|
136
|
+
callback: (payload: RealtimeChangePayload) => void,
|
|
137
|
+
options?: Omit<UseRealtimeOptions, 'channel' | 'event' | 'callback'>
|
|
138
|
+
) {
|
|
139
|
+
return useRealtime({
|
|
140
|
+
...options,
|
|
141
|
+
channel: `table:${table}`,
|
|
142
|
+
event: 'UPDATE',
|
|
143
|
+
callback,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Hook to subscribe to DELETE events on a table
|
|
149
|
+
*/
|
|
150
|
+
export function useTableDeletes(
|
|
151
|
+
table: string,
|
|
152
|
+
callback: (payload: RealtimeChangePayload) => void,
|
|
153
|
+
options?: Omit<UseRealtimeOptions, 'channel' | 'event' | 'callback'>
|
|
154
|
+
) {
|
|
155
|
+
return useRealtime({
|
|
156
|
+
...options,
|
|
157
|
+
channel: `table:${table}`,
|
|
158
|
+
event: 'DELETE',
|
|
159
|
+
callback,
|
|
160
|
+
})
|
|
161
|
+
}
|
package/src/use-rpc.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hooks for RPC (Remote Procedure Calls)
|
|
3
|
+
* Call PostgreSQL functions with React Query integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useQuery, useMutation, useQueryClient, type UseQueryOptions, type UseMutationOptions } from '@tanstack/react-query'
|
|
7
|
+
import { useFluxbaseClient } from './context'
|
|
8
|
+
import type { PostgrestResponse } from '@fluxbase/sdk'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook to call a PostgreSQL function and cache the result
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const { data, isLoading, error } = useRPC(
|
|
16
|
+
* 'calculate_total',
|
|
17
|
+
* { order_id: 123 },
|
|
18
|
+
* { enabled: !!orderId }
|
|
19
|
+
* )
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function useRPC<TData = unknown, TParams extends Record<string, unknown> = Record<string, unknown>>(
|
|
23
|
+
functionName: string,
|
|
24
|
+
params?: TParams,
|
|
25
|
+
options?: Omit<UseQueryOptions<TData, Error>, 'queryKey' | 'queryFn'>
|
|
26
|
+
) {
|
|
27
|
+
const client = useFluxbaseClient()
|
|
28
|
+
|
|
29
|
+
return useQuery<TData, Error>({
|
|
30
|
+
queryKey: ['rpc', functionName, params],
|
|
31
|
+
queryFn: async () => {
|
|
32
|
+
const { data, error } = await client.rpc<TData>(functionName, params)
|
|
33
|
+
if (error) {
|
|
34
|
+
throw new Error(error.message)
|
|
35
|
+
}
|
|
36
|
+
return data as TData
|
|
37
|
+
},
|
|
38
|
+
...options,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Hook to create a mutation for calling PostgreSQL functions
|
|
44
|
+
* Useful for functions that modify data
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* const createOrder = useRPCMutation('create_order')
|
|
49
|
+
*
|
|
50
|
+
* const handleSubmit = async () => {
|
|
51
|
+
* await createOrder.mutateAsync({
|
|
52
|
+
* user_id: 123,
|
|
53
|
+
* items: [{ product_id: 1, quantity: 2 }]
|
|
54
|
+
* })
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function useRPCMutation<TData = unknown, TParams extends Record<string, unknown> = Record<string, unknown>>(
|
|
59
|
+
functionName: string,
|
|
60
|
+
options?: Omit<UseMutationOptions<TData, Error, TParams>, 'mutationFn'>
|
|
61
|
+
) {
|
|
62
|
+
const client = useFluxbaseClient()
|
|
63
|
+
|
|
64
|
+
return useMutation<TData, Error, TParams>({
|
|
65
|
+
mutationFn: async (params: TParams) => {
|
|
66
|
+
const { data, error } = await client.rpc<TData>(functionName, params)
|
|
67
|
+
if (error) {
|
|
68
|
+
throw new Error(error.message)
|
|
69
|
+
}
|
|
70
|
+
return data as TData
|
|
71
|
+
},
|
|
72
|
+
...options,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Hook to call multiple RPC functions in parallel
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* const { data, isLoading } = useRPCBatch([
|
|
82
|
+
* { name: 'get_user_stats', params: { user_id: 123 } },
|
|
83
|
+
* { name: 'get_recent_orders', params: { limit: 10 } },
|
|
84
|
+
* ])
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function useRPCBatch<TData = unknown>(
|
|
88
|
+
calls: Array<{ name: string; params?: Record<string, unknown> }>,
|
|
89
|
+
options?: Omit<UseQueryOptions<TData[], Error, TData[], readonly unknown[]>, 'queryKey' | 'queryFn'>
|
|
90
|
+
) {
|
|
91
|
+
const client = useFluxbaseClient()
|
|
92
|
+
|
|
93
|
+
return useQuery({
|
|
94
|
+
queryKey: ['rpc-batch', calls] as const,
|
|
95
|
+
queryFn: async () => {
|
|
96
|
+
const results = await Promise.all(
|
|
97
|
+
calls.map(async ({ name, params }) => {
|
|
98
|
+
const { data, error } = await client.rpc<TData>(name, params)
|
|
99
|
+
if (error) {
|
|
100
|
+
throw new Error(`${name}: ${error.message}`)
|
|
101
|
+
}
|
|
102
|
+
return data
|
|
103
|
+
})
|
|
104
|
+
)
|
|
105
|
+
return results as TData[]
|
|
106
|
+
},
|
|
107
|
+
...options,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage hooks for Fluxbase SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMutation, useQuery, useQueryClient, type UseQueryOptions } from '@tanstack/react-query'
|
|
6
|
+
import { useFluxbaseClient } from './context'
|
|
7
|
+
import type { ListOptions, UploadOptions } from '@fluxbase/sdk'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to list files in a bucket
|
|
11
|
+
*/
|
|
12
|
+
export function useStorageList(
|
|
13
|
+
bucket: string,
|
|
14
|
+
options?: ListOptions & Omit<UseQueryOptions<any[], Error>, 'queryKey' | 'queryFn'>
|
|
15
|
+
) {
|
|
16
|
+
const client = useFluxbaseClient()
|
|
17
|
+
const { prefix, limit, offset, ...queryOptions } = options || {}
|
|
18
|
+
|
|
19
|
+
return useQuery({
|
|
20
|
+
queryKey: ['fluxbase', 'storage', bucket, 'list', { prefix, limit, offset }],
|
|
21
|
+
queryFn: async () => {
|
|
22
|
+
const { data, error } = await client.storage.from(bucket).list({ prefix, limit, offset })
|
|
23
|
+
|
|
24
|
+
if (error) {
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return data || []
|
|
29
|
+
},
|
|
30
|
+
...queryOptions,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook to upload a file to a bucket
|
|
36
|
+
*/
|
|
37
|
+
export function useStorageUpload(bucket: string) {
|
|
38
|
+
const client = useFluxbaseClient()
|
|
39
|
+
const queryClient = useQueryClient()
|
|
40
|
+
|
|
41
|
+
return useMutation({
|
|
42
|
+
mutationFn: async (params: {
|
|
43
|
+
path: string
|
|
44
|
+
file: File | Blob | ArrayBuffer
|
|
45
|
+
options?: UploadOptions
|
|
46
|
+
}) => {
|
|
47
|
+
const { path, file, options } = params
|
|
48
|
+
const { data, error } = await client.storage.from(bucket).upload(path, file, options)
|
|
49
|
+
|
|
50
|
+
if (error) {
|
|
51
|
+
throw error
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return data
|
|
55
|
+
},
|
|
56
|
+
onSuccess: () => {
|
|
57
|
+
// Invalidate list queries for this bucket
|
|
58
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Hook to download a file from a bucket
|
|
65
|
+
*/
|
|
66
|
+
export function useStorageDownload(bucket: string, path: string | null, enabled = true) {
|
|
67
|
+
const client = useFluxbaseClient()
|
|
68
|
+
|
|
69
|
+
return useQuery({
|
|
70
|
+
queryKey: ['fluxbase', 'storage', bucket, 'download', path],
|
|
71
|
+
queryFn: async () => {
|
|
72
|
+
if (!path) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { data, error } = await client.storage.from(bucket).download(path)
|
|
77
|
+
|
|
78
|
+
if (error) {
|
|
79
|
+
throw error
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return data
|
|
83
|
+
},
|
|
84
|
+
enabled: enabled && !!path,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Hook to delete files from a bucket
|
|
90
|
+
*/
|
|
91
|
+
export function useStorageDelete(bucket: string) {
|
|
92
|
+
const client = useFluxbaseClient()
|
|
93
|
+
const queryClient = useQueryClient()
|
|
94
|
+
|
|
95
|
+
return useMutation({
|
|
96
|
+
mutationFn: async (paths: string[]) => {
|
|
97
|
+
const { error } = await client.storage.from(bucket).remove(paths)
|
|
98
|
+
|
|
99
|
+
if (error) {
|
|
100
|
+
throw error
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
onSuccess: () => {
|
|
104
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hook to get a public URL for a file
|
|
111
|
+
*/
|
|
112
|
+
export function useStoragePublicUrl(bucket: string, path: string | null) {
|
|
113
|
+
const client = useFluxbaseClient()
|
|
114
|
+
|
|
115
|
+
if (!path) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { data } = client.storage.from(bucket).getPublicUrl(path)
|
|
120
|
+
return data.publicUrl
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Hook to create a signed URL
|
|
125
|
+
*/
|
|
126
|
+
export function useStorageSignedUrl(bucket: string, path: string | null, expiresIn?: number) {
|
|
127
|
+
const client = useFluxbaseClient()
|
|
128
|
+
|
|
129
|
+
return useQuery({
|
|
130
|
+
queryKey: ['fluxbase', 'storage', bucket, 'signed-url', path, expiresIn],
|
|
131
|
+
queryFn: async () => {
|
|
132
|
+
if (!path) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { data, error } = await client.storage.from(bucket).createSignedUrl(path, { expiresIn })
|
|
137
|
+
|
|
138
|
+
if (error) {
|
|
139
|
+
throw error
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return data?.signedUrl || null
|
|
143
|
+
},
|
|
144
|
+
enabled: !!path,
|
|
145
|
+
staleTime: expiresIn ? expiresIn * 1000 - 60000 : 1000 * 60 * 50, // Refresh 1 minute before expiry
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Hook to move a file
|
|
151
|
+
*/
|
|
152
|
+
export function useStorageMove(bucket: string) {
|
|
153
|
+
const client = useFluxbaseClient()
|
|
154
|
+
const queryClient = useQueryClient()
|
|
155
|
+
|
|
156
|
+
return useMutation({
|
|
157
|
+
mutationFn: async (params: { fromPath: string; toPath: string }) => {
|
|
158
|
+
const { fromPath, toPath } = params
|
|
159
|
+
const { data, error } = await client.storage.from(bucket).move(fromPath, toPath)
|
|
160
|
+
|
|
161
|
+
if (error) {
|
|
162
|
+
throw error
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return data
|
|
166
|
+
},
|
|
167
|
+
onSuccess: () => {
|
|
168
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Hook to copy a file
|
|
175
|
+
*/
|
|
176
|
+
export function useStorageCopy(bucket: string) {
|
|
177
|
+
const client = useFluxbaseClient()
|
|
178
|
+
const queryClient = useQueryClient()
|
|
179
|
+
|
|
180
|
+
return useMutation({
|
|
181
|
+
mutationFn: async (params: { fromPath: string; toPath: string }) => {
|
|
182
|
+
const { fromPath, toPath } = params
|
|
183
|
+
const { data, error } = await client.storage.from(bucket).copy(fromPath, toPath)
|
|
184
|
+
|
|
185
|
+
if (error) {
|
|
186
|
+
throw error
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return data
|
|
190
|
+
},
|
|
191
|
+
onSuccess: () => {
|
|
192
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Hook to manage buckets
|
|
199
|
+
*/
|
|
200
|
+
export function useStorageBuckets() {
|
|
201
|
+
const client = useFluxbaseClient()
|
|
202
|
+
|
|
203
|
+
return useQuery({
|
|
204
|
+
queryKey: ['fluxbase', 'storage', 'buckets'],
|
|
205
|
+
queryFn: async () => {
|
|
206
|
+
const { data, error } = await client.storage.listBuckets()
|
|
207
|
+
|
|
208
|
+
if (error) {
|
|
209
|
+
throw error
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return data || []
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Hook to create a bucket
|
|
219
|
+
*/
|
|
220
|
+
export function useCreateBucket() {
|
|
221
|
+
const client = useFluxbaseClient()
|
|
222
|
+
const queryClient = useQueryClient()
|
|
223
|
+
|
|
224
|
+
return useMutation({
|
|
225
|
+
mutationFn: async (bucketName: string) => {
|
|
226
|
+
const { error } = await client.storage.createBucket(bucketName)
|
|
227
|
+
|
|
228
|
+
if (error) {
|
|
229
|
+
throw error
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
onSuccess: () => {
|
|
233
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', 'buckets'] })
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Hook to delete a bucket
|
|
240
|
+
*/
|
|
241
|
+
export function useDeleteBucket() {
|
|
242
|
+
const client = useFluxbaseClient()
|
|
243
|
+
const queryClient = useQueryClient()
|
|
244
|
+
|
|
245
|
+
return useMutation({
|
|
246
|
+
mutationFn: async (bucketName: string) => {
|
|
247
|
+
const { error } = await client.storage.deleteBucket(bucketName)
|
|
248
|
+
|
|
249
|
+
if (error) {
|
|
250
|
+
throw error
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
onSuccess: () => {
|
|
254
|
+
queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', 'buckets'] })
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
}
|