@tthr/vue 0.0.40 → 0.0.44
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/nuxt/module.js
CHANGED
|
@@ -38,6 +38,7 @@ export default defineNuxtModule({
|
|
|
38
38
|
const apiKey = process.env.TETHER_API_KEY || '';
|
|
39
39
|
const url = options.url || process.env.TETHER_URL || 'https://tether-api.strands.gg';
|
|
40
40
|
const projectId = options.projectId || process.env.TETHER_PROJECT_ID || '';
|
|
41
|
+
const verbose = options.verbose ?? (process.env.TETHER_VERBOSE === 'true');
|
|
41
42
|
// Calculate WebSocket URL from HTTP URL
|
|
42
43
|
const wsUrl = url.replace(/^https:/, 'wss:').replace(/^http:/, 'ws:');
|
|
43
44
|
// Server-side config (includes API key - never exposed to client)
|
|
@@ -45,10 +46,12 @@ export default defineNuxtModule({
|
|
|
45
46
|
// - NUXT_TETHER_API_KEY
|
|
46
47
|
// - NUXT_TETHER_URL
|
|
47
48
|
// - NUXT_TETHER_PROJECT_ID
|
|
49
|
+
// - NUXT_TETHER_VERBOSE
|
|
48
50
|
nuxt.options.runtimeConfig.tether = {
|
|
49
51
|
apiKey,
|
|
50
52
|
url,
|
|
51
53
|
projectId,
|
|
54
|
+
verbose,
|
|
52
55
|
};
|
|
53
56
|
// Public config (safe for client - no secrets)
|
|
54
57
|
// Can be overridden at runtime via:
|
package/nuxt/module.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface TetherModuleOptions {
|
|
|
26
26
|
projectId?: string;
|
|
27
27
|
/** API endpoint (defaults to Tether Cloud) */
|
|
28
28
|
url?: string;
|
|
29
|
+
/** Enable verbose logging for debugging WebSocket connections */
|
|
30
|
+
verbose?: boolean;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export default defineNuxtModule<TetherModuleOptions>({
|
|
@@ -48,6 +50,7 @@ export default defineNuxtModule<TetherModuleOptions>({
|
|
|
48
50
|
const apiKey = process.env.TETHER_API_KEY || '';
|
|
49
51
|
const url = options.url || process.env.TETHER_URL || 'https://tether-api.strands.gg';
|
|
50
52
|
const projectId = options.projectId || process.env.TETHER_PROJECT_ID || '';
|
|
53
|
+
const verbose = options.verbose ?? (process.env.TETHER_VERBOSE === 'true');
|
|
51
54
|
|
|
52
55
|
// Calculate WebSocket URL from HTTP URL
|
|
53
56
|
const wsUrl = url.replace(/^https:/, 'wss:').replace(/^http:/, 'ws:');
|
|
@@ -57,17 +60,19 @@ export default defineNuxtModule<TetherModuleOptions>({
|
|
|
57
60
|
// - NUXT_TETHER_API_KEY
|
|
58
61
|
// - NUXT_TETHER_URL
|
|
59
62
|
// - NUXT_TETHER_PROJECT_ID
|
|
60
|
-
|
|
63
|
+
// - NUXT_TETHER_VERBOSE
|
|
64
|
+
(nuxt.options.runtimeConfig as any).tether = {
|
|
61
65
|
apiKey,
|
|
62
66
|
url,
|
|
63
67
|
projectId,
|
|
68
|
+
verbose,
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
// Public config (safe for client - no secrets)
|
|
67
72
|
// Can be overridden at runtime via:
|
|
68
73
|
// - NUXT_PUBLIC_TETHER_PROJECT_ID
|
|
69
74
|
// - NUXT_PUBLIC_TETHER_WS_URL
|
|
70
|
-
nuxt.options.runtimeConfig.public.tether = {
|
|
75
|
+
(nuxt.options.runtimeConfig.public as any).tether = {
|
|
71
76
|
projectId,
|
|
72
77
|
wsUrl,
|
|
73
78
|
};
|
|
@@ -140,7 +145,7 @@ export default defineNuxtModule<TetherModuleOptions>({
|
|
|
140
145
|
|
|
141
146
|
// Add Nitro plugin for cron WebSocket connection
|
|
142
147
|
// This auto-connects to Tether when the server starts
|
|
143
|
-
nuxt.hook('nitro:config', (nitroConfig) => {
|
|
148
|
+
nuxt.hook('nitro:config' as any, (nitroConfig: any) => {
|
|
144
149
|
nitroConfig.plugins = nitroConfig.plugins || [];
|
|
145
150
|
// Use direct path - Nitro will handle the #imports resolution at build time
|
|
146
151
|
nitroConfig.plugins.push(resolver.resolve('./runtime/server/plugins/cron.js'));
|
|
@@ -10,6 +10,16 @@ import { useRuntimeConfig } from '#imports';
|
|
|
10
10
|
let functionRegistry = null;
|
|
11
11
|
let registryError = null;
|
|
12
12
|
|
|
13
|
+
// Verbose logging support
|
|
14
|
+
let verboseLogging = false;
|
|
15
|
+
const PREFIX = '[Tether]';
|
|
16
|
+
const log = {
|
|
17
|
+
debug: (...args) => { if (verboseLogging) console.log(PREFIX, ...args); },
|
|
18
|
+
info: (...args) => console.log(PREFIX, ...args),
|
|
19
|
+
warn: (...args) => console.warn(PREFIX, ...args),
|
|
20
|
+
error: (...args) => console.error(PREFIX, ...args),
|
|
21
|
+
};
|
|
22
|
+
|
|
13
23
|
/**
|
|
14
24
|
* Try to load the user's custom functions from ~/tether/functions
|
|
15
25
|
*/
|
|
@@ -21,23 +31,23 @@ async function loadFunctionRegistry() {
|
|
|
21
31
|
try {
|
|
22
32
|
// Try to import the user's functions index
|
|
23
33
|
// This path is relative to the Nuxt app's server runtime
|
|
24
|
-
|
|
34
|
+
log.debug('Loading custom functions from ~~/tether/functions/index.ts');
|
|
25
35
|
const functions = await import('~~/tether/functions/index.ts').catch((err) => {
|
|
26
|
-
|
|
36
|
+
log.debug('Failed to import functions:', err.message);
|
|
27
37
|
return null;
|
|
28
38
|
});
|
|
29
39
|
|
|
30
40
|
if (functions) {
|
|
31
|
-
|
|
41
|
+
log.debug('Loaded function modules:', Object.keys(functions));
|
|
32
42
|
functionRegistry = functions;
|
|
33
43
|
} else {
|
|
34
44
|
// No custom functions found - that's OK, we'll proxy everything
|
|
35
|
-
|
|
45
|
+
log.debug('No custom functions found, will proxy all requests');
|
|
36
46
|
functionRegistry = {};
|
|
37
47
|
}
|
|
38
48
|
} catch (error) {
|
|
39
49
|
// Failed to load - we'll proxy everything to Tether API
|
|
40
|
-
|
|
50
|
+
log.debug('Could not load custom functions:', error.message);
|
|
41
51
|
registryError = error;
|
|
42
52
|
functionRegistry = {};
|
|
43
53
|
}
|
|
@@ -217,6 +227,7 @@ function createDatabaseProxy(apiKey, url, projectId) {
|
|
|
217
227
|
|
|
218
228
|
export default defineEventHandler(async (event) => {
|
|
219
229
|
const config = useRuntimeConfig();
|
|
230
|
+
verboseLogging = config.tether?.verbose || process.env.TETHER_VERBOSE === 'true';
|
|
220
231
|
|
|
221
232
|
// Get API key from runtime config (populated from TETHER_API_KEY env var)
|
|
222
233
|
const apiKey = config.tether?.apiKey || process.env.TETHER_API_KEY;
|
|
@@ -251,12 +262,12 @@ export default defineEventHandler(async (event) => {
|
|
|
251
262
|
|
|
252
263
|
// Try to find a custom function
|
|
253
264
|
const customFn = lookupFunction(body.function);
|
|
254
|
-
|
|
265
|
+
log.debug(`Mutation: ${body.function}, custom function found: ${!!customFn}`);
|
|
255
266
|
|
|
256
267
|
if (customFn) {
|
|
257
268
|
// Execute locally with database proxy
|
|
258
269
|
try {
|
|
259
|
-
|
|
270
|
+
log.debug(`Executing custom mutation: ${body.function}`);
|
|
260
271
|
const db = createDatabaseProxy(apiKey, url, projectId);
|
|
261
272
|
|
|
262
273
|
// Create auth context - try to get user identity from request
|
|
@@ -288,7 +299,7 @@ export default defineEventHandler(async (event) => {
|
|
|
288
299
|
}
|
|
289
300
|
}
|
|
290
301
|
} catch (authError) {
|
|
291
|
-
|
|
302
|
+
log.warn('Auth validation failed:', authError.message);
|
|
292
303
|
// Auth validation failed - continue without identity
|
|
293
304
|
}
|
|
294
305
|
}
|
|
@@ -315,7 +326,7 @@ export default defineEventHandler(async (event) => {
|
|
|
315
326
|
|
|
316
327
|
return { data: result };
|
|
317
328
|
} catch (error) {
|
|
318
|
-
|
|
329
|
+
log.error(`Mutation ${body.function} failed:`, error);
|
|
319
330
|
// Return JSON error response instead of throwing (avoids proxy HTML error pages)
|
|
320
331
|
setResponseStatus(event, 400);
|
|
321
332
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["cron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["cron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoCH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAC3C,IAAI,CAGN;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;;AAyPD,wBAiCG"}
|
|
@@ -20,10 +20,24 @@ let heartbeatTimer = null;
|
|
|
20
20
|
let heartbeatTimeoutTimer = null;
|
|
21
21
|
let awaitingPong = false;
|
|
22
22
|
let shouldReconnect = true;
|
|
23
|
+
let verboseLogging = false;
|
|
23
24
|
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
24
|
-
const HEARTBEAT_INTERVAL =
|
|
25
|
+
const HEARTBEAT_INTERVAL = 20000; // 20 seconds - keeps connection alive through proxies with 30s idle timeout
|
|
25
26
|
const HEARTBEAT_TIMEOUT = 10000;
|
|
26
27
|
const RECONNECT_DELAY = 1000;
|
|
28
|
+
const PREFIX = '[Tether Cron]';
|
|
29
|
+
/** Simple logger that respects verbose flag */
|
|
30
|
+
const log = {
|
|
31
|
+
/** Debug messages - only shown when verbose is enabled */
|
|
32
|
+
debug: (...args) => { if (verboseLogging)
|
|
33
|
+
console.log(PREFIX, ...args); },
|
|
34
|
+
/** Info messages - always shown */
|
|
35
|
+
info: (...args) => console.log(PREFIX, ...args),
|
|
36
|
+
/** Warning messages - always shown */
|
|
37
|
+
warn: (...args) => console.warn(PREFIX, ...args),
|
|
38
|
+
/** Error messages - always shown */
|
|
39
|
+
error: (...args) => console.error(PREFIX, ...args),
|
|
40
|
+
};
|
|
27
41
|
/**
|
|
28
42
|
* Register a cron handler for a specific function
|
|
29
43
|
*
|
|
@@ -40,7 +54,7 @@ const RECONNECT_DELAY = 1000;
|
|
|
40
54
|
*/
|
|
41
55
|
export function registerCronHandler(functionName, handler) {
|
|
42
56
|
cronHandlers.set(functionName, handler);
|
|
43
|
-
|
|
57
|
+
log.debug('Registered handler for:', functionName);
|
|
44
58
|
}
|
|
45
59
|
/**
|
|
46
60
|
* Unregister a cron handler
|
|
@@ -97,22 +111,22 @@ async function reportExecution(config, trigger, result, durationMs) {
|
|
|
97
111
|
}),
|
|
98
112
|
});
|
|
99
113
|
if (!response.ok) {
|
|
100
|
-
|
|
114
|
+
log.error(`Failed to report execution: ${response.status} ${response.statusText}`);
|
|
101
115
|
}
|
|
102
116
|
else {
|
|
103
|
-
|
|
117
|
+
log.debug(`Reported execution ${trigger.executionId}: ${result.success ? 'success' : 'failed'}`);
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
catch (error) {
|
|
107
|
-
|
|
121
|
+
log.error('Failed to report execution:', error);
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
async function handleCronTrigger(config, trigger) {
|
|
111
|
-
|
|
125
|
+
log.info(`Received trigger for ${trigger.functionName} (execution: ${trigger.executionId})`);
|
|
112
126
|
const startTime = Date.now();
|
|
113
127
|
const handler = cronHandlers.get(trigger.functionName);
|
|
114
128
|
if (!handler) {
|
|
115
|
-
|
|
129
|
+
log.warn(`No handler registered for: ${trigger.functionName}`);
|
|
116
130
|
const durationMs = Date.now() - startTime;
|
|
117
131
|
await reportExecution(config, trigger, {
|
|
118
132
|
success: false,
|
|
@@ -131,7 +145,7 @@ async function handleCronTrigger(config, trigger) {
|
|
|
131
145
|
catch (error) {
|
|
132
146
|
const durationMs = Date.now() - startTime;
|
|
133
147
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
134
|
-
|
|
148
|
+
log.error(`Handler error for ${trigger.functionName}:`, errorMessage);
|
|
135
149
|
await reportExecution(config, trigger, {
|
|
136
150
|
success: false,
|
|
137
151
|
error: errorMessage,
|
|
@@ -143,14 +157,18 @@ function startHeartbeat() {
|
|
|
143
157
|
heartbeatTimer = setInterval(() => {
|
|
144
158
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
145
159
|
awaitingPong = true;
|
|
160
|
+
log.debug('Sending heartbeat ping');
|
|
146
161
|
ws.send(JSON.stringify({ type: 'ping' }));
|
|
147
162
|
heartbeatTimeoutTimer = setTimeout(() => {
|
|
148
163
|
if (awaitingPong) {
|
|
149
|
-
|
|
164
|
+
log.warn('Heartbeat timeout - forcing reconnect');
|
|
150
165
|
ws?.close();
|
|
151
166
|
}
|
|
152
167
|
}, HEARTBEAT_TIMEOUT);
|
|
153
168
|
}
|
|
169
|
+
else {
|
|
170
|
+
log.warn(`Cannot send ping - WebSocket state: ${ws?.readyState}`);
|
|
171
|
+
}
|
|
154
172
|
}, HEARTBEAT_INTERVAL);
|
|
155
173
|
}
|
|
156
174
|
function stopHeartbeat() {
|
|
@@ -176,7 +194,7 @@ function connect(config) {
|
|
|
176
194
|
ws = new WebSocketImpl(url);
|
|
177
195
|
ws.onopen = () => {
|
|
178
196
|
reconnectAttempts = 0;
|
|
179
|
-
|
|
197
|
+
log.debug('WebSocket connected');
|
|
180
198
|
};
|
|
181
199
|
ws.onmessage = async (event) => {
|
|
182
200
|
const data = typeof event.data === 'string' ? event.data : event.data.toString();
|
|
@@ -186,12 +204,13 @@ function connect(config) {
|
|
|
186
204
|
case 'connected':
|
|
187
205
|
connectionId = message.connection_id ?? null;
|
|
188
206
|
startHeartbeat();
|
|
189
|
-
|
|
207
|
+
log.info(`Connected with ID: ${connectionId}`);
|
|
190
208
|
break;
|
|
191
209
|
case 'cron_trigger':
|
|
192
210
|
await handleCronTrigger(config, message);
|
|
193
211
|
break;
|
|
194
212
|
case 'pong':
|
|
213
|
+
log.debug('Received pong');
|
|
195
214
|
awaitingPong = false;
|
|
196
215
|
if (heartbeatTimeoutTimer) {
|
|
197
216
|
clearTimeout(heartbeatTimeoutTimer);
|
|
@@ -199,27 +218,27 @@ function connect(config) {
|
|
|
199
218
|
}
|
|
200
219
|
break;
|
|
201
220
|
case 'error':
|
|
202
|
-
|
|
221
|
+
log.error('Server error:', message.error);
|
|
203
222
|
break;
|
|
204
223
|
}
|
|
205
224
|
}
|
|
206
225
|
catch (e) {
|
|
207
|
-
|
|
226
|
+
log.error('Failed to parse message:', e);
|
|
208
227
|
}
|
|
209
228
|
};
|
|
210
229
|
ws.onerror = (error) => {
|
|
211
230
|
const err = error instanceof Error ? error : new Error('WebSocket error');
|
|
212
|
-
|
|
231
|
+
log.error('WebSocket error:', err.message);
|
|
213
232
|
};
|
|
214
|
-
ws.onclose = () => {
|
|
233
|
+
ws.onclose = (event) => {
|
|
215
234
|
connectionId = null;
|
|
216
235
|
stopHeartbeat();
|
|
217
|
-
|
|
236
|
+
log.debug(`WebSocket disconnected (code: ${event.code}, reason: ${event.reason || 'none'})`);
|
|
218
237
|
handleReconnect(config);
|
|
219
238
|
};
|
|
220
239
|
}
|
|
221
240
|
catch (error) {
|
|
222
|
-
|
|
241
|
+
log.error('Failed to connect:', error);
|
|
223
242
|
handleReconnect(config);
|
|
224
243
|
}
|
|
225
244
|
}
|
|
@@ -228,12 +247,12 @@ function handleReconnect(config) {
|
|
|
228
247
|
return;
|
|
229
248
|
}
|
|
230
249
|
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
231
|
-
|
|
250
|
+
log.error('Max reconnection attempts reached');
|
|
232
251
|
return;
|
|
233
252
|
}
|
|
234
253
|
reconnectAttempts++;
|
|
235
254
|
const delay = Math.min(RECONNECT_DELAY * Math.pow(2, reconnectAttempts - 1), 30000);
|
|
236
|
-
|
|
255
|
+
log.debug(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
237
256
|
setTimeout(() => {
|
|
238
257
|
connect(config);
|
|
239
258
|
}, delay);
|
|
@@ -241,15 +260,17 @@ function handleReconnect(config) {
|
|
|
241
260
|
export default defineNitroPlugin((nitro) => {
|
|
242
261
|
// Get config from runtime config
|
|
243
262
|
const config = useRuntimeConfig();
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const
|
|
263
|
+
const tetherConfig = config.tether;
|
|
264
|
+
const apiKey = tetherConfig?.apiKey || process.env.TETHER_API_KEY;
|
|
265
|
+
const url = tetherConfig?.url || process.env.TETHER_URL || 'https://tether-api.strands.gg';
|
|
266
|
+
const projectId = tetherConfig?.projectId || process.env.TETHER_PROJECT_ID;
|
|
267
|
+
verboseLogging = tetherConfig?.verbose || process.env.TETHER_VERBOSE === 'true';
|
|
247
268
|
// Only connect if we have all required config
|
|
248
269
|
if (!apiKey || !projectId) {
|
|
249
|
-
|
|
270
|
+
log.debug('Missing config (apiKey or projectId) - cron connection disabled');
|
|
250
271
|
return;
|
|
251
272
|
}
|
|
252
|
-
|
|
273
|
+
log.info('Initialising cron connection...');
|
|
253
274
|
// Connect on next tick to allow handlers to be registered first
|
|
254
275
|
process.nextTick(() => {
|
|
255
276
|
connect({ url, projectId, apiKey });
|
|
@@ -262,7 +283,6 @@ export default defineNitroPlugin((nitro) => {
|
|
|
262
283
|
ws.close();
|
|
263
284
|
ws = null;
|
|
264
285
|
}
|
|
265
|
-
|
|
286
|
+
log.debug('Connection closed');
|
|
266
287
|
});
|
|
267
288
|
});
|
|
268
|
-
//# sourceMappingURL=cron.js.map
|
|
@@ -10,6 +10,16 @@ import { useRuntimeConfig } from '#imports';
|
|
|
10
10
|
let functionRegistry = null;
|
|
11
11
|
let registryError = null;
|
|
12
12
|
|
|
13
|
+
// Verbose logging support
|
|
14
|
+
let verboseLogging = false;
|
|
15
|
+
const PREFIX = '[Tether]';
|
|
16
|
+
const log = {
|
|
17
|
+
debug: (...args) => { if (verboseLogging) console.log(PREFIX, ...args); },
|
|
18
|
+
info: (...args) => console.log(PREFIX, ...args),
|
|
19
|
+
warn: (...args) => console.warn(PREFIX, ...args),
|
|
20
|
+
error: (...args) => console.error(PREFIX, ...args),
|
|
21
|
+
};
|
|
22
|
+
|
|
13
23
|
/**
|
|
14
24
|
* Try to load the user's custom functions from ~/tether/functions
|
|
15
25
|
*/
|
|
@@ -21,23 +31,23 @@ async function loadFunctionRegistry() {
|
|
|
21
31
|
try {
|
|
22
32
|
// Try to import the user's functions index
|
|
23
33
|
// This path is relative to the Nuxt app's server runtime
|
|
24
|
-
|
|
34
|
+
log.debug('Loading custom functions from ~~/tether/functions/index.ts');
|
|
25
35
|
const functions = await import('~~/tether/functions/index.ts').catch((err) => {
|
|
26
|
-
|
|
36
|
+
log.debug('Failed to import functions:', err.message);
|
|
27
37
|
return null;
|
|
28
38
|
});
|
|
29
39
|
|
|
30
40
|
if (functions) {
|
|
31
|
-
|
|
41
|
+
log.debug('Loaded function modules:', Object.keys(functions));
|
|
32
42
|
functionRegistry = functions;
|
|
33
43
|
} else {
|
|
34
44
|
// No custom functions found - that's OK, we'll proxy everything
|
|
35
|
-
|
|
45
|
+
log.debug('No custom functions found, will proxy all requests');
|
|
36
46
|
functionRegistry = {};
|
|
37
47
|
}
|
|
38
48
|
} catch (error) {
|
|
39
49
|
// Failed to load - we'll proxy everything to Tether API
|
|
40
|
-
|
|
50
|
+
log.debug('Could not load custom functions:', error.message);
|
|
41
51
|
registryError = error;
|
|
42
52
|
functionRegistry = {};
|
|
43
53
|
}
|
|
@@ -217,6 +227,7 @@ function createDatabaseProxy(apiKey, url, projectId) {
|
|
|
217
227
|
|
|
218
228
|
export default defineEventHandler(async (event) => {
|
|
219
229
|
const config = useRuntimeConfig();
|
|
230
|
+
verboseLogging = config.tether?.verbose || process.env.TETHER_VERBOSE === 'true';
|
|
220
231
|
|
|
221
232
|
// Get API key from runtime config (populated from TETHER_API_KEY env var)
|
|
222
233
|
const apiKey = config.tether?.apiKey || process.env.TETHER_API_KEY;
|
|
@@ -251,7 +262,7 @@ export default defineEventHandler(async (event) => {
|
|
|
251
262
|
|
|
252
263
|
// Try to find a custom function
|
|
253
264
|
const customFn = lookupFunction(body.function);
|
|
254
|
-
|
|
265
|
+
log.debug(`Query: ${body.function}, custom function found: ${!!customFn}`);
|
|
255
266
|
|
|
256
267
|
if (customFn) {
|
|
257
268
|
// Execute locally with database proxy
|
|
@@ -288,7 +299,7 @@ export default defineEventHandler(async (event) => {
|
|
|
288
299
|
}
|
|
289
300
|
}
|
|
290
301
|
} catch (authError) {
|
|
291
|
-
|
|
302
|
+
log.warn('Auth validation failed:', authError.message);
|
|
292
303
|
// Auth validation failed - continue without identity
|
|
293
304
|
}
|
|
294
305
|
}
|
|
@@ -306,18 +317,18 @@ export default defineEventHandler(async (event) => {
|
|
|
306
317
|
userId: userIdentity?.subject ?? null,
|
|
307
318
|
};
|
|
308
319
|
|
|
309
|
-
|
|
320
|
+
log.debug(`Executing custom function: ${body.function}`);
|
|
310
321
|
const result = await customFn.handler({
|
|
311
322
|
db,
|
|
312
323
|
ctx,
|
|
313
324
|
auth,
|
|
314
325
|
args: body.args ?? {},
|
|
315
326
|
});
|
|
316
|
-
|
|
327
|
+
log.debug(`Function ${body.function} completed`);
|
|
317
328
|
|
|
318
329
|
return { data: result };
|
|
319
330
|
} catch (error) {
|
|
320
|
-
|
|
331
|
+
log.error(`Function ${body.function} failed:`, error.message || error);
|
|
321
332
|
throw createError({
|
|
322
333
|
statusCode: 500,
|
|
323
334
|
message: error.message || 'Function execution failed',
|