@svadmin/hasura 0.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/package.json +20 -0
- package/src/data-provider.ts +161 -0
- package/src/index.ts +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@svadmin/hasura",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Hasura GraphQL DataProvider for svadmin",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": ["src/**/*"],
|
|
8
|
+
"main": "src/index.ts",
|
|
9
|
+
"types": "src/index.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@svadmin/core": "^0.3.0"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT"
|
|
20
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataProvider, GetListParams, GetListResult, GetOneParams, GetOneResult,
|
|
3
|
+
CreateParams, CreateResult, UpdateParams, UpdateResult, DeleteParams, DeleteResult,
|
|
4
|
+
GetManyParams, GetManyResult, CustomParams, CustomResult, Sort, Filter
|
|
5
|
+
} from '@svadmin/core';
|
|
6
|
+
|
|
7
|
+
// Maps core filter operators to Hasura GraphQL query operators
|
|
8
|
+
function mapOperator(operator: string): string {
|
|
9
|
+
switch (operator) {
|
|
10
|
+
case 'eq': return '_eq';
|
|
11
|
+
case 'ne': return '_neq';
|
|
12
|
+
case 'lt': return '_lt';
|
|
13
|
+
case 'gt': return '_gt';
|
|
14
|
+
case 'lte': return '_lte';
|
|
15
|
+
case 'gte': return '_gte';
|
|
16
|
+
case 'contains': return '_ilike';
|
|
17
|
+
case 'in': return '_in';
|
|
18
|
+
case 'null': return '_is_null';
|
|
19
|
+
default: return '_eq';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Minimal GraphQL Executor
|
|
24
|
+
async function executeGraphQL(endpoint: string, query: string, variables: any = {}, headers: any = {}) {
|
|
25
|
+
const response = await fetch(endpoint, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
28
|
+
body: JSON.stringify({ query, variables }),
|
|
29
|
+
});
|
|
30
|
+
const json = await response.json();
|
|
31
|
+
if (json.errors) {
|
|
32
|
+
throw new Error(json.errors.map((e: any) => e.message).join('\n'));
|
|
33
|
+
}
|
|
34
|
+
return json.data;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createHasuraDataProvider(endpoint: string, defaultHeaders: Record<string, string> = {}): DataProvider {
|
|
38
|
+
return {
|
|
39
|
+
getApiUrl: () => endpoint,
|
|
40
|
+
|
|
41
|
+
async getList<T>({ resource, pagination, sorters, filters, meta }: GetListParams): Promise<GetListResult<T>> {
|
|
42
|
+
const { current = 1, pageSize = 10, mode } = pagination ?? {};
|
|
43
|
+
const offset = mode === 'server' ? (current - 1) * pageSize : 0;
|
|
44
|
+
const limit = mode === 'server' ? pageSize : undefined;
|
|
45
|
+
|
|
46
|
+
// Simplistic AST builder for demonstration. In production, 'graphql-request' or gql AST tools should be used.
|
|
47
|
+
const variables: any = { limit, offset };
|
|
48
|
+
|
|
49
|
+
let whereClause = '';
|
|
50
|
+
if (filters && filters.length > 0) {
|
|
51
|
+
const clauses = filters.filter((f: any) => 'field' in f).map((f: any, i: number) => {
|
|
52
|
+
variables[`f${i}`] = f.operator === 'contains' ? `%${f.value}%` : f.value;
|
|
53
|
+
return `${f.field}: { ${mapOperator(f.operator)}: $f${i} }`;
|
|
54
|
+
});
|
|
55
|
+
whereClause = clauses.length ? `where: { ${clauses.join(', ')} },` : '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let orderByClause = '';
|
|
59
|
+
if (sorters && sorters.length > 0) {
|
|
60
|
+
const orders = sorters.map((s: Sort) => `${s.field}: ${s.order}`);
|
|
61
|
+
orderByClause = `order_by: { ${orders.join(', ')} },`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fields = (meta?.fields as string[])?.join(' ') || 'id';
|
|
65
|
+
|
|
66
|
+
const query = `
|
|
67
|
+
query getList($limit: Int, $offset: Int, ${Object.keys(variables).filter(k => k.startsWith('f')).map(k => `$${k}: String`).join(', ')}) {
|
|
68
|
+
${resource}(limit: $limit, offset: $offset, ${whereClause} ${orderByClause}) {
|
|
69
|
+
${fields}
|
|
70
|
+
}
|
|
71
|
+
${resource}_aggregate {
|
|
72
|
+
aggregate { count }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const data = await executeGraphQL(endpoint, query, variables, defaultHeaders);
|
|
78
|
+
return {
|
|
79
|
+
data: data[resource] || [],
|
|
80
|
+
total: data[`${resource}_aggregate`]?.aggregate?.count || 0,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async getOne<T>({ resource, id, meta }: GetOneParams): Promise<GetOneResult<T>> {
|
|
85
|
+
const fields = (meta?.fields as string[])?.join(' ') || 'id';
|
|
86
|
+
const query = `
|
|
87
|
+
query getOne($id: uuid!) {
|
|
88
|
+
${resource}_by_pk(id: $id) {
|
|
89
|
+
${fields}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
const data = await executeGraphQL(endpoint, query, { id }, defaultHeaders);
|
|
94
|
+
return { data: data[`${resource}_by_pk`] as T };
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async create<T>({ resource, variables, meta }: CreateParams): Promise<CreateResult<T>> {
|
|
98
|
+
const fields = (meta?.fields as string[])?.join(' ') || 'id';
|
|
99
|
+
const query = `
|
|
100
|
+
mutation create($object: ${resource}_insert_input!) {
|
|
101
|
+
insert_${resource}_one(object: $object) {
|
|
102
|
+
${fields}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
const data = await executeGraphQL(endpoint, query, { object: variables }, defaultHeaders);
|
|
107
|
+
return { data: data[`insert_${resource}_one`] as T };
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async update<T>({ resource, id, variables, meta }: UpdateParams): Promise<UpdateResult<T>> {
|
|
111
|
+
const fields = (meta?.fields as string[])?.join(' ') || 'id';
|
|
112
|
+
const query = `
|
|
113
|
+
mutation update($id: uuid!, $set: ${resource}_set_input!) {
|
|
114
|
+
update_${resource}_by_pk(pk_columns: {id: $id}, _set: $set) {
|
|
115
|
+
${fields}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
const data = await executeGraphQL(endpoint, query, { id, set: variables }, defaultHeaders);
|
|
120
|
+
return { data: data[`update_${resource}_by_pk`] as T };
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
async deleteOne<T>({ resource, id, meta }: DeleteParams): Promise<DeleteResult<T>> {
|
|
124
|
+
const fields = (meta?.fields as string[])?.join(' ') || 'id';
|
|
125
|
+
const query = `
|
|
126
|
+
mutation deleteOne($id: uuid!) {
|
|
127
|
+
delete_${resource}_by_pk(id: $id) {
|
|
128
|
+
${fields}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
const data = await executeGraphQL(endpoint, query, { id }, defaultHeaders);
|
|
133
|
+
return { data: data[`delete_${resource}_by_pk`] as T };
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async getMany<T>({ resource, ids, meta }: GetManyParams): Promise<GetManyResult<T>> {
|
|
137
|
+
const fields = (meta?.fields as string[])?.join(' ') || 'id';
|
|
138
|
+
const query = `
|
|
139
|
+
query getMany($ids: [uuid!]!) {
|
|
140
|
+
${resource}(where: {id: {_in: $ids}}) {
|
|
141
|
+
${fields}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
const data = await executeGraphQL(endpoint, query, { ids }, defaultHeaders);
|
|
146
|
+
return { data: data[resource] || [] };
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
async custom<T>({ url, method, payload, headers }: CustomParams): Promise<CustomResult<T>> {
|
|
150
|
+
// By default Hasura only understands GraphQL via POST, but arbitrary custom calls might be used for REST endpoints mapping.
|
|
151
|
+
const response = await fetch(url, {
|
|
152
|
+
method: method.toUpperCase(),
|
|
153
|
+
headers: { 'Content-Type': 'application/json', ...defaultHeaders, ...headers },
|
|
154
|
+
body: payload ? JSON.stringify(payload) : undefined,
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) throw new Error(`Custom request failed: ${response.status}`);
|
|
157
|
+
const data = await response.json();
|
|
158
|
+
return { data: data as T };
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createHasuraDataProvider } from './data-provider';
|