@tthr/vue 0.0.84 → 0.0.86
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 +2 -2
- package/nuxt/module.ts +2 -2
- package/nuxt/runtime/composables.ts +638 -0
- package/nuxt/runtime/plugin.client.ts +34 -0
- package/nuxt/runtime/server/mutation.post.js +30 -364
- package/nuxt/runtime/server/mutation.post.ts +53 -0
- package/nuxt/runtime/server/plugins/cron.ts +377 -0
- package/nuxt/runtime/server/query.post.js +27 -362
- package/nuxt/runtime/server/query.post.ts +51 -0
- package/nuxt/runtime/server/utils/handler.js +318 -0
- package/nuxt/runtime/server/utils/handler.ts +375 -0
- package/nuxt/runtime/server/utils/tether.js +187 -210
- package/nuxt/runtime/server/utils/tether.ts +292 -0
- package/package.json +7 -9
- package/dist/nuxt.d.ts +0 -14
- package/dist/nuxt.d.ts.map +0 -1
- package/dist/nuxt.js +0 -48
- package/dist/nuxt.js.map +0 -1
- package/dist/runtime/composables.d.ts +0 -73
- package/dist/runtime/composables.d.ts.map +0 -1
- package/dist/runtime/composables.js +0 -112
- package/dist/runtime/composables.js.map +0 -1
- package/dist/runtime/plugin.d.ts +0 -11
- package/dist/runtime/plugin.d.ts.map +0 -1
- package/dist/runtime/plugin.js +0 -33
- package/dist/runtime/plugin.js.map +0 -1
- package/nuxt/runtime/composables.d.ts +0 -142
- package/nuxt/runtime/composables.d.ts.map +0 -1
- package/nuxt/runtime/composables.js +0 -480
- package/nuxt/runtime/composables.js.map +0 -1
- package/nuxt/runtime/plugin.client.d.ts +0 -17
- package/nuxt/runtime/plugin.client.d.ts.map +0 -1
- package/nuxt/runtime/plugin.client.js +0 -20
- package/nuxt/runtime/plugin.client.js.map +0 -1
- package/nuxt/runtime/server/plugins/cron.d.ts +0 -38
- package/nuxt/runtime/server/plugins/cron.d.ts.map +0 -1
- package/nuxt/runtime/server/plugins/cron.js.map +0 -1
|
@@ -2,371 +2,36 @@
|
|
|
2
2
|
* Server-side query handler
|
|
3
3
|
* Executes local custom functions or proxies generic CRUD to Tether API
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Try to load the user's custom functions from ~/tether/functions
|
|
25
|
-
*/
|
|
26
|
-
async function loadFunctionRegistry() {
|
|
27
|
-
if (functionRegistry !== null || registryError !== null) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// Try to import the user's functions index
|
|
33
|
-
// This path is relative to the Nuxt app's server runtime
|
|
34
|
-
log.debug('Loading custom functions from ~~/tether/functions/index.ts');
|
|
35
|
-
const functions = await import('~~/tether/functions/index.ts').catch((err) => {
|
|
36
|
-
log.debug('Failed to import functions:', err.message);
|
|
37
|
-
return null;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (functions) {
|
|
41
|
-
log.debug('Loaded function modules:', Object.keys(functions));
|
|
42
|
-
functionRegistry = functions;
|
|
43
|
-
} else {
|
|
44
|
-
// No custom functions found - that's OK, we'll proxy everything
|
|
45
|
-
log.debug('No custom functions found, will proxy all requests');
|
|
46
|
-
functionRegistry = {};
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
// Failed to load - we'll proxy everything to Tether API
|
|
50
|
-
log.debug('Could not load custom functions:', error.message);
|
|
51
|
-
registryError = error;
|
|
52
|
-
functionRegistry = {};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Look up a function by name (e.g., "channel.getMyChannels")
|
|
58
|
-
*/
|
|
59
|
-
function lookupFunction(name) {
|
|
60
|
-
if (!functionRegistry || !name) return null;
|
|
61
|
-
|
|
62
|
-
const parts = name.split('.');
|
|
63
|
-
if (parts.length !== 2) return null;
|
|
64
|
-
|
|
65
|
-
const [moduleName, fnName] = parts;
|
|
66
|
-
const module = functionRegistry[moduleName];
|
|
67
|
-
|
|
68
|
-
if (!module) return null;
|
|
69
|
-
|
|
70
|
-
const fn = module[fnName];
|
|
71
|
-
|
|
72
|
-
// Check if it's a valid Tether function (has a handler)
|
|
73
|
-
if (fn && typeof fn === 'object' && typeof fn.handler === 'function') {
|
|
74
|
-
return fn;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Create a database proxy that routes calls to Tether's CRUD endpoints
|
|
82
|
-
*/
|
|
83
|
-
function createDatabaseProxy(apiKey, url, projectId) {
|
|
84
|
-
return new Proxy({}, {
|
|
85
|
-
get(_target, tableName) {
|
|
86
|
-
const makeRequest = async (operation, args) => {
|
|
87
|
-
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
88
|
-
method: 'POST',
|
|
89
|
-
headers: {
|
|
90
|
-
'Content-Type': 'application/json',
|
|
91
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
92
|
-
},
|
|
93
|
-
body: JSON.stringify({
|
|
94
|
-
function: `${tableName}.${operation}`,
|
|
95
|
-
args,
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
101
|
-
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const result = await response.json();
|
|
105
|
-
return result.data;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const makeMutation = async (operation, args) => {
|
|
109
|
-
const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
|
|
110
|
-
method: 'POST',
|
|
111
|
-
headers: {
|
|
112
|
-
'Content-Type': 'application/json',
|
|
113
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
114
|
-
},
|
|
115
|
-
body: JSON.stringify({
|
|
116
|
-
function: `${tableName}.${operation}`,
|
|
117
|
-
args,
|
|
118
|
-
}),
|
|
5
|
+
import { defineEventHandler, readBody, createError } from 'h3';
|
|
6
|
+
import { getConfig, lookupFunction, createDatabaseProxy, buildAuthContext, proxyToTetherApi, log, } from './utils/handler';
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const body = await readBody(event);
|
|
10
|
+
if (!body?.function) {
|
|
11
|
+
throw createError({
|
|
12
|
+
statusCode: 400,
|
|
13
|
+
message: 'Missing "function" in request body',
|
|
119
14
|
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
15
|
+
}
|
|
16
|
+
const customFn = await lookupFunction(body.function);
|
|
17
|
+
log.debug(`Query: ${body.function}, custom function found: ${!!customFn}`);
|
|
18
|
+
if (customFn) {
|
|
19
|
+
try {
|
|
20
|
+
const db = createDatabaseProxy(config.apiKey, config.url, config.projectId);
|
|
21
|
+
const { auth, ctx } = await buildAuthContext(event, config.url, config.projectId);
|
|
22
|
+
log.debug(`Executing custom function: ${body.function}`);
|
|
23
|
+
const result = await customFn.handler({ db, ctx, auth, args: body.args ?? {} });
|
|
24
|
+
log.debug(`Function ${body.function} completed`);
|
|
25
|
+
return { data: result };
|
|
124
26
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
findMany: async (options) => {
|
|
132
|
-
const args = {};
|
|
133
|
-
if (options?.where) args.where = options.where;
|
|
134
|
-
if (options?.limit) args.limit = options.limit;
|
|
135
|
-
if (options?.offset) args.offset = options.offset;
|
|
136
|
-
// Handle orderBy as either a string or an object { column: 'asc'|'desc' }
|
|
137
|
-
if (options?.orderBy) {
|
|
138
|
-
if (typeof options.orderBy === 'string') {
|
|
139
|
-
args.orderBy = options.orderBy;
|
|
140
|
-
} else if (typeof options.orderBy === 'object') {
|
|
141
|
-
const [column, dir] = Object.entries(options.orderBy)[0] || [];
|
|
142
|
-
if (column) {
|
|
143
|
-
args.orderBy = column;
|
|
144
|
-
args.orderDir = dir?.toUpperCase?.() || 'ASC';
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (options?.orderDir) args.orderDir = options.orderDir;
|
|
149
|
-
return makeRequest('list', args);
|
|
150
|
-
},
|
|
151
|
-
findFirst: async (options) => {
|
|
152
|
-
const args = { limit: 1 };
|
|
153
|
-
if (options?.where) args.where = options.where;
|
|
154
|
-
const results = await makeRequest('list', args);
|
|
155
|
-
return results?.[0] ?? null;
|
|
156
|
-
},
|
|
157
|
-
findUnique: async (options) => {
|
|
158
|
-
const args = { limit: 1 };
|
|
159
|
-
if (options?.where) args.where = options.where;
|
|
160
|
-
const results = await makeRequest('list', args);
|
|
161
|
-
return results?.[0] ?? null;
|
|
162
|
-
},
|
|
163
|
-
findById: async (id) => {
|
|
164
|
-
return makeRequest('get', { id });
|
|
165
|
-
},
|
|
166
|
-
count: async (options) => {
|
|
167
|
-
const args = {};
|
|
168
|
-
if (options?.where) args.where = options.where;
|
|
169
|
-
const result = await makeRequest('count', args);
|
|
170
|
-
return result?.count ?? 0;
|
|
171
|
-
},
|
|
172
|
-
insert: async (data) => {
|
|
173
|
-
return makeMutation('create', { data });
|
|
174
|
-
},
|
|
175
|
-
insertMany: async (items) => {
|
|
176
|
-
const results = [];
|
|
177
|
-
for (const data of items) {
|
|
178
|
-
const result = await makeMutation('create', { data });
|
|
179
|
-
results.push(result);
|
|
180
|
-
}
|
|
181
|
-
return results;
|
|
182
|
-
},
|
|
183
|
-
create: async (options) => {
|
|
184
|
-
return makeMutation('create', { data: options.data });
|
|
185
|
-
},
|
|
186
|
-
update: async (options) => {
|
|
187
|
-
const whereObj = options.where;
|
|
188
|
-
// Support both _id (standard) and id (legacy) in where clause
|
|
189
|
-
const id = whereObj?._id ?? whereObj?.id;
|
|
190
|
-
if (!id) {
|
|
191
|
-
throw new Error('Update requires an _id in the where clause');
|
|
192
|
-
}
|
|
193
|
-
const result = await makeMutation('update', { id, data: options.data });
|
|
194
|
-
return result?.rowsAffected ?? 0;
|
|
195
|
-
},
|
|
196
|
-
upsert: async (options) => {
|
|
197
|
-
const whereObj = options.where;
|
|
198
|
-
|
|
199
|
-
// Try to find existing record using the where clause
|
|
200
|
-
// This allows upserting on any field (e.g. clip_id, email, etc.)
|
|
201
|
-
// Use list with limit: 1 since findFirst doesn't exist on the server
|
|
202
|
-
const results = await makeRequest('list', { where: whereObj, limit: 1 }).catch(() => []);
|
|
203
|
-
const existing = results?.[0] ?? null;
|
|
204
|
-
|
|
205
|
-
if (existing) {
|
|
206
|
-
// Use _id from the found record for the update
|
|
207
|
-
const id = existing._id ?? existing.id;
|
|
208
|
-
if (!id) {
|
|
209
|
-
throw new Error('Found record has no _id or id field for update');
|
|
210
|
-
}
|
|
211
|
-
await makeMutation('update', { id, data: options.update });
|
|
212
|
-
return { ...existing, ...options.update };
|
|
213
|
-
} else {
|
|
214
|
-
return makeMutation('create', { data: options.create });
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
delete: async (options) => {
|
|
218
|
-
const whereObj = options.where;
|
|
219
|
-
// Support both _id (standard) and id (legacy) in where clause
|
|
220
|
-
const id = whereObj?._id ?? whereObj?.id;
|
|
221
|
-
if (id) {
|
|
222
|
-
const result = await makeMutation('delete', { id });
|
|
223
|
-
return result?.rowsAffected ?? 0;
|
|
224
|
-
}
|
|
225
|
-
// Compound where clause — use deleteMany
|
|
226
|
-
const result = await makeMutation('deleteMany', { where: whereObj });
|
|
227
|
-
return result?.rowsAffected ?? 0;
|
|
228
|
-
},
|
|
229
|
-
deleteById: async (id) => {
|
|
230
|
-
const result = await makeMutation('delete', { id });
|
|
231
|
-
return (result?.rowsAffected ?? 0) > 0;
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export default defineEventHandler(async (event) => {
|
|
239
|
-
const config = useRuntimeConfig();
|
|
240
|
-
verboseLogging = config.tether?.verbose || process.env.TETHER_VERBOSE === 'true';
|
|
241
|
-
|
|
242
|
-
// Get API key from runtime config (populated from TETHER_API_KEY env var)
|
|
243
|
-
const apiKey = config.tether?.apiKey || process.env.TETHER_API_KEY;
|
|
244
|
-
const url = config.tether?.url || process.env.TETHER_URL || 'https://tether-api.strands.gg';
|
|
245
|
-
const projectId = config.tether?.projectId || process.env.TETHER_PROJECT_ID;
|
|
246
|
-
|
|
247
|
-
if (!apiKey) {
|
|
248
|
-
throw createError({
|
|
249
|
-
statusCode: 500,
|
|
250
|
-
message: 'Tether API key not configured. Set TETHER_API_KEY environment variable.',
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (!projectId) {
|
|
255
|
-
throw createError({
|
|
256
|
-
statusCode: 500,
|
|
257
|
-
message: 'Tether project ID not configured.',
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const body = await readBody(event);
|
|
262
|
-
|
|
263
|
-
if (!body?.function) {
|
|
264
|
-
throw createError({
|
|
265
|
-
statusCode: 400,
|
|
266
|
-
message: 'Missing "function" in request body',
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Load function registry (once)
|
|
271
|
-
await loadFunctionRegistry();
|
|
272
|
-
|
|
273
|
-
// Try to find a custom function
|
|
274
|
-
const customFn = lookupFunction(body.function);
|
|
275
|
-
log.debug(`Query: ${body.function}, custom function found: ${!!customFn}`);
|
|
276
|
-
|
|
277
|
-
if (customFn) {
|
|
278
|
-
// Execute locally with database proxy
|
|
279
|
-
try {
|
|
280
|
-
const db = createDatabaseProxy(apiKey, url, projectId);
|
|
281
|
-
|
|
282
|
-
// Create auth context - try to get user identity from request
|
|
283
|
-
// The app should pass auth info via a custom header or we check for common auth patterns
|
|
284
|
-
let userIdentity = null;
|
|
285
|
-
|
|
286
|
-
// Check for Strands auth token in cookie or header
|
|
287
|
-
const authHeader = getHeader(event, 'authorization');
|
|
288
|
-
const strandsToken = getHeader(event, 'x-strands-token');
|
|
289
|
-
const cookieToken = getCookie(event, 'strands_oauth_token');
|
|
290
|
-
|
|
291
|
-
// If using Strands auth, validate the token via Tether server
|
|
292
|
-
if (strandsToken || cookieToken || authHeader?.startsWith('Bearer ')) {
|
|
293
|
-
const token = strandsToken || cookieToken || authHeader?.replace('Bearer ', '');
|
|
294
|
-
if (token) {
|
|
295
|
-
try {
|
|
296
|
-
// Validate token via Tether server (which checks if Strands Auth is enabled)
|
|
297
|
-
const authResponse = await fetch(`${url}/api/v1/projects/${projectId}/auth/validate`, {
|
|
298
|
-
method: 'POST',
|
|
299
|
-
headers: { 'Content-Type': 'application/json' },
|
|
300
|
-
body: JSON.stringify({ accessToken: token }),
|
|
27
|
+
catch (error) {
|
|
28
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
29
|
+
log.error(`Function ${body.function} failed:`, err.message);
|
|
30
|
+
throw createError({
|
|
31
|
+
statusCode: 500,
|
|
32
|
+
message: err.message || 'Function execution failed',
|
|
301
33
|
});
|
|
302
|
-
if (authResponse.ok) {
|
|
303
|
-
const authData = await authResponse.json();
|
|
304
|
-
if (authData.valid) {
|
|
305
|
-
userIdentity = {
|
|
306
|
-
subject: authData.userId,
|
|
307
|
-
email: authData.email,
|
|
308
|
-
name: authData.name,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
} catch (authError) {
|
|
313
|
-
log.warn('Auth validation failed:', authError.message);
|
|
314
|
-
// Auth validation failed - continue without identity
|
|
315
|
-
}
|
|
316
34
|
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const auth = {
|
|
320
|
-
getUserIdentity: async () => userIdentity,
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
const ctx = {
|
|
324
|
-
auth: {
|
|
325
|
-
userId: userIdentity?.subject ?? null,
|
|
326
|
-
claims: userIdentity ?? {},
|
|
327
|
-
},
|
|
328
|
-
userId: userIdentity?.subject ?? null,
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
log.debug(`Executing custom function: ${body.function}`);
|
|
332
|
-
const result = await customFn.handler({
|
|
333
|
-
db,
|
|
334
|
-
ctx,
|
|
335
|
-
auth,
|
|
336
|
-
args: body.args ?? {},
|
|
337
|
-
});
|
|
338
|
-
log.debug(`Function ${body.function} completed`);
|
|
339
|
-
|
|
340
|
-
return { data: result };
|
|
341
|
-
} catch (error) {
|
|
342
|
-
log.error(`Function ${body.function} failed:`, error.message || error);
|
|
343
|
-
throw createError({
|
|
344
|
-
statusCode: 500,
|
|
345
|
-
message: error.message || 'Function execution failed',
|
|
346
|
-
});
|
|
347
35
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// No custom function found - proxy to Tether API (generic CRUD)
|
|
351
|
-
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
352
|
-
method: 'POST',
|
|
353
|
-
headers: {
|
|
354
|
-
'Content-Type': 'application/json',
|
|
355
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
356
|
-
},
|
|
357
|
-
body: JSON.stringify({
|
|
358
|
-
function: body.function,
|
|
359
|
-
args: body.args,
|
|
360
|
-
}),
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
if (!response.ok) {
|
|
364
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
365
|
-
throw createError({
|
|
366
|
-
statusCode: response.status,
|
|
367
|
-
message: error.error || 'Query failed',
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return response.json();
|
|
36
|
+
return proxyToTetherApi(config, 'query', body.function, body.args);
|
|
372
37
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side query handler
|
|
3
|
+
* Executes local custom functions or proxies generic CRUD to Tether API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { defineEventHandler, readBody, createError } from 'h3';
|
|
7
|
+
import {
|
|
8
|
+
getConfig,
|
|
9
|
+
lookupFunction,
|
|
10
|
+
createDatabaseProxy,
|
|
11
|
+
buildAuthContext,
|
|
12
|
+
proxyToTetherApi,
|
|
13
|
+
log,
|
|
14
|
+
} from './utils/handler';
|
|
15
|
+
|
|
16
|
+
export default defineEventHandler(async (event) => {
|
|
17
|
+
const config = getConfig();
|
|
18
|
+
const body = await readBody(event);
|
|
19
|
+
|
|
20
|
+
if (!body?.function) {
|
|
21
|
+
throw createError({
|
|
22
|
+
statusCode: 400,
|
|
23
|
+
message: 'Missing "function" in request body',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const customFn = await lookupFunction(body.function);
|
|
28
|
+
log.debug(`Query: ${body.function}, custom function found: ${!!customFn}`);
|
|
29
|
+
|
|
30
|
+
if (customFn) {
|
|
31
|
+
try {
|
|
32
|
+
const db = createDatabaseProxy(config.apiKey, config.url, config.projectId);
|
|
33
|
+
const { auth, ctx } = await buildAuthContext(event, config.url, config.projectId);
|
|
34
|
+
|
|
35
|
+
log.debug(`Executing custom function: ${body.function}`);
|
|
36
|
+
const result = await customFn.handler({ db, ctx, auth, args: body.args ?? {} });
|
|
37
|
+
log.debug(`Function ${body.function} completed`);
|
|
38
|
+
|
|
39
|
+
return { data: result };
|
|
40
|
+
} catch (error: unknown) {
|
|
41
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
42
|
+
log.error(`Function ${body.function} failed:`, err.message);
|
|
43
|
+
throw createError({
|
|
44
|
+
statusCode: 500,
|
|
45
|
+
message: err.message || 'Function execution failed',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return proxyToTetherApi(config, 'query', body.function, body.args);
|
|
51
|
+
});
|