tthr 0.0.27 → 0.0.29
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/dist/index.js +141 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167,14 +167,16 @@ async function deploySchemaToServer(projectId, token, schemaPath, dryRun) {
|
|
|
167
167
|
const schemaSource = await fs3.readFile(schemaPath, "utf-8");
|
|
168
168
|
const tables = parseSchema(schemaSource);
|
|
169
169
|
spinner.text = `Found ${tables.length} table(s)`;
|
|
170
|
+
const sql = generateSchemaSQL(tables);
|
|
170
171
|
if (dryRun) {
|
|
171
172
|
spinner.info("Dry run - would deploy schema:");
|
|
172
173
|
for (const table of tables) {
|
|
173
|
-
console.log(chalk2.dim(` - ${table.name}`));
|
|
174
|
+
console.log(chalk2.dim(` - ${table.name} (${Object.keys(table.columns).length} columns)`));
|
|
174
175
|
}
|
|
176
|
+
console.log(chalk2.bold("\nGenerated SQL:\n"));
|
|
177
|
+
console.log(chalk2.cyan(sql));
|
|
175
178
|
return;
|
|
176
179
|
}
|
|
177
|
-
const sql = generateSchemaSQL(tables);
|
|
178
180
|
spinner.text = "Deploying schema...";
|
|
179
181
|
const response = await fetch(`${API_URL}/projects/${projectId}/deploy/schema`, {
|
|
180
182
|
method: "POST",
|
|
@@ -333,54 +335,63 @@ function parseSchema(source) {
|
|
|
333
335
|
}
|
|
334
336
|
return tables;
|
|
335
337
|
}
|
|
338
|
+
function getColumnSqlType(type) {
|
|
339
|
+
switch (type) {
|
|
340
|
+
case "text":
|
|
341
|
+
return "TEXT";
|
|
342
|
+
case "integer":
|
|
343
|
+
return "INTEGER";
|
|
344
|
+
case "real":
|
|
345
|
+
return "REAL";
|
|
346
|
+
case "blob":
|
|
347
|
+
return "BLOB";
|
|
348
|
+
case "timestamp":
|
|
349
|
+
return "TEXT";
|
|
350
|
+
case "boolean":
|
|
351
|
+
return "INTEGER";
|
|
352
|
+
case "json":
|
|
353
|
+
return "TEXT";
|
|
354
|
+
default:
|
|
355
|
+
return "TEXT";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function buildColumnSql(colName, def, forAlterTable = false) {
|
|
359
|
+
const sqlType = getColumnSqlType(def.type);
|
|
360
|
+
let colSql = `${colName} ${sqlType}`;
|
|
361
|
+
if (def.primaryKey) colSql += " PRIMARY KEY";
|
|
362
|
+
if (def.notNull && !forAlterTable) colSql += " NOT NULL";
|
|
363
|
+
if (def.unique) colSql += " UNIQUE";
|
|
364
|
+
if (def.hasDefault && def.type === "timestamp") {
|
|
365
|
+
colSql += " DEFAULT (datetime('now'))";
|
|
366
|
+
}
|
|
367
|
+
if (def.references) {
|
|
368
|
+
const [refTable, refCol] = def.references.split(".");
|
|
369
|
+
colSql += ` REFERENCES ${refTable}(${refCol})`;
|
|
370
|
+
}
|
|
371
|
+
return colSql;
|
|
372
|
+
}
|
|
336
373
|
function generateSchemaSQL(tables) {
|
|
337
374
|
const statements = [];
|
|
338
375
|
for (const table of tables) {
|
|
339
376
|
const columnDefs = [];
|
|
340
377
|
for (const [colName, colDef] of Object.entries(table.columns)) {
|
|
341
378
|
const def = colDef;
|
|
342
|
-
|
|
343
|
-
switch (def.type) {
|
|
344
|
-
case "text":
|
|
345
|
-
sqlType = "TEXT";
|
|
346
|
-
break;
|
|
347
|
-
case "integer":
|
|
348
|
-
sqlType = "INTEGER";
|
|
349
|
-
break;
|
|
350
|
-
case "real":
|
|
351
|
-
sqlType = "REAL";
|
|
352
|
-
break;
|
|
353
|
-
case "blob":
|
|
354
|
-
sqlType = "BLOB";
|
|
355
|
-
break;
|
|
356
|
-
case "timestamp":
|
|
357
|
-
sqlType = "TEXT";
|
|
358
|
-
break;
|
|
359
|
-
case "boolean":
|
|
360
|
-
sqlType = "INTEGER";
|
|
361
|
-
break;
|
|
362
|
-
case "json":
|
|
363
|
-
sqlType = "TEXT";
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
let colSql = `${colName} ${sqlType}`;
|
|
367
|
-
if (def.primaryKey) colSql += " PRIMARY KEY";
|
|
368
|
-
if (def.notNull) colSql += " NOT NULL";
|
|
369
|
-
if (def.unique) colSql += " UNIQUE";
|
|
370
|
-
if (def.hasDefault && def.type === "timestamp") {
|
|
371
|
-
colSql += " DEFAULT (datetime('now'))";
|
|
372
|
-
}
|
|
373
|
-
if (def.references) {
|
|
374
|
-
const [refTable, refCol] = def.references.split(".");
|
|
375
|
-
colSql += ` REFERENCES ${refTable}(${refCol})`;
|
|
376
|
-
}
|
|
377
|
-
columnDefs.push(colSql);
|
|
379
|
+
columnDefs.push(buildColumnSql(colName, def));
|
|
378
380
|
}
|
|
379
381
|
statements.push(
|
|
380
382
|
`CREATE TABLE IF NOT EXISTS ${table.name} (
|
|
381
383
|
${columnDefs.join(",\n ")}
|
|
382
384
|
);`
|
|
383
385
|
);
|
|
386
|
+
for (const [colName, colDef] of Object.entries(table.columns)) {
|
|
387
|
+
const def = colDef;
|
|
388
|
+
if (def.primaryKey) continue;
|
|
389
|
+
const alterColSql = buildColumnSql(colName, def, true);
|
|
390
|
+
statements.push(
|
|
391
|
+
`-- Add column if missing (will error if exists, which is OK)
|
|
392
|
+
ALTER TABLE ${table.name} ADD COLUMN ${alterColSql};`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
384
395
|
}
|
|
385
396
|
return statements.join("\n\n");
|
|
386
397
|
}
|
|
@@ -817,7 +828,7 @@ export const create = mutation({
|
|
|
817
828
|
id,
|
|
818
829
|
title: args.title,
|
|
819
830
|
content: args.content ?? '',
|
|
820
|
-
authorId: ctx.userId,
|
|
831
|
+
authorId: ctx.userId ?? 'anonymous',
|
|
821
832
|
createdAt: now,
|
|
822
833
|
updatedAt: now,
|
|
823
834
|
},
|
|
@@ -857,6 +868,15 @@ export const remove = mutation({
|
|
|
857
868
|
});
|
|
858
869
|
},
|
|
859
870
|
});
|
|
871
|
+
`
|
|
872
|
+
);
|
|
873
|
+
await fs4.writeFile(
|
|
874
|
+
path4.join(projectPath, "tether", "functions", "index.ts"),
|
|
875
|
+
`// Re-export all function modules
|
|
876
|
+
// The Tether SDK uses this file to discover custom functions
|
|
877
|
+
|
|
878
|
+
export * as posts from './posts';
|
|
879
|
+
export * as comments from './comments';
|
|
860
880
|
`
|
|
861
881
|
);
|
|
862
882
|
await fs4.writeFile(
|
|
@@ -906,7 +926,7 @@ export const create = mutation({
|
|
|
906
926
|
id,
|
|
907
927
|
postId: args.postId,
|
|
908
928
|
content: args.content,
|
|
909
|
-
authorId: ctx.userId,
|
|
929
|
+
authorId: ctx.userId ?? 'anonymous',
|
|
910
930
|
createdAt: now,
|
|
911
931
|
},
|
|
912
932
|
});
|
|
@@ -1087,8 +1107,8 @@ PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
|
1087
1107
|
}
|
|
1088
1108
|
}
|
|
1089
1109
|
}
|
|
1090
|
-
function getInstallCommand(pm,
|
|
1091
|
-
const devFlag =
|
|
1110
|
+
function getInstallCommand(pm, isDev5 = false) {
|
|
1111
|
+
const devFlag = isDev5 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
|
|
1092
1112
|
return `${pm} ${pm === "npm" ? "install" : "add"} ${devFlag}`.trim();
|
|
1093
1113
|
}
|
|
1094
1114
|
async function installTetherPackages(projectPath, template, packageManager) {
|
|
@@ -2144,18 +2164,18 @@ function detectPackageManager() {
|
|
|
2144
2164
|
}
|
|
2145
2165
|
return "npm";
|
|
2146
2166
|
}
|
|
2147
|
-
function getInstallCommand2(pm, packages,
|
|
2167
|
+
function getInstallCommand2(pm, packages, isDev5) {
|
|
2148
2168
|
const packagesStr = packages.join(" ");
|
|
2149
2169
|
switch (pm) {
|
|
2150
2170
|
case "bun":
|
|
2151
|
-
return
|
|
2171
|
+
return isDev5 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
|
|
2152
2172
|
case "pnpm":
|
|
2153
|
-
return
|
|
2173
|
+
return isDev5 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
|
|
2154
2174
|
case "yarn":
|
|
2155
|
-
return
|
|
2175
|
+
return isDev5 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
|
|
2156
2176
|
case "npm":
|
|
2157
2177
|
default:
|
|
2158
|
-
return
|
|
2178
|
+
return isDev5 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
|
|
2159
2179
|
}
|
|
2160
2180
|
}
|
|
2161
2181
|
async function getLatestVersion2(packageName) {
|
|
@@ -2255,6 +2275,79 @@ async function updateCommand(options) {
|
|
|
2255
2275
|
}
|
|
2256
2276
|
}
|
|
2257
2277
|
|
|
2278
|
+
// src/commands/exec.ts
|
|
2279
|
+
import chalk9 from "chalk";
|
|
2280
|
+
import ora8 from "ora";
|
|
2281
|
+
import fs9 from "fs-extra";
|
|
2282
|
+
import path9 from "path";
|
|
2283
|
+
var isDev4 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
2284
|
+
var API_URL4 = isDev4 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
2285
|
+
async function execCommand(sql) {
|
|
2286
|
+
if (!sql || sql.trim() === "") {
|
|
2287
|
+
console.log(chalk9.red("\nError: SQL query required"));
|
|
2288
|
+
console.log(chalk9.dim('Usage: tthr exec "SELECT * FROM table_name"\n'));
|
|
2289
|
+
process.exit(1);
|
|
2290
|
+
}
|
|
2291
|
+
const credentials = await requireAuth();
|
|
2292
|
+
const envPath = path9.resolve(process.cwd(), ".env");
|
|
2293
|
+
let projectId;
|
|
2294
|
+
if (await fs9.pathExists(envPath)) {
|
|
2295
|
+
const envContent = await fs9.readFile(envPath, "utf-8");
|
|
2296
|
+
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
2297
|
+
projectId = match?.[1]?.trim();
|
|
2298
|
+
}
|
|
2299
|
+
if (!projectId) {
|
|
2300
|
+
console.log(chalk9.red("\nError: Project ID not found"));
|
|
2301
|
+
console.log(chalk9.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
2302
|
+
process.exit(1);
|
|
2303
|
+
}
|
|
2304
|
+
const spinner = ora8("Executing query...").start();
|
|
2305
|
+
try {
|
|
2306
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/exec`, {
|
|
2307
|
+
method: "POST",
|
|
2308
|
+
headers: {
|
|
2309
|
+
"Content-Type": "application/json",
|
|
2310
|
+
"Authorization": `Bearer ${credentials.accessToken}`
|
|
2311
|
+
},
|
|
2312
|
+
body: JSON.stringify({ sql })
|
|
2313
|
+
});
|
|
2314
|
+
if (!response.ok) {
|
|
2315
|
+
const error = await response.json();
|
|
2316
|
+
spinner.fail(`Query failed: ${error.error || response.statusText}`);
|
|
2317
|
+
process.exit(1);
|
|
2318
|
+
}
|
|
2319
|
+
const result = await response.json();
|
|
2320
|
+
spinner.succeed("Query executed");
|
|
2321
|
+
if (result.columns && result.rows) {
|
|
2322
|
+
console.log();
|
|
2323
|
+
if (result.rows.length === 0) {
|
|
2324
|
+
console.log(chalk9.dim(" No rows returned"));
|
|
2325
|
+
} else {
|
|
2326
|
+
console.log(chalk9.bold(" " + result.columns.join(" ")));
|
|
2327
|
+
console.log(chalk9.dim(" " + result.columns.map(() => "--------").join(" ")));
|
|
2328
|
+
for (const row of result.rows) {
|
|
2329
|
+
const values = result.columns.map((col) => {
|
|
2330
|
+
const val = row[col];
|
|
2331
|
+
if (val === null) return chalk9.dim("NULL");
|
|
2332
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
2333
|
+
return String(val);
|
|
2334
|
+
});
|
|
2335
|
+
console.log(" " + values.join(" "));
|
|
2336
|
+
}
|
|
2337
|
+
console.log(chalk9.dim(`
|
|
2338
|
+
${result.rows.length} row(s)`));
|
|
2339
|
+
}
|
|
2340
|
+
} else if (result.rowsAffected !== void 0) {
|
|
2341
|
+
console.log(chalk9.dim(`
|
|
2342
|
+
${result.rowsAffected} row(s) affected`));
|
|
2343
|
+
}
|
|
2344
|
+
console.log();
|
|
2345
|
+
} catch (error) {
|
|
2346
|
+
spinner.fail(`Failed to execute query: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2347
|
+
process.exit(1);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2258
2351
|
// src/index.ts
|
|
2259
2352
|
var program = new Command();
|
|
2260
2353
|
program.name("tthr").description("Tether CLI - Realtime SQLite for modern applications").version("0.0.1");
|
|
@@ -2267,4 +2360,5 @@ program.command("login").description("Authenticate with Tether").action(loginCom
|
|
|
2267
2360
|
program.command("logout").description("Sign out of Tether").action(logoutCommand);
|
|
2268
2361
|
program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
|
|
2269
2362
|
program.command("update").description("Update all Tether packages to the latest version").option("--dry-run", "Show what would be updated without updating").action(updateCommand);
|
|
2363
|
+
program.command("exec <sql>").description("Execute a SQL query against the project database").action(execCommand);
|
|
2270
2364
|
program.parse();
|