primitive-admin 1.0.21 → 1.0.23
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/bin/primitive.js +28 -6
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/auth.js +1 -1
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/database-types.js +453 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.js +645 -0
- package/dist/src/commands/databases.js.map +1 -0
- package/dist/src/commands/email-templates.js +267 -0
- package/dist/src/commands/email-templates.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +189 -0
- package/dist/src/commands/group-type-configs.js.map +1 -0
- package/dist/src/commands/groups.js +366 -0
- package/dist/src/commands/groups.js.map +1 -0
- package/dist/src/commands/init.js +125 -48
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/rule-sets.js +366 -0
- package/dist/src/commands/rule-sets.js.map +1 -0
- package/dist/src/commands/sync.js +117 -3
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/lib/api-client.js +18 -0
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/constants.js +3 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/template.js +35 -14
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/version-check.js +169 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { ApiClient } from "../lib/api-client.js";
|
|
2
|
+
import { getCurrentAppId } from "../lib/config.js";
|
|
3
|
+
import { success, error, info, keyValue, formatTable, formatId, formatDate, json, } from "../lib/output.js";
|
|
4
|
+
function resolveAppId(appId, options) {
|
|
5
|
+
const resolved = appId || options.app || getCurrentAppId();
|
|
6
|
+
if (!resolved) {
|
|
7
|
+
error("No app specified. Use <app-id>, --app, or 'primitive use <app-id>' to set context.");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
return resolved;
|
|
11
|
+
}
|
|
12
|
+
export function registerDatabasesCommands(program) {
|
|
13
|
+
const databases = program
|
|
14
|
+
.command("databases")
|
|
15
|
+
.description("Create, manage, and configure online databases")
|
|
16
|
+
.addHelpText("after", `
|
|
17
|
+
Examples:
|
|
18
|
+
$ primitive databases list
|
|
19
|
+
$ primitive databases create "My Database"
|
|
20
|
+
$ primitive databases get <database-id>
|
|
21
|
+
$ primitive databases records models <database-id>
|
|
22
|
+
$ primitive databases records describe <database-id> <model-name>
|
|
23
|
+
$ primitive databases operations list <database-id>
|
|
24
|
+
$ primitive databases operations execute <database-id> <operation-name> --params '{"key":"value"}'
|
|
25
|
+
$ primitive databases indexes list <database-id>
|
|
26
|
+
$ primitive databases indexes create <database-id> --model contacts --field email
|
|
27
|
+
$ primitive databases permissions list <database-id>
|
|
28
|
+
`);
|
|
29
|
+
// List databases
|
|
30
|
+
databases
|
|
31
|
+
.command("list")
|
|
32
|
+
.description("List databases in an app")
|
|
33
|
+
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
34
|
+
.option("--app <app-id>", "App ID")
|
|
35
|
+
.option("--json", "Output as JSON")
|
|
36
|
+
.action(async (appId, options) => {
|
|
37
|
+
const resolvedAppId = resolveAppId(appId, options);
|
|
38
|
+
const client = new ApiClient();
|
|
39
|
+
try {
|
|
40
|
+
const result = await client.listDatabases(resolvedAppId);
|
|
41
|
+
const list = Array.isArray(result) ? result : result?.databases ?? [];
|
|
42
|
+
if (options.json) {
|
|
43
|
+
json(list);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (list.length === 0) {
|
|
47
|
+
info("No databases found.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log(formatTable(list, [
|
|
51
|
+
{ header: "ID", key: "databaseId", format: formatId },
|
|
52
|
+
{ header: "TITLE", key: "title" },
|
|
53
|
+
{ header: "TYPE", key: "databaseType", format: (v) => v || "—" },
|
|
54
|
+
{ header: "PERMISSION", key: "permission" },
|
|
55
|
+
{ header: "CREATED", key: "createdAt", format: formatDate },
|
|
56
|
+
]));
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
error(err.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Create database
|
|
64
|
+
databases
|
|
65
|
+
.command("create")
|
|
66
|
+
.description("Create a new database")
|
|
67
|
+
.argument("<title>", "Database title")
|
|
68
|
+
.option("--app <app-id>", "App ID")
|
|
69
|
+
.requiredOption("--type <database-type>", "Database type (required)")
|
|
70
|
+
.option("--json", "Output as JSON")
|
|
71
|
+
.action(async (title, options) => {
|
|
72
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
73
|
+
const client = new ApiClient();
|
|
74
|
+
try {
|
|
75
|
+
const params = { title, databaseType: options.type };
|
|
76
|
+
const result = await client.createDatabase(resolvedAppId, params);
|
|
77
|
+
if (options.json) {
|
|
78
|
+
json(result);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
success("Database created.");
|
|
82
|
+
keyValue("Database ID", result.databaseId);
|
|
83
|
+
keyValue("Title", result.title);
|
|
84
|
+
if (result.databaseType) {
|
|
85
|
+
keyValue("Database Type", result.databaseType);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
error(err.message);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// Get database
|
|
94
|
+
databases
|
|
95
|
+
.command("get")
|
|
96
|
+
.description("Get database details")
|
|
97
|
+
.argument("<database-id>", "Database ID")
|
|
98
|
+
.option("--app <app-id>", "App ID")
|
|
99
|
+
.option("--json", "Output as JSON")
|
|
100
|
+
.action(async (databaseId, options) => {
|
|
101
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
102
|
+
const client = new ApiClient();
|
|
103
|
+
try {
|
|
104
|
+
const result = await client.getDatabase(resolvedAppId, databaseId);
|
|
105
|
+
if (options.json) {
|
|
106
|
+
json(result);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
keyValue("Database ID", result.databaseId);
|
|
110
|
+
keyValue("Title", result.title);
|
|
111
|
+
keyValue("Database Type", result.databaseType || "none");
|
|
112
|
+
keyValue("Permission", result.permission || "none");
|
|
113
|
+
keyValue("Created", formatDate(result.createdAt));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
error(err.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Update database
|
|
121
|
+
databases
|
|
122
|
+
.command("update")
|
|
123
|
+
.description("Update a database (title, type)")
|
|
124
|
+
.argument("<database-id>", "Database ID")
|
|
125
|
+
.option("--app <app-id>", "App ID")
|
|
126
|
+
.option("--title <title>", "New title")
|
|
127
|
+
.option("--type <database-type>", "Database type (use 'none' to clear)")
|
|
128
|
+
.option("--json", "Output as JSON")
|
|
129
|
+
.action(async (databaseId, options) => {
|
|
130
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
131
|
+
const client = new ApiClient();
|
|
132
|
+
const params = {};
|
|
133
|
+
if (options.title) {
|
|
134
|
+
params.title = options.title;
|
|
135
|
+
}
|
|
136
|
+
if (options.type !== undefined) {
|
|
137
|
+
params.databaseType =
|
|
138
|
+
options.type === "none"
|
|
139
|
+
? null
|
|
140
|
+
: options.type;
|
|
141
|
+
}
|
|
142
|
+
if (Object.keys(params).length === 0) {
|
|
143
|
+
error("Provide at least one of --title or --type.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const result = await client.updateDatabase(resolvedAppId, databaseId, params);
|
|
148
|
+
if (options.json) {
|
|
149
|
+
json(result);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
success("Database updated.");
|
|
153
|
+
keyValue("Database ID", result.databaseId);
|
|
154
|
+
keyValue("Title", result.title);
|
|
155
|
+
keyValue("Database Type", result.databaseType || "none");
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
error(err.message);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Delete database
|
|
163
|
+
databases
|
|
164
|
+
.command("delete")
|
|
165
|
+
.description("Delete a database")
|
|
166
|
+
.argument("<database-id>", "Database ID")
|
|
167
|
+
.option("--app <app-id>", "App ID")
|
|
168
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
169
|
+
.action(async (databaseId, options) => {
|
|
170
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
171
|
+
if (!options.yes) {
|
|
172
|
+
const inquirer = await import("inquirer");
|
|
173
|
+
const { confirm } = await inquirer.default.prompt([
|
|
174
|
+
{
|
|
175
|
+
type: "confirm",
|
|
176
|
+
name: "confirm",
|
|
177
|
+
message: `Delete database ${databaseId}? This cannot be undone.`,
|
|
178
|
+
default: false,
|
|
179
|
+
},
|
|
180
|
+
]);
|
|
181
|
+
if (!confirm) {
|
|
182
|
+
info("Cancelled.");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const client = new ApiClient();
|
|
187
|
+
try {
|
|
188
|
+
await client.deleteDatabase(resolvedAppId, databaseId);
|
|
189
|
+
success(`Database ${databaseId} deleted.`);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
error(err.message);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// ---- Permissions subcommand group ----
|
|
197
|
+
const permissions = databases
|
|
198
|
+
.command("permissions")
|
|
199
|
+
.description("Manage user permissions on a database");
|
|
200
|
+
// List permissions
|
|
201
|
+
permissions
|
|
202
|
+
.command("list")
|
|
203
|
+
.description("List user permissions for a database")
|
|
204
|
+
.argument("<database-id>", "Database ID")
|
|
205
|
+
.option("--app <app-id>", "App ID")
|
|
206
|
+
.option("--json", "Output as JSON")
|
|
207
|
+
.action(async (databaseId, options) => {
|
|
208
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
209
|
+
const client = new ApiClient();
|
|
210
|
+
try {
|
|
211
|
+
const result = await client.listDatabasePermissions(resolvedAppId, databaseId);
|
|
212
|
+
const list = Array.isArray(result) ? result : result?.permissions ?? [];
|
|
213
|
+
if (options.json) {
|
|
214
|
+
json(list);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (list.length === 0) {
|
|
218
|
+
info("No permissions found.");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
console.log(formatTable(list, [
|
|
222
|
+
{ header: "USER_ID", key: "userId", format: formatId },
|
|
223
|
+
{ header: "EMAIL", key: "userEmail" },
|
|
224
|
+
{ header: "PERMISSION", key: "permission" },
|
|
225
|
+
{ header: "GRANTED", key: "grantedAt", format: formatDate },
|
|
226
|
+
]));
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
error(err.message);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
// Grant permission
|
|
234
|
+
permissions
|
|
235
|
+
.command("grant")
|
|
236
|
+
.description("Grant a user permission on a database")
|
|
237
|
+
.argument("<database-id>", "Database ID")
|
|
238
|
+
.requiredOption("--user-id <user-id>", "User ID to grant permission to")
|
|
239
|
+
.requiredOption("--permission <permission>", "Permission level: manager")
|
|
240
|
+
.option("--app <app-id>", "App ID")
|
|
241
|
+
.option("--json", "Output as JSON")
|
|
242
|
+
.action(async (databaseId, options) => {
|
|
243
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
244
|
+
const client = new ApiClient();
|
|
245
|
+
try {
|
|
246
|
+
const result = await client.grantDatabasePermission(resolvedAppId, databaseId, {
|
|
247
|
+
userId: options.userId,
|
|
248
|
+
permission: options.permission,
|
|
249
|
+
});
|
|
250
|
+
if (options.json) {
|
|
251
|
+
json(result);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
success(`Permission '${options.permission}' granted to user ${options.userId}.`);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
error(err.message);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// Revoke permission
|
|
262
|
+
permissions
|
|
263
|
+
.command("revoke")
|
|
264
|
+
.description("Revoke a user's permission on a database")
|
|
265
|
+
.argument("<database-id>", "Database ID")
|
|
266
|
+
.argument("<user-id>", "User ID to revoke")
|
|
267
|
+
.option("--app <app-id>", "App ID")
|
|
268
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
269
|
+
.action(async (databaseId, userId, options) => {
|
|
270
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
271
|
+
if (!options.yes) {
|
|
272
|
+
const inquirer = await import("inquirer");
|
|
273
|
+
const { confirm } = await inquirer.default.prompt([
|
|
274
|
+
{
|
|
275
|
+
type: "confirm",
|
|
276
|
+
name: "confirm",
|
|
277
|
+
message: `Revoke permission for user ${userId} on database ${databaseId}?`,
|
|
278
|
+
default: false,
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
if (!confirm) {
|
|
282
|
+
info("Cancelled.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const client = new ApiClient();
|
|
287
|
+
try {
|
|
288
|
+
await client.revokeDatabasePermission(resolvedAppId, databaseId, userId);
|
|
289
|
+
success(`Permission revoked for user ${userId}.`);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
error(err.message);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
// ---- Records subcommand group (models & describe only) ----
|
|
297
|
+
const records = databases
|
|
298
|
+
.command("records")
|
|
299
|
+
.description("Inspect models and schemas in a database");
|
|
300
|
+
// List models
|
|
301
|
+
records
|
|
302
|
+
.command("models")
|
|
303
|
+
.description("List model names in a database")
|
|
304
|
+
.argument("<database-id>", "Database ID")
|
|
305
|
+
.option("--app <app-id>", "App ID")
|
|
306
|
+
.option("--json", "Output as JSON")
|
|
307
|
+
.action(async (databaseId, options) => {
|
|
308
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
309
|
+
const client = new ApiClient();
|
|
310
|
+
try {
|
|
311
|
+
const result = await client.listDatabaseModels(resolvedAppId, databaseId);
|
|
312
|
+
const models = result.models || [];
|
|
313
|
+
if (options.json) {
|
|
314
|
+
json(models);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (models.length === 0) {
|
|
318
|
+
info("No models found.");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
for (const m of models) {
|
|
322
|
+
console.log(` ${m}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
error(err.message);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
// Describe model
|
|
331
|
+
records
|
|
332
|
+
.command("describe")
|
|
333
|
+
.description("Show inferred schema for a model")
|
|
334
|
+
.argument("<database-id>", "Database ID")
|
|
335
|
+
.argument("<model-name>", "Model name")
|
|
336
|
+
.option("--app <app-id>", "App ID")
|
|
337
|
+
.option("--json", "Output as JSON")
|
|
338
|
+
.action(async (databaseId, modelName, options) => {
|
|
339
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
340
|
+
const client = new ApiClient();
|
|
341
|
+
try {
|
|
342
|
+
const result = await client.describeDatabaseModel(resolvedAppId, databaseId, modelName);
|
|
343
|
+
const fields = result.fields || [];
|
|
344
|
+
if (options.json) {
|
|
345
|
+
json(fields);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (fields.length === 0) {
|
|
349
|
+
info("No fields detected.");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
console.log(formatTable(fields, [
|
|
353
|
+
{ header: "FIELD", key: "field_name" },
|
|
354
|
+
{ header: "TYPE", key: "inferred_type" },
|
|
355
|
+
]));
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
error(err.message);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
// ---- Metadata subcommand group ----
|
|
363
|
+
const metadata = databases
|
|
364
|
+
.command("metadata")
|
|
365
|
+
.description("Manage database metadata");
|
|
366
|
+
metadata
|
|
367
|
+
.command("update")
|
|
368
|
+
.description("Update database metadata (merge with existing)")
|
|
369
|
+
.argument("<database-id>", "Database ID")
|
|
370
|
+
.requiredOption("--data <json>", "Metadata fields as JSON (merged with existing)")
|
|
371
|
+
.option("--app <app-id>", "App ID")
|
|
372
|
+
.option("--json", "Output as JSON")
|
|
373
|
+
.action(async (databaseId, options) => {
|
|
374
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
375
|
+
const client = new ApiClient();
|
|
376
|
+
let data;
|
|
377
|
+
try {
|
|
378
|
+
data = JSON.parse(options.data);
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
error("Invalid --data JSON.");
|
|
382
|
+
process.exit(1);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const result = await client.updateDatabaseMetadata(resolvedAppId, databaseId, data);
|
|
387
|
+
if (options.json) {
|
|
388
|
+
json(result);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
success("Metadata updated.");
|
|
392
|
+
if (result.metadata) {
|
|
393
|
+
keyValue("Metadata", JSON.stringify(result.metadata));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
error(err.message);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
// ---- Operations subcommand group ----
|
|
402
|
+
const operations = databases
|
|
403
|
+
.command("operations")
|
|
404
|
+
.description("List and execute registered operations on a database");
|
|
405
|
+
// List operations
|
|
406
|
+
operations
|
|
407
|
+
.command("list")
|
|
408
|
+
.description("List registered operations available on a database")
|
|
409
|
+
.argument("<database-id>", "Database ID")
|
|
410
|
+
.option("--app <app-id>", "App ID")
|
|
411
|
+
.option("--json", "Output as JSON")
|
|
412
|
+
.action(async (databaseId, options) => {
|
|
413
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
414
|
+
const client = new ApiClient();
|
|
415
|
+
try {
|
|
416
|
+
const result = await client.listDatabaseOperations(resolvedAppId, databaseId);
|
|
417
|
+
const list = Array.isArray(result) ? result : [];
|
|
418
|
+
if (options.json) {
|
|
419
|
+
json(list);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (list.length === 0) {
|
|
423
|
+
info("No operations found.");
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
console.log(formatTable(list, [
|
|
427
|
+
{ header: "NAME", key: "name" },
|
|
428
|
+
{ header: "TYPE", key: "type" },
|
|
429
|
+
{ header: "MODEL", key: "modelName" },
|
|
430
|
+
{ header: "ACCESS", key: "access" },
|
|
431
|
+
]));
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
error(err.message);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
// Execute operation
|
|
439
|
+
operations
|
|
440
|
+
.command("execute")
|
|
441
|
+
.description("Execute a registered operation on a database")
|
|
442
|
+
.argument("<database-id>", "Database ID")
|
|
443
|
+
.argument("<operation-name>", "Operation name")
|
|
444
|
+
.option("--params <json>", "Operation parameters as JSON")
|
|
445
|
+
.option("--limit <n>", "Max records to return (for queries)")
|
|
446
|
+
.option("--cursor <cursor>", "Pagination cursor")
|
|
447
|
+
.option("--token <jwt>", "Execute as a specific user (dev/test — JWT visible in process args)")
|
|
448
|
+
.option("--app <app-id>", "App ID")
|
|
449
|
+
.option("--json", "Output as JSON")
|
|
450
|
+
.option("--timing", "Show server-side timing breakdown")
|
|
451
|
+
.action(async (databaseId, operationName, options) => {
|
|
452
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
453
|
+
const client = new ApiClient();
|
|
454
|
+
let params;
|
|
455
|
+
if (options.params) {
|
|
456
|
+
try {
|
|
457
|
+
params = JSON.parse(options.params);
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
error("Invalid --params JSON.");
|
|
461
|
+
process.exit(1);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const body = {};
|
|
466
|
+
if (params)
|
|
467
|
+
body.params = params;
|
|
468
|
+
if (options.limit)
|
|
469
|
+
body.limit = parseInt(options.limit, 10);
|
|
470
|
+
if (options.cursor)
|
|
471
|
+
body.cursor = options.cursor;
|
|
472
|
+
try {
|
|
473
|
+
const result = await client.executeDatabaseOperation(resolvedAppId, databaseId, operationName, body, options.token, { timing: !!options.timing });
|
|
474
|
+
if (options.json) {
|
|
475
|
+
json(result);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Smart display based on result shape
|
|
479
|
+
if (result.data && Array.isArray(result.data)) {
|
|
480
|
+
if (result.data.length === 0) {
|
|
481
|
+
info("No records found.");
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
const cols = Object.keys(result.data[0]).filter((k) => k !== "type");
|
|
485
|
+
console.log(formatTable(result.data, cols.map((c) => ({
|
|
486
|
+
header: c.toUpperCase(),
|
|
487
|
+
key: c,
|
|
488
|
+
format: (v) => {
|
|
489
|
+
if (v === null || v === undefined)
|
|
490
|
+
return "—";
|
|
491
|
+
if (typeof v === "object") {
|
|
492
|
+
const s = JSON.stringify(v);
|
|
493
|
+
return s.length > 50 ? s.slice(0, 47) + "..." : s;
|
|
494
|
+
}
|
|
495
|
+
const s = String(v);
|
|
496
|
+
return s.length > 50 ? s.slice(0, 47) + "..." : s;
|
|
497
|
+
},
|
|
498
|
+
}))));
|
|
499
|
+
}
|
|
500
|
+
if (result.hasMore) {
|
|
501
|
+
info(`More records available. Use --cursor ${result.cursor} to continue.`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (result.count !== undefined) {
|
|
505
|
+
console.log(`Count: ${result.count}`);
|
|
506
|
+
}
|
|
507
|
+
else if (result.result !== undefined) {
|
|
508
|
+
// Aggregate result
|
|
509
|
+
json(result);
|
|
510
|
+
}
|
|
511
|
+
else if (result.results && Array.isArray(result.results)) {
|
|
512
|
+
// Batch/mutation results
|
|
513
|
+
success(`Operation executed (${result.results.length} result${result.results.length === 1 ? "" : "s"}).`);
|
|
514
|
+
}
|
|
515
|
+
else if (result.success !== undefined) {
|
|
516
|
+
success("Operation executed.");
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
json(result);
|
|
520
|
+
}
|
|
521
|
+
// Display timing if present
|
|
522
|
+
if (result._timing) {
|
|
523
|
+
console.log("");
|
|
524
|
+
console.log("Timing:");
|
|
525
|
+
for (const [key, value] of Object.entries(result._timing)) {
|
|
526
|
+
console.log(` ${key}: ${value}ms`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
error(err.message);
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
// ---- Indexes subcommand group ----
|
|
536
|
+
const indexes = databases
|
|
537
|
+
.command("indexes")
|
|
538
|
+
.description("Manage indexes on a database");
|
|
539
|
+
// List indexes
|
|
540
|
+
indexes
|
|
541
|
+
.command("list")
|
|
542
|
+
.description("List indexes for a database or model")
|
|
543
|
+
.argument("<database-id>", "Database ID")
|
|
544
|
+
.option("--model <model-name>", "Filter by model name")
|
|
545
|
+
.option("--app <app-id>", "App ID")
|
|
546
|
+
.option("--json", "Output as JSON")
|
|
547
|
+
.action(async (databaseId, options) => {
|
|
548
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
549
|
+
const client = new ApiClient();
|
|
550
|
+
try {
|
|
551
|
+
const result = await client.listDatabaseIndexes(resolvedAppId, databaseId, options.model);
|
|
552
|
+
const list = result.indexes || [];
|
|
553
|
+
if (options.json) {
|
|
554
|
+
json(list);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (list.length === 0) {
|
|
558
|
+
info("No indexes found.");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
console.log(formatTable(list, [
|
|
562
|
+
{ header: "MODEL", key: "model_name" },
|
|
563
|
+
{ header: "FIELD", key: "field_name" },
|
|
564
|
+
{ header: "TYPE", key: "field_type" },
|
|
565
|
+
{ header: "UNIQUE", key: "is_unique", format: (v) => (v ? "Yes" : "No") },
|
|
566
|
+
]));
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
error(err.message);
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
// Register index
|
|
574
|
+
indexes
|
|
575
|
+
.command("create")
|
|
576
|
+
.description("Create an index on a model field")
|
|
577
|
+
.argument("<database-id>", "Database ID")
|
|
578
|
+
.requiredOption("--model <model-name>", "Model name")
|
|
579
|
+
.requiredOption("--field <field-name>", "Field name to index")
|
|
580
|
+
.option("--type <field-type>", "Field type: string, number, or boolean", "string")
|
|
581
|
+
.option("--unique", "Make this a unique index")
|
|
582
|
+
.option("--app <app-id>", "App ID")
|
|
583
|
+
.option("--json", "Output as JSON")
|
|
584
|
+
.action(async (databaseId, options) => {
|
|
585
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
586
|
+
const client = new ApiClient();
|
|
587
|
+
try {
|
|
588
|
+
const result = await client.registerDatabaseIndex(resolvedAppId, databaseId, {
|
|
589
|
+
modelName: options.model,
|
|
590
|
+
fieldName: options.field,
|
|
591
|
+
fieldType: options.type,
|
|
592
|
+
unique: !!options.unique,
|
|
593
|
+
});
|
|
594
|
+
if (options.json) {
|
|
595
|
+
json(result);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
success(`Index created on ${options.model}.${options.field} (${options.type}${options.unique ? ", unique" : ""}).`);
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
error(err.message);
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
// Drop index
|
|
606
|
+
indexes
|
|
607
|
+
.command("drop")
|
|
608
|
+
.description("Drop an index from a model field")
|
|
609
|
+
.argument("<database-id>", "Database ID")
|
|
610
|
+
.requiredOption("--model <model-name>", "Model name")
|
|
611
|
+
.requiredOption("--field <field-name>", "Field name")
|
|
612
|
+
.option("--app <app-id>", "App ID")
|
|
613
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
614
|
+
.action(async (databaseId, options) => {
|
|
615
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
616
|
+
if (!options.yes) {
|
|
617
|
+
const inquirer = await import("inquirer");
|
|
618
|
+
const { confirm } = await inquirer.default.prompt([
|
|
619
|
+
{
|
|
620
|
+
type: "confirm",
|
|
621
|
+
name: "confirm",
|
|
622
|
+
message: `Drop index on ${options.model}.${options.field}?`,
|
|
623
|
+
default: false,
|
|
624
|
+
},
|
|
625
|
+
]);
|
|
626
|
+
if (!confirm) {
|
|
627
|
+
info("Cancelled.");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const client = new ApiClient();
|
|
632
|
+
try {
|
|
633
|
+
await client.dropDatabaseIndex(resolvedAppId, databaseId, {
|
|
634
|
+
modelName: options.model,
|
|
635
|
+
fieldName: options.field,
|
|
636
|
+
});
|
|
637
|
+
success(`Index dropped on ${options.model}.${options.field}.`);
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
error(err.message);
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
//# sourceMappingURL=databases.js.map
|