@tthr/vue 0.0.83 → 0.0.85
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 +7 -9
- package/nuxt/module.ts +7 -9
- package/nuxt/runtime/composables.ts +638 -0
- package/nuxt/runtime/plugin.client.ts +34 -0
- package/nuxt/runtime/server/mutation.post.ts +53 -0
- package/nuxt/runtime/server/plugins/cron.ts +377 -0
- package/nuxt/runtime/server/query.post.ts +51 -0
- package/nuxt/runtime/server/utils/handler.ts +375 -0
- package/nuxt/runtime/server/utils/{tether.js → tether.ts} +78 -29
- package/package.json +6 -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 -414
- 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 -21
- package/nuxt/runtime/plugin.client.js.map +0 -1
- package/nuxt/runtime/server/mutation.post.js +0 -373
- 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 +0 -621
- package/nuxt/runtime/server/plugins/cron.js.map +0 -1
- package/nuxt/runtime/server/query.post.js +0 -372
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared handler utilities for Tether server routes
|
|
3
|
+
*
|
|
4
|
+
* Provides function registry loading, database proxy creation,
|
|
5
|
+
* auth context building, and logging — used by both query and mutation handlers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createError, getHeader, getCookie, type H3Event } from 'h3';
|
|
9
|
+
import { useRuntimeConfig } from '#imports';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Logging
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
let verboseLogging = false;
|
|
16
|
+
const PREFIX = '[Tether]';
|
|
17
|
+
|
|
18
|
+
export const log = {
|
|
19
|
+
debug: (...args: unknown[]) => { if (verboseLogging) console.log(PREFIX, ...args); },
|
|
20
|
+
info: (...args: unknown[]) => console.log(PREFIX, ...args),
|
|
21
|
+
warn: (...args: unknown[]) => console.warn(PREFIX, ...args),
|
|
22
|
+
error: (...args: unknown[]) => console.error(PREFIX, ...args),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Config
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
export interface TetherServerConfig {
|
|
30
|
+
apiKey: string;
|
|
31
|
+
url: string;
|
|
32
|
+
projectId: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read and validate Tether config from runtime config + env vars.
|
|
37
|
+
* Throws H3 errors if required values are missing.
|
|
38
|
+
*/
|
|
39
|
+
export function getConfig(): TetherServerConfig {
|
|
40
|
+
const config = useRuntimeConfig();
|
|
41
|
+
verboseLogging = config.tether?.verbose || process.env.TETHER_VERBOSE === 'true';
|
|
42
|
+
|
|
43
|
+
const apiKey = config.tether?.apiKey || process.env.TETHER_API_KEY;
|
|
44
|
+
const url = config.tether?.url || process.env.TETHER_URL || 'https://tether-api.strands.gg';
|
|
45
|
+
const projectId = config.tether?.projectId || process.env.TETHER_PROJECT_ID;
|
|
46
|
+
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
throw createError({
|
|
49
|
+
statusCode: 500,
|
|
50
|
+
message: 'Tether API key not configured. Set TETHER_API_KEY environment variable.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!projectId) {
|
|
55
|
+
throw createError({
|
|
56
|
+
statusCode: 500,
|
|
57
|
+
message: 'Tether project ID not configured.',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { apiKey, url, projectId };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Function Registry
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
interface TetherFunction {
|
|
69
|
+
handler: (ctx: unknown) => Promise<unknown>;
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type FunctionRegistry = Record<string, Record<string, TetherFunction>>;
|
|
74
|
+
|
|
75
|
+
let functionRegistry: FunctionRegistry | null = null;
|
|
76
|
+
let registryError: Error | null = null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load the user's custom functions from ~/tether/functions (once).
|
|
80
|
+
*/
|
|
81
|
+
async function loadFunctionRegistry(): Promise<void> {
|
|
82
|
+
if (functionRegistry !== null || registryError !== null) return;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
log.debug('Loading custom functions from ~~/tether/functions/index.ts');
|
|
86
|
+
const functions = await import('~~/tether/functions/index.ts').catch((err: Error) => {
|
|
87
|
+
log.debug('Failed to import functions:', err.message);
|
|
88
|
+
return null;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (functions) {
|
|
92
|
+
log.debug('Loaded function modules:', Object.keys(functions));
|
|
93
|
+
functionRegistry = functions as FunctionRegistry;
|
|
94
|
+
} else {
|
|
95
|
+
log.debug('No custom functions found, will proxy all requests');
|
|
96
|
+
functionRegistry = {};
|
|
97
|
+
}
|
|
98
|
+
} catch (error: unknown) {
|
|
99
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
100
|
+
log.debug('Could not load custom functions:', err.message);
|
|
101
|
+
registryError = err;
|
|
102
|
+
functionRegistry = {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Look up a function by name (e.g. "channel.getMyChannels").
|
|
108
|
+
* Returns null if no custom function is registered.
|
|
109
|
+
*/
|
|
110
|
+
export async function lookupFunction(name: string): Promise<TetherFunction | null> {
|
|
111
|
+
await loadFunctionRegistry();
|
|
112
|
+
|
|
113
|
+
if (!functionRegistry || !name) return null;
|
|
114
|
+
|
|
115
|
+
const parts = name.split('.');
|
|
116
|
+
if (parts.length !== 2) return null;
|
|
117
|
+
|
|
118
|
+
const [moduleName, fnName] = parts;
|
|
119
|
+
const mod = functionRegistry[moduleName];
|
|
120
|
+
if (!mod) return null;
|
|
121
|
+
|
|
122
|
+
const fn = mod[fnName];
|
|
123
|
+
if (fn && typeof fn === 'object' && typeof fn.handler === 'function') {
|
|
124
|
+
return fn;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Database Proxy
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a database proxy that routes calls to Tether's CRUD endpoints.
|
|
136
|
+
*/
|
|
137
|
+
export function createDatabaseProxy(apiKey: string, url: string, projectId: string) {
|
|
138
|
+
return new Proxy({} as Record<string, unknown>, {
|
|
139
|
+
get(_target, tableName: string) {
|
|
140
|
+
const makeRequest = async (operation: string, args: Record<string, unknown>) => {
|
|
141
|
+
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({
|
|
148
|
+
function: `${tableName}.${operation}`,
|
|
149
|
+
args,
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
155
|
+
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = await response.json();
|
|
159
|
+
return result.data;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const makeMutation = async (operation: string, args: Record<string, unknown>) => {
|
|
163
|
+
const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: {
|
|
166
|
+
'Content-Type': 'application/json',
|
|
167
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
168
|
+
},
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
function: `${tableName}.${operation}`,
|
|
171
|
+
args,
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
177
|
+
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result = await response.json();
|
|
181
|
+
return result.data;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
findMany: async (options?: { where?: Record<string, unknown>; limit?: number; offset?: number; orderBy?: string | Record<string, string>; orderDir?: string }) => {
|
|
186
|
+
const args: Record<string, unknown> = {};
|
|
187
|
+
if (options?.where) args.where = options.where;
|
|
188
|
+
if (options?.limit) args.limit = options.limit;
|
|
189
|
+
if (options?.offset) args.offset = options.offset;
|
|
190
|
+
if (options?.orderBy) {
|
|
191
|
+
if (typeof options.orderBy === 'string') {
|
|
192
|
+
args.orderBy = options.orderBy;
|
|
193
|
+
} else if (typeof options.orderBy === 'object') {
|
|
194
|
+
const [column, dir] = Object.entries(options.orderBy)[0] || [];
|
|
195
|
+
if (column) {
|
|
196
|
+
args.orderBy = column;
|
|
197
|
+
args.orderDir = dir?.toUpperCase?.() || 'ASC';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (options?.orderDir) args.orderDir = options.orderDir;
|
|
202
|
+
return makeRequest('list', args);
|
|
203
|
+
},
|
|
204
|
+
findFirst: async (options?: { where?: Record<string, unknown> }) => {
|
|
205
|
+
const args: Record<string, unknown> = { limit: 1 };
|
|
206
|
+
if (options?.where) args.where = options.where;
|
|
207
|
+
const results = await makeRequest('list', args);
|
|
208
|
+
return results?.[0] ?? null;
|
|
209
|
+
},
|
|
210
|
+
findUnique: async (options?: { where?: Record<string, unknown> }) => {
|
|
211
|
+
const args: Record<string, unknown> = { limit: 1 };
|
|
212
|
+
if (options?.where) args.where = options.where;
|
|
213
|
+
const results = await makeRequest('list', args);
|
|
214
|
+
return results?.[0] ?? null;
|
|
215
|
+
},
|
|
216
|
+
findById: async (id: unknown) => {
|
|
217
|
+
return makeRequest('get', { id });
|
|
218
|
+
},
|
|
219
|
+
count: async (options?: { where?: Record<string, unknown> }) => {
|
|
220
|
+
const args: Record<string, unknown> = {};
|
|
221
|
+
if (options?.where) args.where = options.where;
|
|
222
|
+
const result = await makeRequest('count', args);
|
|
223
|
+
return result?.count ?? 0;
|
|
224
|
+
},
|
|
225
|
+
insert: async (data: unknown) => {
|
|
226
|
+
return makeMutation('create', { data });
|
|
227
|
+
},
|
|
228
|
+
insertMany: async (items: unknown[]) => {
|
|
229
|
+
const results: unknown[] = [];
|
|
230
|
+
for (const data of items) {
|
|
231
|
+
const result = await makeMutation('create', { data });
|
|
232
|
+
results.push(result);
|
|
233
|
+
}
|
|
234
|
+
return results;
|
|
235
|
+
},
|
|
236
|
+
create: async (options: { data: unknown }) => {
|
|
237
|
+
return makeMutation('create', { data: options.data });
|
|
238
|
+
},
|
|
239
|
+
update: async (options: { where: Record<string, unknown>; data: unknown }) => {
|
|
240
|
+
const id = options.where?._id ?? options.where?.id;
|
|
241
|
+
if (!id) {
|
|
242
|
+
throw new Error('Update requires an _id in the where clause');
|
|
243
|
+
}
|
|
244
|
+
const result = await makeMutation('update', { id, data: options.data });
|
|
245
|
+
return result?.rowsAffected ?? 0;
|
|
246
|
+
},
|
|
247
|
+
upsert: async (options: { where: Record<string, unknown>; create: unknown; update: unknown }) => {
|
|
248
|
+
const results = await makeRequest('list', { where: options.where, limit: 1 }).catch(() => []);
|
|
249
|
+
const existing = results?.[0] ?? null;
|
|
250
|
+
|
|
251
|
+
if (existing) {
|
|
252
|
+
const id = existing._id ?? existing.id;
|
|
253
|
+
if (!id) {
|
|
254
|
+
throw new Error('Found record has no _id or id field for update');
|
|
255
|
+
}
|
|
256
|
+
await makeMutation('update', { id, data: options.update });
|
|
257
|
+
return { ...existing, ...options.update };
|
|
258
|
+
} else {
|
|
259
|
+
return makeMutation('create', { data: options.create });
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
delete: async (options: { where: Record<string, unknown> }) => {
|
|
263
|
+
const id = options.where?._id ?? options.where?.id;
|
|
264
|
+
if (id) {
|
|
265
|
+
const result = await makeMutation('delete', { id });
|
|
266
|
+
return result?.rowsAffected ?? 0;
|
|
267
|
+
}
|
|
268
|
+
const result = await makeMutation('deleteMany', { where: options.where });
|
|
269
|
+
return result?.rowsAffected ?? 0;
|
|
270
|
+
},
|
|
271
|
+
deleteById: async (id: unknown) => {
|
|
272
|
+
const result = await makeMutation('delete', { id });
|
|
273
|
+
return (result?.rowsAffected ?? 0) > 0;
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// Auth Context
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
interface UserIdentity {
|
|
285
|
+
subject: string;
|
|
286
|
+
email?: string;
|
|
287
|
+
name?: string;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Build auth context by extracting and validating tokens from the request.
|
|
292
|
+
*/
|
|
293
|
+
export async function buildAuthContext(event: H3Event, url: string, projectId: string) {
|
|
294
|
+
let userIdentity: UserIdentity | null = null;
|
|
295
|
+
|
|
296
|
+
const authHeader = getHeader(event, 'authorization');
|
|
297
|
+
const strandsToken = getHeader(event, 'x-strands-token');
|
|
298
|
+
const cookieToken = getCookie(event, 'strands_oauth_token');
|
|
299
|
+
|
|
300
|
+
if (strandsToken || cookieToken || authHeader?.startsWith('Bearer ')) {
|
|
301
|
+
const token = strandsToken || cookieToken || authHeader?.replace('Bearer ', '');
|
|
302
|
+
if (token) {
|
|
303
|
+
try {
|
|
304
|
+
const authResponse = await fetch(`${url}/api/v1/projects/${projectId}/auth/validate`, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
headers: { 'Content-Type': 'application/json' },
|
|
307
|
+
body: JSON.stringify({ accessToken: token }),
|
|
308
|
+
});
|
|
309
|
+
if (authResponse.ok) {
|
|
310
|
+
const authData = await authResponse.json();
|
|
311
|
+
if (authData.valid) {
|
|
312
|
+
userIdentity = {
|
|
313
|
+
subject: authData.userId,
|
|
314
|
+
email: authData.email,
|
|
315
|
+
name: authData.name,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} catch (authError: unknown) {
|
|
320
|
+
const err = authError instanceof Error ? authError : new Error(String(authError));
|
|
321
|
+
log.warn('Auth validation failed:', err.message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const auth = {
|
|
327
|
+
getUserIdentity: async () => userIdentity,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const ctx = {
|
|
331
|
+
auth: {
|
|
332
|
+
userId: userIdentity?.subject ?? null,
|
|
333
|
+
claims: userIdentity ?? {},
|
|
334
|
+
},
|
|
335
|
+
userId: userIdentity?.subject ?? null,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
return { auth, ctx };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Proxy to Tether API
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Proxy a request directly to the Tether API (for generic CRUD when no custom function exists).
|
|
347
|
+
*/
|
|
348
|
+
export async function proxyToTetherApi(
|
|
349
|
+
config: TetherServerConfig,
|
|
350
|
+
endpoint: 'query' | 'mutation',
|
|
351
|
+
functionName: string,
|
|
352
|
+
args: unknown
|
|
353
|
+
) {
|
|
354
|
+
const response = await fetch(`${config.url}/api/v1/projects/${config.projectId}/${endpoint}`, {
|
|
355
|
+
method: 'POST',
|
|
356
|
+
headers: {
|
|
357
|
+
'Content-Type': 'application/json',
|
|
358
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
359
|
+
},
|
|
360
|
+
body: JSON.stringify({
|
|
361
|
+
function: functionName,
|
|
362
|
+
args,
|
|
363
|
+
}),
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
368
|
+
throw createError({
|
|
369
|
+
statusCode: response.status,
|
|
370
|
+
message: error.error || `${endpoint === 'query' ? 'Query' : 'Mutation'} failed`,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return response.json();
|
|
375
|
+
}
|
|
@@ -20,13 +20,77 @@
|
|
|
20
20
|
|
|
21
21
|
import { createError } from 'h3';
|
|
22
22
|
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
interface AssetMetadata {
|
|
28
|
+
id: string;
|
|
29
|
+
filename: string;
|
|
30
|
+
contentType: string;
|
|
31
|
+
size: number;
|
|
32
|
+
sha256: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
metadata: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface UploadUrlOptions {
|
|
38
|
+
filename: string;
|
|
39
|
+
contentType: string;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ListAssetsOptions {
|
|
44
|
+
limit?: number;
|
|
45
|
+
offset?: number;
|
|
46
|
+
contentType?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface TetherServerClient {
|
|
50
|
+
query: (name: string, args?: unknown) => Promise<unknown>;
|
|
51
|
+
mutation: (name: string, args?: unknown) => Promise<unknown>;
|
|
52
|
+
storage: {
|
|
53
|
+
generateUploadUrl: (options: UploadUrlOptions) => Promise<{ assetId: string; uploadUrl: string; expiresAt: string }>;
|
|
54
|
+
confirmUpload: (assetId: string, sha256?: string) => Promise<AssetMetadata>;
|
|
55
|
+
getUrl: (assetId: string) => Promise<string>;
|
|
56
|
+
getMetadata: (assetId: string) => Promise<AssetMetadata>;
|
|
57
|
+
delete: (assetId: string) => Promise<void>;
|
|
58
|
+
list: (options?: ListAssetsOptions) => Promise<{ assets: AssetMetadata[]; totalCount: number }>;
|
|
59
|
+
};
|
|
60
|
+
projectId: string;
|
|
61
|
+
url: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Helpers
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map snake_case API response to camelCase
|
|
70
|
+
*/
|
|
71
|
+
function mapAssetMetadata(asset: Record<string, unknown>): AssetMetadata {
|
|
72
|
+
return {
|
|
73
|
+
id: asset.id as string,
|
|
74
|
+
filename: asset.filename as string,
|
|
75
|
+
contentType: asset.content_type as string,
|
|
76
|
+
size: asset.size as number,
|
|
77
|
+
sha256: asset.sha256 as string,
|
|
78
|
+
createdAt: asset.created_at as string,
|
|
79
|
+
metadata: (asset.metadata as Record<string, unknown>) ?? {},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Main Export
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
23
87
|
/**
|
|
24
88
|
* Get a Tether client for use in server-side code
|
|
25
89
|
*
|
|
26
|
-
* @param
|
|
90
|
+
* @param _event - The H3 event (optional, used for request context)
|
|
27
91
|
* @returns A Tether client with query, mutation, and storage methods
|
|
28
92
|
*/
|
|
29
|
-
export function useTetherServer(_event) {
|
|
93
|
+
export function useTetherServer(_event?: unknown): TetherServerClient {
|
|
30
94
|
// Use process.env directly to avoid #imports dependency
|
|
31
95
|
// The Nuxt module sets NUXT_TETHER_* vars from runtime config
|
|
32
96
|
const apiKey = process.env.NUXT_TETHER_API_KEY || process.env.TETHER_API_KEY;
|
|
@@ -52,7 +116,7 @@ export function useTetherServer(_event) {
|
|
|
52
116
|
'Authorization': `Bearer ${apiKey}`,
|
|
53
117
|
};
|
|
54
118
|
|
|
55
|
-
async function query(name, args) {
|
|
119
|
+
async function query(name: string, args?: unknown): Promise<unknown> {
|
|
56
120
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
57
121
|
method: 'POST',
|
|
58
122
|
headers,
|
|
@@ -71,7 +135,7 @@ export function useTetherServer(_event) {
|
|
|
71
135
|
return result.data;
|
|
72
136
|
}
|
|
73
137
|
|
|
74
|
-
async function mutation(name, args) {
|
|
138
|
+
async function mutation(name: string, args?: unknown): Promise<unknown> {
|
|
75
139
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
|
|
76
140
|
method: 'POST',
|
|
77
141
|
headers,
|
|
@@ -91,7 +155,7 @@ export function useTetherServer(_event) {
|
|
|
91
155
|
}
|
|
92
156
|
|
|
93
157
|
const storage = {
|
|
94
|
-
async generateUploadUrl(options) {
|
|
158
|
+
async generateUploadUrl(options: UploadUrlOptions) {
|
|
95
159
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/assets/upload-url`, {
|
|
96
160
|
method: 'POST',
|
|
97
161
|
headers,
|
|
@@ -112,13 +176,13 @@ export function useTetherServer(_event) {
|
|
|
112
176
|
|
|
113
177
|
const result = await response.json();
|
|
114
178
|
return {
|
|
115
|
-
assetId: result.asset_id,
|
|
116
|
-
uploadUrl: result.upload_url,
|
|
117
|
-
expiresAt: result.expires_at,
|
|
179
|
+
assetId: result.asset_id as string,
|
|
180
|
+
uploadUrl: result.upload_url as string,
|
|
181
|
+
expiresAt: result.expires_at as string,
|
|
118
182
|
};
|
|
119
183
|
},
|
|
120
184
|
|
|
121
|
-
async confirmUpload(assetId, sha256) {
|
|
185
|
+
async confirmUpload(assetId: string, sha256?: string) {
|
|
122
186
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/assets/${assetId}/complete`, {
|
|
123
187
|
method: 'POST',
|
|
124
188
|
headers,
|
|
@@ -137,7 +201,7 @@ export function useTetherServer(_event) {
|
|
|
137
201
|
return mapAssetMetadata(result.asset);
|
|
138
202
|
},
|
|
139
203
|
|
|
140
|
-
async getUrl(assetId) {
|
|
204
|
+
async getUrl(assetId: string): Promise<string> {
|
|
141
205
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/assets/${assetId}/url`, {
|
|
142
206
|
method: 'GET',
|
|
143
207
|
headers,
|
|
@@ -155,7 +219,7 @@ export function useTetherServer(_event) {
|
|
|
155
219
|
return result.url;
|
|
156
220
|
},
|
|
157
221
|
|
|
158
|
-
async getMetadata(assetId) {
|
|
222
|
+
async getMetadata(assetId: string): Promise<AssetMetadata> {
|
|
159
223
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/assets/${assetId}`, {
|
|
160
224
|
method: 'GET',
|
|
161
225
|
headers,
|
|
@@ -173,7 +237,7 @@ export function useTetherServer(_event) {
|
|
|
173
237
|
return mapAssetMetadata(result.asset);
|
|
174
238
|
},
|
|
175
239
|
|
|
176
|
-
async delete(assetId) {
|
|
240
|
+
async delete(assetId: string): Promise<void> {
|
|
177
241
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/assets/${assetId}`, {
|
|
178
242
|
method: 'DELETE',
|
|
179
243
|
headers,
|
|
@@ -188,7 +252,7 @@ export function useTetherServer(_event) {
|
|
|
188
252
|
}
|
|
189
253
|
},
|
|
190
254
|
|
|
191
|
-
async list(options) {
|
|
255
|
+
async list(options?: ListAssetsOptions) {
|
|
192
256
|
const params = new URLSearchParams();
|
|
193
257
|
if (options?.limit) params.set('limit', String(options.limit));
|
|
194
258
|
if (options?.offset) params.set('offset', String(options.offset));
|
|
@@ -213,7 +277,7 @@ export function useTetherServer(_event) {
|
|
|
213
277
|
const result = await response.json();
|
|
214
278
|
return {
|
|
215
279
|
assets: result.assets.map(mapAssetMetadata),
|
|
216
|
-
totalCount: result.total_count,
|
|
280
|
+
totalCount: result.total_count as number,
|
|
217
281
|
};
|
|
218
282
|
},
|
|
219
283
|
};
|
|
@@ -226,18 +290,3 @@ export function useTetherServer(_event) {
|
|
|
226
290
|
url,
|
|
227
291
|
};
|
|
228
292
|
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Map snake_case API response to camelCase
|
|
232
|
-
*/
|
|
233
|
-
function mapAssetMetadata(asset) {
|
|
234
|
-
return {
|
|
235
|
-
id: asset.id,
|
|
236
|
-
filename: asset.filename,
|
|
237
|
-
contentType: asset.content_type,
|
|
238
|
-
size: asset.size,
|
|
239
|
-
sha256: asset.sha256,
|
|
240
|
-
createdAt: asset.created_at,
|
|
241
|
-
metadata: asset.metadata,
|
|
242
|
-
};
|
|
243
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tthr/vue",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.85",
|
|
4
4
|
"description": "Tether Vue/Nuxt SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"./nuxt": {
|
|
18
18
|
"import": "./nuxt/module.ts"
|
|
19
19
|
},
|
|
20
|
-
"./nuxt/runtime/server/plugins/cron
|
|
21
|
-
"import": "./nuxt/runtime/server/plugins/cron.
|
|
20
|
+
"./nuxt/runtime/server/plugins/cron": {
|
|
21
|
+
"import": "./nuxt/runtime/server/plugins/cron.ts"
|
|
22
22
|
},
|
|
23
|
-
"./nuxt/runtime/server/utils/tether
|
|
24
|
-
"import": "./nuxt/runtime/server/utils/tether.
|
|
23
|
+
"./nuxt/runtime/server/utils/tether": {
|
|
24
|
+
"import": "./nuxt/runtime/server/utils/tether.ts"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
@@ -29,10 +29,7 @@
|
|
|
29
29
|
"nuxt/module.ts",
|
|
30
30
|
"nuxt/module.js",
|
|
31
31
|
"nuxt/module.js.map",
|
|
32
|
-
"nuxt/runtime/**/*.
|
|
33
|
-
"nuxt/runtime/**/*.js.map",
|
|
34
|
-
"nuxt/runtime/**/*.d.ts",
|
|
35
|
-
"nuxt/runtime/**/*.d.ts.map",
|
|
32
|
+
"nuxt/runtime/**/*.ts",
|
|
36
33
|
"nuxt/runtime/**/*.vue"
|
|
37
34
|
],
|
|
38
35
|
"dependencies": {
|
package/dist/nuxt.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @tthr/vue/nuxt - Tether Nuxt Module
|
|
3
|
-
*
|
|
4
|
-
* Automatically configures Tether for Nuxt applications.
|
|
5
|
-
*/
|
|
6
|
-
export interface TetherModuleOptions {
|
|
7
|
-
/** Project ID from Tether dashboard */
|
|
8
|
-
projectId?: string;
|
|
9
|
-
/** API endpoint (defaults to Tether Cloud) */
|
|
10
|
-
url?: string;
|
|
11
|
-
}
|
|
12
|
-
declare const _default: import("@nuxt/schema").NuxtModule<TetherModuleOptions, TetherModuleOptions, false>;
|
|
13
|
-
export default _default;
|
|
14
|
-
//# sourceMappingURL=nuxt.d.ts.map
|
package/dist/nuxt.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nuxt.d.ts","sourceRoot":"","sources":["../src/nuxt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;;AAED,wBA2CG"}
|
package/dist/nuxt.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @tthr/vue/nuxt - Tether Nuxt Module
|
|
3
|
-
*
|
|
4
|
-
* Automatically configures Tether for Nuxt applications.
|
|
5
|
-
*/
|
|
6
|
-
import { defineNuxtModule, addPlugin, createResolver, addImports } from '@nuxt/kit';
|
|
7
|
-
export default defineNuxtModule({
|
|
8
|
-
meta: {
|
|
9
|
-
name: '@tthr/vue',
|
|
10
|
-
configKey: 'tether',
|
|
11
|
-
compatibility: {
|
|
12
|
-
nuxt: '>=3.0.0',
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
defaults: {
|
|
16
|
-
projectId: undefined,
|
|
17
|
-
url: undefined,
|
|
18
|
-
},
|
|
19
|
-
setup(options, nuxt) {
|
|
20
|
-
const resolver = createResolver(import.meta.url);
|
|
21
|
-
// Add runtime config
|
|
22
|
-
nuxt.options.runtimeConfig.public.tether = {
|
|
23
|
-
projectId: options.projectId || '',
|
|
24
|
-
url: options.url || 'https://api.tether.strands.gg',
|
|
25
|
-
};
|
|
26
|
-
// Add the plugin that initialises Tether
|
|
27
|
-
addPlugin({
|
|
28
|
-
src: resolver.resolve('./runtime/plugin'),
|
|
29
|
-
mode: 'client',
|
|
30
|
-
});
|
|
31
|
-
// Auto-import composables
|
|
32
|
-
addImports([
|
|
33
|
-
{
|
|
34
|
-
name: 'useQuery',
|
|
35
|
-
from: resolver.resolve('./runtime/composables'),
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'useMutation',
|
|
39
|
-
from: resolver.resolve('./runtime/composables'),
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
name: 'useTether',
|
|
43
|
-
from: resolver.resolve('./runtime/composables'),
|
|
44
|
-
},
|
|
45
|
-
]);
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
//# sourceMappingURL=nuxt.js.map
|
package/dist/nuxt.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nuxt.js","sourceRoot":"","sources":["../src/nuxt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AASpF,eAAe,gBAAgB,CAAsB;IACnD,IAAI,EAAE;QACJ,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE;YACb,IAAI,EAAE,SAAS;SAChB;KACF;IACD,QAAQ,EAAE;QACR,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,SAAS;KACf;IACD,KAAK,CAAC,OAAO,EAAE,IAAI;QACjB,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEjD,qBAAqB;QACrB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,GAAG;YACzC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE;YAClC,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,+BAA+B;SACpD,CAAC;QAEF,yCAAyC;QACzC,SAAS,CAAC;YACR,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,kBAAkB,CAAC;YACzC,IAAI,EAAE,QAAQ;SACf,CAAC,CAAC;QAEH,0BAA0B;QAC1B,UAAU,CAAC;YACT;gBACE,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC;aAChD;YACD;gBACE,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC;aAChD;YACD;gBACE,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC;aAChD;SACF,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
|