nextjs-hasura-auth 0.1.1 → 0.1.3

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.
@@ -0,0 +1,6 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import http from 'http';
3
+ import { WebSocket, WebSocketServer } from 'ws';
4
+ export declare function proxyGET(request: NextRequest): Promise<NextResponse>;
5
+ export declare function proxyPOST(request: NextRequest): Promise<NextResponse>;
6
+ export declare function proxySOCKET(client: WebSocket, request: http.IncomingMessage, server: WebSocketServer): Promise<void>;
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.proxyGET = proxyGET;
49
+ exports.proxyPOST = proxyPOST;
50
+ exports.proxySOCKET = proxySOCKET;
51
+ const server_1 = require("next/server");
52
+ const ws_1 = __importStar(require("ws"));
53
+ const jwt_1 = require("next-auth/jwt");
54
+ const debug_1 = __importDefault(require("@/lib/debug"));
55
+ const jwt_2 = require("@/lib/jwt");
56
+ const debugGraphql = (0, debug_1.default)('graphql:proxy');
57
+ let HASURA_ENDPOINT;
58
+ let HASURA_WS_ENDPOINT;
59
+ let HASURA_ADMIN_SECRET;
60
+ let NEXTAUTH_SECRET;
61
+ if (typeof window === 'undefined') {
62
+ // --- Environment Variables --- (Moved here, consider centralizing further)
63
+ HASURA_ENDPOINT = process.env.NEXT_PUBLIC_HASURA_GRAPHQL_URL;
64
+ HASURA_WS_ENDPOINT = HASURA_ENDPOINT === null || HASURA_ENDPOINT === void 0 ? void 0 : HASURA_ENDPOINT.replace('https', 'wss').replace('http', 'ws');
65
+ HASURA_ADMIN_SECRET = process.env.HASURA_ADMIN_SECRET;
66
+ NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET;
67
+ // --- Basic Checks --- (Moved here)
68
+ if (!HASURA_ENDPOINT) {
69
+ console.error("❌ CRITICAL: NEXT_PUBLIC_HASURA_GRAPHQL_URL environment variable is not set.");
70
+ debugGraphql("❌ CRITICAL: NEXT_PUBLIC_HASURA_GRAPHQL_URL environment variable is not set.");
71
+ }
72
+ if (!HASURA_WS_ENDPOINT) {
73
+ console.error("❌ CRITICAL: Cannot derive WebSocket endpoint from NEXT_PUBLIC_HASURA_GRAPHQL_URL.");
74
+ debugGraphql("❌ CRITICAL: Cannot derive WebSocket endpoint from NEXT_PUBLIC_HASURA_GRAPHQL_URL.");
75
+ }
76
+ if (!HASURA_ADMIN_SECRET) {
77
+ // Allow Admin Secret to be optional for WS if only JWT is used, but log warning
78
+ console.warn("⚠️ WARNING: HASURA_ADMIN_SECRET environment variable is not set. Anonymous WS access will fail.");
79
+ debugGraphql("⚠️ WARNING: HASURA_ADMIN_SECRET environment variable is not set. Anonymous WS access will fail.");
80
+ }
81
+ if (!NEXTAUTH_SECRET) {
82
+ console.error("❌ CRITICAL: NEXTAUTH_SECRET environment variable is not set.");
83
+ debugGraphql("❌ CRITICAL: NEXTAUTH_SECRET environment variable is not set.");
84
+ }
85
+ }
86
+ // =======================================================================
87
+ // GET Handler Logic
88
+ // =======================================================================
89
+ function proxyGET(request) {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ debugGraphql('Executing proxyGET');
92
+ return server_1.NextResponse.json({
93
+ status: 'ok',
94
+ message: 'GraphQL API and WebSocket proxy active',
95
+ endpoints: {
96
+ http: '/api/graphql',
97
+ ws: '/api/graphql' // WebSocket uses the same endpoint via upgrade
98
+ },
99
+ hasura_endpoint: HASURA_ENDPOINT,
100
+ });
101
+ });
102
+ }
103
+ // =======================================================================
104
+ // POST Handler Logic
105
+ // =======================================================================
106
+ function proxyPOST(request) {
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ debugGraphql('--- proxyPOST Start ---');
109
+ if (!HASURA_ENDPOINT) {
110
+ const errorMsg = 'Hasura HTTP endpoint is not configured on the server.';
111
+ console.error(`❌ ${errorMsg}`);
112
+ debugGraphql(`❌ ${errorMsg}`);
113
+ return server_1.NextResponse.json({ errors: [{ message: errorMsg }] }, { status: 500 });
114
+ }
115
+ try {
116
+ const body = yield request.json();
117
+ const queryStr = JSON.stringify(body).substring(0, 200);
118
+ debugGraphql(`📤 GraphQL Query Received (preview): ${queryStr}${queryStr.length >= 200 ? '...' : ''}`);
119
+ const headers = {
120
+ 'Content-Type': 'application/json',
121
+ };
122
+ if (!HASURA_ADMIN_SECRET) {
123
+ const errorMsg = 'HASURA_ADMIN_SECRET is not configured on the server for HTTP proxy.';
124
+ console.error(`❌ ${errorMsg}`);
125
+ debugGraphql(`❌ ${errorMsg}`);
126
+ // Important: Do not proceed if admin secret is missing for POST
127
+ return server_1.NextResponse.json({ errors: [{ message: errorMsg }] }, { status: 500 });
128
+ }
129
+ headers['x-hasura-admin-secret'] = HASURA_ADMIN_SECRET;
130
+ debugGraphql('🔑 Using Hasura Admin Secret for downstream HTTP request.');
131
+ debugGraphql(`🔗 Sending request to Hasura HTTP: ${HASURA_ENDPOINT}`);
132
+ const hasuraResponse = yield fetch(HASURA_ENDPOINT, {
133
+ method: 'POST',
134
+ headers,
135
+ body: JSON.stringify(body),
136
+ });
137
+ const data = yield hasuraResponse.json();
138
+ if (data.errors) {
139
+ console.error('❌ Error response from Hasura:', JSON.stringify(data.errors));
140
+ debugGraphql('❌ Error response from Hasura:', JSON.stringify(data.errors));
141
+ }
142
+ else {
143
+ debugGraphql('✅ Successful response from Hasura HTTP.');
144
+ }
145
+ debugGraphql('--- proxyPOST End ---');
146
+ return server_1.NextResponse.json(data, { status: hasuraResponse.status });
147
+ }
148
+ catch (error) {
149
+ console.error('❌ Error proxying HTTP GraphQL request:', error.stack || error);
150
+ debugGraphql('❌ Error proxying HTTP GraphQL request:', error.message);
151
+ debugGraphql('--- proxyPOST End (Error) ---');
152
+ return server_1.NextResponse.json({
153
+ errors: [
154
+ {
155
+ message: 'Error processing GraphQL request via proxy.',
156
+ extensions: {
157
+ code: 'INTERNAL_SERVER_ERROR',
158
+ path: 'graphql-proxy-http'
159
+ }
160
+ }
161
+ ]
162
+ }, { status: 500 });
163
+ }
164
+ });
165
+ }
166
+ // =======================================================================
167
+ // SOCKET Handler Logic
168
+ // =======================================================================
169
+ function proxySOCKET(client, request, server) {
170
+ return __awaiter(this, void 0, void 0, function* () {
171
+ const clientId = Math.random().toString(36).substring(2, 8); // Shorter ID
172
+ debugGraphql(`--- proxySOCKET [${clientId}] Start ---`);
173
+ if (!HASURA_WS_ENDPOINT) {
174
+ console.error(`❌ [${clientId}] Hasura WebSocket endpoint not configured.`);
175
+ debugGraphql(`❌ [${clientId}] Hasura WebSocket endpoint not configured.`);
176
+ client.close(1011, 'WebSocket endpoint not configured');
177
+ return;
178
+ }
179
+ if (!NEXTAUTH_SECRET) {
180
+ console.error(`❌ [${clientId}] NEXTAUTH_SECRET not configured.`);
181
+ debugGraphql(`❌ [${clientId}] NEXTAUTH_SECRET not configured.`);
182
+ client.close(1011, 'Server authentication secret not configured');
183
+ return;
184
+ }
185
+ let hasuraWs = null;
186
+ let clientConnectionInitialized = false;
187
+ let hasuraConnectionInitialized = false;
188
+ const closeConnections = (code = 1000, reason = 'Closing connection') => {
189
+ debugGraphql(`[${clientId}] Closing connections: Code=${code}, Reason=${reason}`);
190
+ if (client.readyState === ws_1.WebSocket.OPEN || client.readyState === ws_1.WebSocket.CONNECTING) {
191
+ client.close(code, reason);
192
+ }
193
+ if (hasuraWs && (hasuraWs.readyState === ws_1.WebSocket.OPEN || hasuraWs.readyState === ws_1.WebSocket.CONNECTING)) {
194
+ hasuraWs.close(code, reason);
195
+ }
196
+ debugGraphql(`[${clientId}] Connections closed.`);
197
+ };
198
+ try {
199
+ const token = yield (0, jwt_1.getToken)({
200
+ req: request,
201
+ secret: NEXTAUTH_SECRET
202
+ });
203
+ const headers = {};
204
+ if (token === null || token === void 0 ? void 0 : token.sub) {
205
+ debugGraphql(`👤 [${clientId}] User authenticated (ID: ${token.sub}). Generating Hasura JWT.`);
206
+ try {
207
+ const hasuraClaims = {
208
+ 'x-hasura-allowed-roles': ['user', 'anonymous', 'me'], // Keep fixed roles for simplicity in proxy
209
+ 'x-hasura-default-role': 'user',
210
+ 'x-hasura-user-id': token.sub,
211
+ };
212
+ const jwt = yield (0, jwt_2.generateJWT)(token.sub, hasuraClaims); // Assumes generateJWT uses env secret
213
+ headers['Authorization'] = `Bearer ${jwt}`;
214
+ debugGraphql(`🔑 [${clientId}] Using generated JWT (user role) for Hasura WS connection.`);
215
+ }
216
+ catch (jwtError) {
217
+ console.error(`❌ [${clientId}] Failed to generate Hasura JWT for user:`, jwtError);
218
+ debugGraphql(`❌ [${clientId}] Failed to generate Hasura JWT for user:`, jwtError.message);
219
+ closeConnections(1011, "JWT generation failed");
220
+ return;
221
+ }
222
+ }
223
+ else {
224
+ // --- MODIFICATION START: Generate Anonymous JWT instead of using Admin Secret ---
225
+ debugGraphql(`👤 [${clientId}] User not authenticated. Generating Anonymous JWT.`);
226
+ try {
227
+ const anonymousUserId = `anon-${clientId}`; // Create a unique-ish ID for anonymous user
228
+ const hasuraClaims = {
229
+ 'x-hasura-allowed-roles': ['anonymous'], // Only allow anonymous role
230
+ 'x-hasura-default-role': 'anonymous',
231
+ 'x-hasura-user-id': anonymousUserId, // Provide an ID
232
+ };
233
+ // Use the same secret mechanism as for authenticated users
234
+ const jwt = yield (0, jwt_2.generateJWT)(anonymousUserId, hasuraClaims); // Assumes generateJWT uses env secret
235
+ headers['Authorization'] = `Bearer ${jwt}`;
236
+ debugGraphql(`🔑 [${clientId}] Using generated JWT (anonymous role) for Hasura WS connection.`);
237
+ }
238
+ catch (jwtError) {
239
+ console.error(`❌ [${clientId}] Failed to generate Hasura JWT for anonymous:`, jwtError);
240
+ debugGraphql(`❌ [${clientId}] Failed to generate Hasura JWT for anonymous:`, jwtError.message);
241
+ // Fallback or error closing might depend on requirements, here we close.
242
+ closeConnections(1011, "Anonymous JWT generation failed");
243
+ return;
244
+ }
245
+ // --- MODIFICATION END ---
246
+ /* --- OLD CODE using Admin Secret ---
247
+ } else if (HASURA_ADMIN_SECRET) {
248
+ debugGraphql(`👤 [${clientId}] User not authenticated. Using Admin Secret for Hasura WS connection.`);
249
+ headers['x-hasura-admin-secret'] = HASURA_ADMIN_SECRET;
250
+ } else {
251
+ debugGraphql(`❌ [${clientId}] Anonymous connection attempted, but no Admin Secret configured.`);
252
+ console.error(`❌ [${clientId}] Anonymous connection attempted, but no Admin Secret configured.`);
253
+ closeConnections(1011, 'Server configuration error for anonymous access.');
254
+ return;
255
+ }
256
+ */
257
+ }
258
+ debugGraphql(`🔗 [${clientId}] Establishing connection to Hasura WS: ${HASURA_WS_ENDPOINT}`);
259
+ hasuraWs = new ws_1.default(HASURA_WS_ENDPOINT, 'graphql-ws', { headers });
260
+ // --- WebSocket Event Handlers (Moved logic here) ---
261
+ hasuraWs.on('open', () => {
262
+ debugGraphql(`✅ [${clientId}] Connection to Hasura WS established.`);
263
+ const initMessage = { type: 'connection_init', payload: {} };
264
+ debugGraphql(`📤 [${clientId}] Sending connection_init to Hasura.`);
265
+ hasuraWs === null || hasuraWs === void 0 ? void 0 : hasuraWs.send(JSON.stringify(initMessage));
266
+ });
267
+ client.on('message', (message) => {
268
+ if (!hasuraWs || hasuraWs.readyState !== ws_1.WebSocket.OPEN) {
269
+ debugGraphql(`⚠️ [${clientId}] Received message from client, but Hasura WS not open. Ignoring.`);
270
+ return;
271
+ }
272
+ try {
273
+ const messageStr = message.toString();
274
+ const parsedMessage = JSON.parse(messageStr);
275
+ const type = parsedMessage.type;
276
+ // const preview = messageStr.substring(0, 100) + (messageStr.length > 100 ? '...' : '');
277
+ if (type === 'connection_init') {
278
+ debugGraphql(`🤝 [${clientId}] Received connection_init from client.`);
279
+ clientConnectionInitialized = true;
280
+ if (hasuraConnectionInitialized) {
281
+ debugGraphql(`🤝 [${clientId}] Sending connection_ack to client (Hasura already acked).`);
282
+ client.send(JSON.stringify({ type: 'connection_ack' }));
283
+ }
284
+ return; // Do NOT forward client's connection_init
285
+ }
286
+ if (!clientConnectionInitialized) {
287
+ console.error(`❌ [${clientId}] Message type ${type} received from client before connection_init.`);
288
+ debugGraphql(`❌ [${clientId}] Message type ${type} received from client before connection_init.`);
289
+ closeConnections(4401, 'Connection not initialized');
290
+ return;
291
+ }
292
+ if (['start', 'stop', 'subscribe', 'complete'].includes(type)) {
293
+ debugGraphql(`📤 [${clientId}] Forwarding ${type} C -> H`);
294
+ hasuraWs.send(messageStr);
295
+ }
296
+ else {
297
+ debugGraphql(`❓ [${clientId}] Unknown message type from client: ${type}. Ignoring.`);
298
+ }
299
+ }
300
+ catch (err) {
301
+ console.error(`❌ [${clientId}] Error processing client message:`, err);
302
+ debugGraphql(`❌ [${clientId}] Error processing client message:`, err.message);
303
+ }
304
+ });
305
+ hasuraWs.on('message', (message) => {
306
+ if (client.readyState !== ws_1.WebSocket.OPEN) {
307
+ debugGraphql(`⚠️ [${clientId}] Received message from Hasura, but client WS not open. Ignoring.`);
308
+ return;
309
+ }
310
+ try {
311
+ const messageStr = message.toString();
312
+ const parsedMessage = JSON.parse(messageStr);
313
+ const type = parsedMessage.type;
314
+ // const preview = messageStr.substring(0, 100) + (messageStr.length > 100 ? '...' : '');
315
+ if (type === 'connection_ack') {
316
+ debugGraphql(`🤝 [${clientId}] Received connection_ack from Hasura.`);
317
+ hasuraConnectionInitialized = true;
318
+ if (clientConnectionInitialized) {
319
+ debugGraphql(`🤝 [${clientId}] Sending connection_ack to client (Hasura just acked).`);
320
+ client.send(JSON.stringify({ type: 'connection_ack' }));
321
+ }
322
+ return;
323
+ }
324
+ if (type === 'ka') {
325
+ // debugGraphql(`[${clientId}] Received keep-alive from Hasura. Ignoring.`);
326
+ return; // Ignore Hasura keep-alive
327
+ }
328
+ let messageToSend = messageStr;
329
+ if (type === 'data') {
330
+ debugGraphql(`🔄 [${clientId}] Translating message type 'data' -> 'next'`);
331
+ parsedMessage.type = 'next';
332
+ messageToSend = JSON.stringify(parsedMessage);
333
+ }
334
+ else if (type === 'error') {
335
+ debugGraphql(`❗ [${clientId}] Forwarding error H -> C`);
336
+ }
337
+ else if (type === 'complete') {
338
+ debugGraphql(`✅ [${clientId}] Forwarding complete H -> C`);
339
+ }
340
+ else {
341
+ debugGraphql(`❓ [${clientId}] Unknown message type from Hasura: ${type}. Forwarding as-is.`);
342
+ }
343
+ client.send(messageToSend);
344
+ }
345
+ catch (err) {
346
+ console.error(`❌ [${clientId}] Error processing Hasura message:`, err);
347
+ debugGraphql(`❌ [${clientId}] Error processing Hasura message:`, err.message);
348
+ }
349
+ });
350
+ client.on('close', (code, reason) => {
351
+ const reasonStr = reason.toString();
352
+ debugGraphql(`👋 [${clientId}] Client disconnected: ${code} ${reasonStr}`);
353
+ debugGraphql(`--- proxySOCKET [${clientId}] End (Client Close) ---`);
354
+ closeConnections(code, reasonStr);
355
+ });
356
+ hasuraWs.on('close', (code, reason) => {
357
+ const reasonStr = reason.toString();
358
+ debugGraphql(`👋 [${clientId}] Hasura disconnected: ${code} ${reasonStr}`);
359
+ debugGraphql(`--- proxySOCKET [${clientId}] End (Hasura Close) ---`);
360
+ closeConnections(code, reasonStr);
361
+ });
362
+ client.on('error', (error) => {
363
+ console.error(`❌ [${clientId}] Client WebSocket error:`, error);
364
+ debugGraphql(`❌ [${clientId}] Client WebSocket error:`, error.message);
365
+ debugGraphql(`--- proxySOCKET [${clientId}] End (Client Error) ---`);
366
+ closeConnections(1011, 'Client error');
367
+ });
368
+ hasuraWs.on('error', (error) => {
369
+ console.error(`❌ [${clientId}] Hasura WebSocket error:`, error);
370
+ debugGraphql(`❌ [${clientId}] Hasura WebSocket error:`, error.message);
371
+ debugGraphql(`--- proxySOCKET [${clientId}] End (Hasura Error) ---`);
372
+ closeConnections(1011, 'Hasura connection error');
373
+ });
374
+ }
375
+ catch (error) {
376
+ console.error(`❌ [${clientId}] Error setting up WebSocket proxy:`, error);
377
+ debugGraphql(`❌ [${clientId}] Error setting up WebSocket proxy:`, error.message);
378
+ debugGraphql(`--- proxySOCKET [${clientId}] End (Setup Error) ---`);
379
+ // Ensure client connection is closed on setup error
380
+ if (client.readyState === ws_1.WebSocket.OPEN || client.readyState === ws_1.WebSocket.CONNECTING) {
381
+ client.close(1011, 'Proxy setup error');
382
+ }
383
+ }
384
+ });
385
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ // lib/hasura-schema.ts
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const dotenv_1 = __importDefault(require("dotenv"));
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const graphql_1 = require("graphql"); // Use standard introspection query function
21
+ dotenv_1.default.config({ path: path_1.default.resolve(__dirname, '../.env') });
22
+ const HASURA_GRAPHQL_URL = process.env.NEXT_PUBLIC_HASURA_GRAPHQL_URL;
23
+ const HASURA_ADMIN_SECRET = process.env.HASURA_ADMIN_SECRET;
24
+ const OUTPUT_PATH = path_1.default.resolve(__dirname, '../public/hasura-schema.json');
25
+ if (!HASURA_GRAPHQL_URL) {
26
+ console.error('❌ Ошибка: NEXT_PUBLIC_HASURA_GRAPHQL_URL не определен в .env');
27
+ process.exit(1);
28
+ }
29
+ function fetchSchema() {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ var _a;
32
+ console.log(`🚀 Запрос схемы интроспекции с ${HASURA_GRAPHQL_URL}...`);
33
+ try {
34
+ const headers = {
35
+ 'Content-Type': 'application/json',
36
+ };
37
+ if (HASURA_ADMIN_SECRET) {
38
+ headers['X-Hasura-Admin-Secret'] = HASURA_ADMIN_SECRET;
39
+ console.log('🔑 Используется Hasura Admin Secret.');
40
+ }
41
+ else {
42
+ console.warn('⚠️ HASURA_ADMIN_SECRET не найден. Запрос схемы без админ-прав (может быть неполным).');
43
+ }
44
+ const response = yield axios_1.default.post(HASURA_GRAPHQL_URL, // Add non-null assertion here
45
+ {
46
+ query: (0, graphql_1.getIntrospectionQuery)(), // Use the function to get the query string
47
+ }, { headers });
48
+ if (response.data.errors) {
49
+ throw new Error(`Ошибка GraphQL при запросе схемы: ${JSON.stringify(response.data.errors)}`);
50
+ }
51
+ if (!response.data || !response.data.data || !response.data.data.__schema) {
52
+ throw new Error('Некорректный ответ от сервера Hasura. Отсутствует data.__schema.');
53
+ }
54
+ const introspectionResult = response.data;
55
+ console.log(`💾 Сохранение схемы в ${OUTPUT_PATH}...`);
56
+ fs_1.default.writeFileSync(OUTPUT_PATH, JSON.stringify(introspectionResult, null, 2)); // Сохраняем весь результат
57
+ console.log(`✅ Схема успешно получена и сохранена в ${OUTPUT_PATH}`);
58
+ }
59
+ catch (error) {
60
+ console.error('❌ Ошибка при получении или сохранении схемы:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message || error);
61
+ process.exit(1);
62
+ }
63
+ });
64
+ }
65
+ fetchSchema();
@@ -0,0 +1,16 @@
1
+ import { AxiosInstance } from 'axios';
2
+ interface HasuraOptions {
3
+ url: string;
4
+ secret: string;
5
+ }
6
+ export declare class Hasura {
7
+ private readonly clientInstance;
8
+ constructor(options: HasuraOptions);
9
+ get client(): AxiosInstance;
10
+ sql(sql: string, source?: string, cascade?: boolean): Promise<any>;
11
+ v1(request: {
12
+ type: string;
13
+ args: object;
14
+ }): Promise<any>;
15
+ }
16
+ export {};
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.Hasura = void 0;
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const debug_1 = __importDefault(require("./debug")); // Assuming debug is in lib and alias @ points to root/src
18
+ const debug = (0, debug_1.default)('hasura');
19
+ class Hasura {
20
+ constructor(options) {
21
+ const { url, secret } = options;
22
+ if (!url || !secret) {
23
+ const errorMessage = '❌ Hasura URL or Admin Secret is missing. Check NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT and HASURA_ADMIN_SECRET environment variables.';
24
+ debug(errorMessage);
25
+ throw new Error(errorMessage);
26
+ }
27
+ this.clientInstance = axios_1.default.create({
28
+ baseURL: url.replace('/v1/graphql', ''), // Ensure base URL is correct
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ 'X-Hasura-Admin-Secret': secret,
32
+ },
33
+ });
34
+ debug('✅ Hasura client initialized successfully.');
35
+ }
36
+ get client() {
37
+ return this.clientInstance;
38
+ }
39
+ sql(sql_1) {
40
+ return __awaiter(this, arguments, void 0, function* (sql, source = 'default', cascade = false) {
41
+ var _a, _b, _c;
42
+ debug('🔧 Executing SQL via /v2/query...');
43
+ try {
44
+ const response = yield this.clientInstance.post('/v2/query', {
45
+ type: 'run_sql',
46
+ args: {
47
+ source,
48
+ sql,
49
+ cascade,
50
+ },
51
+ });
52
+ debug('✅ SQL executed successfully.');
53
+ return response.data;
54
+ }
55
+ catch (error) {
56
+ const errorMessage = `❌ Error executing SQL: ${((_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) || error.message}`;
57
+ debug(errorMessage, ((_c = error.response) === null || _c === void 0 ? void 0 : _c.data) || error);
58
+ throw new Error(errorMessage); // Re-throw after logging
59
+ }
60
+ });
61
+ }
62
+ v1(request) {
63
+ return __awaiter(this, void 0, void 0, function* () {
64
+ var _a, _b, _c, _d, _e, _f, _g, _h;
65
+ debug(`🚀 Sending request to /v1/metadata: ${request.type}`);
66
+ try {
67
+ const response = yield this.clientInstance.post('/v1/metadata', request);
68
+ debug(`✅ /v1/metadata request successful for type: ${request.type}`);
69
+ return response.data;
70
+ }
71
+ catch (error) {
72
+ // Log specific Hasura errors if available, otherwise log the general error
73
+ const hasuraError = ((_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) || ((_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.message) || error.message;
74
+ const errorCode = (_f = (_e = error.response) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.code;
75
+ const requestType = request.type; // Get the type of the request
76
+ // Don't throw an error for common "already exists/tracked/not found/defined" issues
77
+ const ignorableErrors = [
78
+ 'already exists',
79
+ 'already tracked',
80
+ 'not found',
81
+ 'already defined', // for permissions creation
82
+ ];
83
+ let isIgnorable = typeof hasuraError === 'string' && ignorableErrors.some(phrase => hasuraError.includes(phrase));
84
+ // Specifically ignore 'permission-denied' ONLY for drop/untrack operations, as it likely means "not found" in this context
85
+ if (!isIgnorable && errorCode === 'permission-denied' && (requestType.startsWith('pg_drop_') || requestType.startsWith('pg_untrack_'))) {
86
+ debug(`📝 Note: Ignoring 'permission-denied' for ${requestType}, likely means target object was not found.`);
87
+ isIgnorable = true;
88
+ }
89
+ if (isIgnorable) {
90
+ debug(`📝 Note: Non-critical issue for ${requestType} - ${hasuraError} ${errorCode ? `(Code: ${errorCode})` : ''}`);
91
+ return ((_g = error.response) === null || _g === void 0 ? void 0 : _g.data) || { info: hasuraError }; // Return info instead of throwing
92
+ }
93
+ else {
94
+ const errorMessage = `❌ Error in /v1/metadata for type ${requestType}: ${hasuraError} ${errorCode ? `(Code: ${errorCode})` : ''}`;
95
+ debug(errorMessage, ((_h = error.response) === null || _h === void 0 ? void 0 : _h.data) || error);
96
+ throw new Error(errorMessage); // Re-throw critical errors
97
+ }
98
+ }
99
+ });
100
+ }
101
+ }
102
+ exports.Hasura = Hasura;
@@ -0,0 +1,7 @@
1
+ type ConnectionStatus = 'connecting' | 'connected' | 'error';
2
+ /**
3
+ * Hook to check connection to Hasura GraphQL endpoint
4
+ * @returns {ConnectionStatus} Current connection status
5
+ */
6
+ export declare function useCheckConnection(): ConnectionStatus;
7
+ export default useCheckConnection;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ 'use client';
3
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
+ return new (P || (P = Promise))(function (resolve, reject) {
6
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10
+ });
11
+ };
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.useCheckConnection = useCheckConnection;
17
+ const react_1 = require("react");
18
+ const apollo_1 = require("../apollo");
19
+ const debug_1 = __importDefault(require("@/lib/debug"));
20
+ // Create a debug logger for this module
21
+ const debug = (0, debug_1.default)('hooks:connection');
22
+ /**
23
+ * Hook to check connection to Hasura GraphQL endpoint
24
+ * @returns {ConnectionStatus} Current connection status
25
+ */
26
+ function useCheckConnection() {
27
+ const [status, setStatus] = (0, react_1.useState)('connecting');
28
+ (0, react_1.useEffect)(() => {
29
+ let mounted = true;
30
+ const checkConnectionStatus = () => __awaiter(this, void 0, void 0, function* () {
31
+ try {
32
+ if (mounted)
33
+ setStatus('connecting');
34
+ const client = (0, apollo_1.getClient)();
35
+ const isConnected = yield (0, apollo_1.checkConnection)(client);
36
+ if (!mounted)
37
+ return;
38
+ if (isConnected) {
39
+ // Use the debug function directly if log is undefined
40
+ debug('hooks:connection', '✅ Connected to Hasura GraphQL endpoint');
41
+ setStatus('connected');
42
+ }
43
+ else {
44
+ debug('hooks:connection', '❌ Failed to connect to Hasura GraphQL endpoint');
45
+ setStatus('error');
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (mounted) {
50
+ debug('hooks:connection', '❌ Connection error:', error);
51
+ setStatus('error');
52
+ }
53
+ }
54
+ });
55
+ checkConnectionStatus();
56
+ return () => {
57
+ mounted = false;
58
+ };
59
+ }, []);
60
+ return status;
61
+ }
62
+ exports.default = useCheckConnection;
@@ -1,3 +1,8 @@
1
1
  export * from './utils';
2
2
  export * from './apollo';
3
3
  export * from './generator';
4
+ export * from './hasura';
5
+ export * from './auth';
6
+ export * from './client';
7
+ export * from './jwt';
8
+ export * from './graphql-proxy';