svcloud 0.1.0 → 0.1.1

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/package.json +1 -1
  2. package/src/commands/db.ts +55 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svcloud",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The SV Cloud CLI. Alpha: login, logout, status, open, projects list, mcp, init, and runs are built; see PLANNING.md for what's still missing.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * `svcloud db` — PLANNING.md §4 Step 5. Mirrors F8's row editor
3
- * (`apps/cloud/src/routes/database.ts`) plus the two DDL routes added
4
- * alongside this command (`db_create_table`/`db_run_migration`, previously
5
- * MCP-tool-only — see that file's header). Cell/column payloads are taken as
6
- * JSON on the command line rather than a flag-per-field grammar: the primary
3
+ * (`apps/cloud/src/routes/database.ts`) plus the DDL routes added
4
+ * alongside this command (`db_create_table`/`db_run_migration`/`db_create_index`,
5
+ * previously MCP-tool-only — see that file's header). Cell/column payloads are taken
6
+ * as JSON on the command line rather than a flag-per-field grammar: the primary
7
7
  * caller for structured data like this is a coding agent, and it goes
8
8
  * through `svcloud mcp`'s tools directly (`lib/prompt.ts`'s header), not this
9
9
  * command — this path exists for a human or a script at a real terminal.
10
10
  */
11
11
  import { apiFetch } from "../lib/api";
12
12
  import { findProjectBySlug } from "../lib/find-project";
13
- import { die, printJson, printTable, takeOption } from "../lib/output";
13
+ import { die, hasFlag, printJson, printTable, takeOption } from "../lib/output";
14
14
 
15
15
  const USAGE = `Usage:
16
16
  svcloud db tables <app>
@@ -18,12 +18,14 @@ const USAGE = `Usage:
18
18
  svcloud db insert <app> <table> <cells-json>
19
19
  svcloud db update <app> <table> <row-id> <cells-json>
20
20
  svcloud db delete <app> <table> <row-id>
21
- svcloud db create-table <app> <table> <columns-json> --primary-key <name>
21
+ svcloud db create-table <app> <table> <columns-json> --primary-key <name> [--indexes <indexes-json>]
22
+ svcloud db create-index <app> <table> <columns-json> [--name <name>] [--unique]
22
23
  svcloud db migrate <app> <table> <column-json> [--default <value>]
23
24
 
24
25
  <cells-json> is a JSON object, e.g. '{"title":"Hello"}'
25
26
  <columns-json> is a JSON array, e.g. '[{"name":"id","type":"uuid","nullable":false},{"name":"title","type":"text"}]'
26
27
  <column-json> is a single column, e.g. '{"name":"count","type":"number","nullable":false}'
28
+ <indexes-json> is a JSON array of indexes, e.g. '[{"columns":["email"],"unique":true}]'
27
29
  Column types: text, number, boolean, timestamp, json, uuid`;
28
30
 
29
31
  interface DbColumn {
@@ -193,13 +195,25 @@ async function dbDelete(argv: string[], json: boolean): Promise<void> {
193
195
 
194
196
  async function dbCreateTable(argv: string[], json: boolean): Promise<void> {
195
197
  const pkOpt = takeOption(argv, "primary-key");
196
- const [slug, table, columnsJson] = pkOpt.rest;
198
+ const indexesOpt = takeOption(pkOpt.rest, "indexes");
199
+ const [slug, table, columnsJson] = indexesOpt.rest;
197
200
  if (!table || !pkOpt.value) die(USAGE);
198
201
  const projectId = await resolveProjectId(slug);
199
202
  const columns = parseJsonArg<unknown[]>(columnsJson, "<columns-json>");
203
+ const indexes = indexesOpt.value
204
+ ? parseJsonArg<unknown[]>(indexesOpt.value, "<indexes-json>")
205
+ : undefined;
200
206
  const result = await apiFetch<{ table: string; created: boolean }>(
201
207
  `/api/v1/projects/${projectId}/database/tables`,
202
- { method: "POST", body: { name: table, columns, primary_key: pkOpt.value } },
208
+ {
209
+ method: "POST",
210
+ body: {
211
+ name: table,
212
+ columns,
213
+ primary_key: pkOpt.value,
214
+ ...(indexes ? { indexes } : {}),
215
+ },
216
+ },
203
217
  );
204
218
  if (json) {
205
219
  printJson(result);
@@ -208,6 +222,36 @@ async function dbCreateTable(argv: string[], json: boolean): Promise<void> {
208
222
  console.log(`Created table "${result.table}".`);
209
223
  }
210
224
 
225
+ async function dbCreateIndex(argv: string[], json: boolean): Promise<void> {
226
+ const nameOpt = takeOption(argv, "name");
227
+ const uniqueFlag = hasFlag(nameOpt.rest, "unique");
228
+ const [slug, table, columnsJson] = uniqueFlag.rest;
229
+ if (!table || !columnsJson) die(USAGE);
230
+ const projectId = await resolveProjectId(slug);
231
+ const parsedColumns = parseJsonArg<unknown>(columnsJson, "<columns-json>");
232
+ const columns = Array.isArray(parsedColumns)
233
+ ? parsedColumns
234
+ : typeof parsedColumns === "string"
235
+ ? [parsedColumns]
236
+ : die("<columns-json> must be a JSON array of column names or a string.");
237
+ const result = await apiFetch<{ table: string; index: string; created: boolean }>(
238
+ `/api/v1/projects/${projectId}/database/tables/${encodeURIComponent(table)}/indexes`,
239
+ {
240
+ method: "POST",
241
+ body: {
242
+ columns,
243
+ name: nameOpt.value,
244
+ unique: uniqueFlag.present ? true : undefined,
245
+ },
246
+ },
247
+ );
248
+ if (json) {
249
+ printJson(result);
250
+ return;
251
+ }
252
+ console.log(`Created index "${result.index}" on "${result.table}".`);
253
+ }
254
+
211
255
  async function dbMigrate(argv: string[], json: boolean): Promise<void> {
212
256
  const defaultOpt = takeOption(argv, "default");
213
257
  const [slug, table, columnJson] = defaultOpt.rest;
@@ -241,9 +285,12 @@ export async function dbCommand(argv: string[], json: boolean): Promise<void> {
241
285
  return dbDelete(rest, json);
242
286
  case "create-table":
243
287
  return dbCreateTable(rest, json);
288
+ case "create-index":
289
+ return dbCreateIndex(rest, json);
244
290
  case "migrate":
245
291
  return dbMigrate(rest, json);
246
292
  default:
247
293
  die(USAGE);
248
294
  }
249
295
  }
296
+