@svadmin/supabase 0.11.8 → 0.12.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/package.json +8 -8
- package/src/data-provider.ts +192 -9
- package/src/index.ts +6 -0
- package/src/rpc.ts +69 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/supabase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Supabase adapters — DataProvider, AuthProvider, LiveProvider, AuditHandler",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@svadmin/core": "^0.
|
|
28
|
-
"@supabase/supabase-js": "^2.112.
|
|
27
|
+
"@svadmin/core": "^0.36.2",
|
|
28
|
+
"@supabase/supabase-js": "^2.112.3",
|
|
29
29
|
"@refinedev/supabase": "^6.0.2",
|
|
30
30
|
"@supacloud/js": "^0.21.1"
|
|
31
31
|
},
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"author": "zuohuadong",
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|
|
36
|
-
"url": "https://github.com/
|
|
36
|
+
"url": "https://github.com/vibeunion/svadmin.git",
|
|
37
37
|
"directory": "packages/supabase"
|
|
38
38
|
},
|
|
39
|
-
"homepage": "https://github.com/
|
|
39
|
+
"homepage": "https://github.com/vibeunion/svadmin#readme",
|
|
40
40
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/vibeunion/svadmin/issues"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@svadmin/core": {
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@svadmin/refine-adapter": "^0.10.
|
|
58
|
+
"@svadmin/refine-adapter": "^0.10.2"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@supabase/supabase-js": "^2.112.
|
|
61
|
+
"@supabase/supabase-js": "^2.112.3"
|
|
62
62
|
}
|
|
63
63
|
}
|
package/src/data-provider.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import type { DataProvider } from '@svadmin/core';
|
|
1
|
+
import type { CustomParams, CustomResult, DataProvider } from '@svadmin/core';
|
|
2
2
|
import { createRefineAdapter } from '@svadmin/refine-adapter';
|
|
3
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
4
|
// Static import — dynamic import() gets dropped by Vite/Rollup in static SPA builds
|
|
4
5
|
import { dataProvider as refineDataProvider } from '@refinedev/supabase';
|
|
5
6
|
|
|
7
|
+
export interface CreateSupabaseDataProviderOptions {
|
|
8
|
+
/** Default schema for table and RPC operations */
|
|
9
|
+
schema?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
/**
|
|
7
|
-
* Creates a supabase data provider using the official @refinedev/supabase package
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @param
|
|
11
|
-
* @
|
|
13
|
+
* Creates a supabase data provider using the official @refinedev/supabase package
|
|
14
|
+
* with full support for custom RPC calls, Edge Functions, and schema targeting.
|
|
15
|
+
*
|
|
16
|
+
* @param client SupabaseClient instance
|
|
17
|
+
* @param options Optional provider configuration
|
|
18
|
+
* @returns A fully compatible svadmin DataProvider with custom RPC support
|
|
12
19
|
*/
|
|
13
20
|
export function createSupabaseDataProvider(
|
|
14
|
-
|
|
21
|
+
client: SupabaseClient,
|
|
22
|
+
options: CreateSupabaseDataProviderOptions = {}
|
|
15
23
|
): DataProvider {
|
|
16
24
|
if (typeof refineDataProvider !== 'function') {
|
|
17
25
|
throw new Error(
|
|
@@ -19,6 +27,181 @@ export function createSupabaseDataProvider(
|
|
|
19
27
|
'Ensure the package is installed correctly: bun add @refinedev/supabase'
|
|
20
28
|
);
|
|
21
29
|
}
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const refineProvider = refineDataProvider(client as any);
|
|
33
|
+
const adapter = createRefineAdapter(refineProvider);
|
|
34
|
+
|
|
35
|
+
// Provide first-class custom implementation for Supabase
|
|
36
|
+
adapter.custom = async <TData = unknown, TVariables = unknown>({
|
|
37
|
+
url,
|
|
38
|
+
method = 'get',
|
|
39
|
+
payload,
|
|
40
|
+
query,
|
|
41
|
+
headers,
|
|
42
|
+
filters,
|
|
43
|
+
sorters,
|
|
44
|
+
meta,
|
|
45
|
+
}: CustomParams<TVariables>): Promise<CustomResult<TData>> => {
|
|
46
|
+
const targetSchema = (meta?.schema as string | undefined) ?? options.schema;
|
|
47
|
+
const targetClient = targetSchema ? client.schema(targetSchema) : client;
|
|
48
|
+
|
|
49
|
+
// 1. Edge Function invocation
|
|
50
|
+
const isEdgeFunction =
|
|
51
|
+
meta?.function === true ||
|
|
52
|
+
meta?.edgeFunction === true ||
|
|
53
|
+
url.startsWith('functions/') ||
|
|
54
|
+
url.startsWith('functions:');
|
|
55
|
+
|
|
56
|
+
if (isEdgeFunction) {
|
|
57
|
+
const functionName = url.replace(/^functions[/:]/, '');
|
|
58
|
+
const { data, error } = await client.functions.invoke(functionName, {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
body: payload as any,
|
|
61
|
+
headers: headers as Record<string, string>,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (error) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[svadmin/supabase] Edge Function "${functionName}" failed: ${error.message}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { data: data as TData };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. PostgreSQL Stored Procedure / RPC
|
|
74
|
+
const isExplicitRpc =
|
|
75
|
+
meta?.rpc === true ||
|
|
76
|
+
url.startsWith('rpc/') ||
|
|
77
|
+
url.startsWith('rpc:');
|
|
78
|
+
const isImplicitRpc =
|
|
79
|
+
!isExplicitRpc &&
|
|
80
|
+
meta?.rpc !== false &&
|
|
81
|
+
!url.startsWith('http://') &&
|
|
82
|
+
!url.startsWith('https://') &&
|
|
83
|
+
!url.includes('/') &&
|
|
84
|
+
(method === 'post' || meta?.rpc === true);
|
|
85
|
+
|
|
86
|
+
if (isExplicitRpc || (isImplicitRpc && meta?.rpc === true)) {
|
|
87
|
+
const functionName = url.replace(/^rpc[/:]/, '');
|
|
88
|
+
const rpcArgs = (payload ?? query ?? {}) as Record<string, unknown>;
|
|
89
|
+
const rpcOptions: { head?: boolean; count?: 'exact' | 'planned' | 'estimated'; get?: boolean } = {};
|
|
90
|
+
if (meta?.head) rpcOptions.head = Boolean(meta.head);
|
|
91
|
+
if (meta?.count) rpcOptions.count = meta.count as 'exact' | 'planned' | 'estimated';
|
|
92
|
+
if (meta?.get) rpcOptions.get = Boolean(meta.get);
|
|
93
|
+
|
|
94
|
+
const { data, error } = await targetClient.rpc(functionName, rpcArgs, rpcOptions);
|
|
95
|
+
|
|
96
|
+
if (error) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`[svadmin/supabase] RPC function "${functionName}" failed: ${error.message}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { data: data as TData };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Direct Table / View Query via PostgREST client
|
|
106
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
107
|
+
const dbQuery = targetClient.from(url);
|
|
108
|
+
|
|
109
|
+
if (method === 'get') {
|
|
110
|
+
let selectQuery = dbQuery.select((meta?.select as string) ?? '*');
|
|
111
|
+
if (query) {
|
|
112
|
+
for (const [key, value] of Object.entries(query)) {
|
|
113
|
+
if (value !== undefined && value !== null) {
|
|
114
|
+
selectQuery = selectQuery.eq(key, value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (filters && Array.isArray(filters)) {
|
|
119
|
+
for (const filter of filters) {
|
|
120
|
+
if ('field' in filter && 'value' in filter) {
|
|
121
|
+
selectQuery = selectQuery.eq(filter.field, filter.value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (sorters && Array.isArray(sorters)) {
|
|
126
|
+
for (const sorter of sorters) {
|
|
127
|
+
selectQuery = selectQuery.order(sorter.field, {
|
|
128
|
+
ascending: sorter.order === 'asc',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const { data, error } = await selectQuery;
|
|
133
|
+
if (error) {
|
|
134
|
+
throw new Error(`[svadmin/supabase] Query "${url}" failed: ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
return { data: data as TData };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (method === 'post') {
|
|
140
|
+
const { data, error } = await dbQuery
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
142
|
+
.insert(payload as any)
|
|
143
|
+
.select((meta?.select as string) ?? '*');
|
|
144
|
+
if (error) {
|
|
145
|
+
throw new Error(`[svadmin/supabase] Insert into "${url}" failed: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
return { data: (Array.isArray(data) ? data[0] : data) as TData };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (method === 'put' || method === 'patch') {
|
|
151
|
+
let updateQuery = dbQuery
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
.update(payload as any);
|
|
154
|
+
if (query) {
|
|
155
|
+
for (const [key, value] of Object.entries(query)) {
|
|
156
|
+
if (value !== undefined && value !== null) {
|
|
157
|
+
updateQuery = updateQuery.eq(key, value);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const { data, error } = await updateQuery.select((meta?.select as string) ?? '*');
|
|
162
|
+
if (error) {
|
|
163
|
+
throw new Error(`[svadmin/supabase] Update "${url}" failed: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
return { data: (Array.isArray(data) ? data[0] : data) as TData };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (method === 'delete') {
|
|
169
|
+
let deleteQuery = dbQuery.delete();
|
|
170
|
+
if (query) {
|
|
171
|
+
for (const [key, value] of Object.entries(query)) {
|
|
172
|
+
if (value !== undefined && value !== null) {
|
|
173
|
+
deleteQuery = deleteQuery.eq(key, value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const { data, error } = await deleteQuery.select((meta?.select as string) ?? '*');
|
|
178
|
+
if (error) {
|
|
179
|
+
throw new Error(`[svadmin/supabase] Delete from "${url}" failed: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
return { data: data as TData };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 4. Fallback to standard HTTP fetch for absolute URLs
|
|
186
|
+
const fetchHeaders: Record<string, string> = {
|
|
187
|
+
'Content-Type': 'application/json',
|
|
188
|
+
...(headers ?? {}),
|
|
189
|
+
};
|
|
190
|
+
const response = await fetch(url, {
|
|
191
|
+
method: method.toUpperCase(),
|
|
192
|
+
headers: fetchHeaders,
|
|
193
|
+
body: payload ? JSON.stringify(payload) : undefined,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`[svadmin/supabase] Custom request to "${url}" failed with status ${response.status}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const data = await response.json();
|
|
203
|
+
return { data: data as TData };
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return adapter;
|
|
24
207
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @svadmin/supabase — Supabase adapters
|
|
2
2
|
|
|
3
3
|
export { createSupabaseDataProvider } from './data-provider';
|
|
4
|
+
export type { CreateSupabaseDataProviderOptions } from './data-provider';
|
|
4
5
|
export { createSupabaseAuthProvider } from './auth-provider';
|
|
5
6
|
export type {
|
|
6
7
|
SupabaseAuthProviderOptions,
|
|
@@ -9,6 +10,11 @@ export type {
|
|
|
9
10
|
} from './auth-provider';
|
|
10
11
|
export { createSupabaseLiveProvider } from './live-provider';
|
|
11
12
|
export { createSupabaseAuditHandler } from './audit-handler';
|
|
13
|
+
export { createSupabaseRpc } from './rpc';
|
|
14
|
+
export type {
|
|
15
|
+
SupabaseRpcClient,
|
|
16
|
+
SupabaseRpcOptions,
|
|
17
|
+
} from './rpc';
|
|
12
18
|
export {
|
|
13
19
|
createSupaCloudTaskProvider,
|
|
14
20
|
createSupaCloudTaskLiveProvider,
|
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Supabase RPC Helper — Type-safe and Ergonomic PostgreSQL Function Execution
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
|
|
4
|
+
export interface SupabaseRpcOptions {
|
|
5
|
+
/** Database schema where the RPC function resides (defaults to client schema or 'public') */
|
|
6
|
+
schema?: string;
|
|
7
|
+
/** When true, do not return result rows (sets Prefer: return=minimal) */
|
|
8
|
+
head?: boolean;
|
|
9
|
+
/** Count algorithm to use */
|
|
10
|
+
count?: 'exact' | 'planned' | 'estimated';
|
|
11
|
+
/** Use GET HTTP method for read-only RPCs (enables HTTP caching) */
|
|
12
|
+
get?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SupabaseRpcClient {
|
|
16
|
+
/**
|
|
17
|
+
* Invoke a Postgres stored procedure / function via Supabase RPC.
|
|
18
|
+
*
|
|
19
|
+
* @param functionName Name of the PostgreSQL stored procedure
|
|
20
|
+
* @param args Arguments to pass to the procedure
|
|
21
|
+
* @param options Additional Supabase RPC options (schema, head, count, get)
|
|
22
|
+
* @returns The returned data from the RPC call
|
|
23
|
+
*/
|
|
24
|
+
call: <TResult = unknown, TArgs extends Record<string, unknown> = Record<string, unknown>>(
|
|
25
|
+
functionName: string,
|
|
26
|
+
args?: TArgs,
|
|
27
|
+
options?: SupabaseRpcOptions
|
|
28
|
+
) => Promise<TResult>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a dedicated Supabase RPC helper for executing PostgreSQL stored procedures.
|
|
33
|
+
*
|
|
34
|
+
* @param client SupabaseClient instance
|
|
35
|
+
* @param defaultOptions Default options applied to all RPC calls
|
|
36
|
+
*/
|
|
37
|
+
export function createSupabaseRpc(
|
|
38
|
+
client: SupabaseClient,
|
|
39
|
+
defaultOptions: SupabaseRpcOptions = {}
|
|
40
|
+
): SupabaseRpcClient {
|
|
41
|
+
return {
|
|
42
|
+
async call<TResult = unknown, TArgs extends Record<string, unknown> = Record<string, unknown>>(
|
|
43
|
+
functionName: string,
|
|
44
|
+
args?: TArgs,
|
|
45
|
+
options: SupabaseRpcOptions = {}
|
|
46
|
+
): Promise<TResult> {
|
|
47
|
+
const mergedOptions = { ...defaultOptions, ...options };
|
|
48
|
+
const targetClient = mergedOptions.schema ? client.schema(mergedOptions.schema) : client;
|
|
49
|
+
|
|
50
|
+
const { data, error } = await targetClient.rpc(
|
|
51
|
+
functionName,
|
|
52
|
+
args ?? ({} as TArgs),
|
|
53
|
+
{
|
|
54
|
+
head: mergedOptions.head,
|
|
55
|
+
count: mergedOptions.count,
|
|
56
|
+
get: mergedOptions.get,
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (error) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`[svadmin/supabase] RPC function "${functionName}" failed: ${error.message}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return data as TResult;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|