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.
Files changed (2) hide show
  1. package/dist/index.js +141 -47
  2. 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
- let sqlType = "TEXT";
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, isDev4 = false) {
1091
- const devFlag = isDev4 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
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, isDev4) {
2167
+ function getInstallCommand2(pm, packages, isDev5) {
2148
2168
  const packagesStr = packages.join(" ");
2149
2169
  switch (pm) {
2150
2170
  case "bun":
2151
- return isDev4 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
2171
+ return isDev5 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
2152
2172
  case "pnpm":
2153
- return isDev4 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
2173
+ return isDev5 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
2154
2174
  case "yarn":
2155
- return isDev4 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
2175
+ return isDev5 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
2156
2176
  case "npm":
2157
2177
  default:
2158
- return isDev4 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tthr",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Tether CLI - project scaffolding, migrations, and deployment",
5
5
  "type": "module",
6
6
  "bin": {