primitive-admin 1.0.22 → 1.0.24

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.
@@ -19,12 +19,12 @@ Examples:
19
19
  $ primitive databases create "My Database"
20
20
  $ primitive databases get <database-id>
21
21
  $ primitive databases records models <database-id>
22
- $ primitive databases records query <database-id> <model-name>
23
- $ primitive databases records save <database-id> <model-name> --data '{"name":"Alice"}'
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"}'
24
25
  $ primitive databases indexes list <database-id>
25
26
  $ primitive databases indexes create <database-id> --model contacts --field email
26
27
  $ primitive databases permissions list <database-id>
27
- $ primitive databases group-permissions list <database-id>
28
28
  `);
29
29
  // List databases
30
30
  databases
@@ -50,7 +50,7 @@ Examples:
50
50
  console.log(formatTable(list, [
51
51
  { header: "ID", key: "databaseId", format: formatId },
52
52
  { header: "TITLE", key: "title" },
53
- { header: "DEFAULT PERM", key: "defaultPermission", format: (v) => v || "—" },
53
+ { header: "TYPE", key: "databaseType", format: (v) => v || "—" },
54
54
  { header: "PERMISSION", key: "permission" },
55
55
  { header: "CREATED", key: "createdAt", format: formatDate },
56
56
  ]));
@@ -66,16 +66,13 @@ Examples:
66
66
  .description("Create a new database")
67
67
  .argument("<title>", "Database title")
68
68
  .option("--app <app-id>", "App ID")
69
- .option("--default-permission <level>", "Default permission for all app users: reader or read-write")
69
+ .requiredOption("--type <database-type>", "Database type (required)")
70
70
  .option("--json", "Output as JSON")
71
71
  .action(async (title, options) => {
72
72
  const resolvedAppId = resolveAppId(undefined, options);
73
73
  const client = new ApiClient();
74
74
  try {
75
- const params = { title };
76
- if (options.defaultPermission) {
77
- params.defaultPermission = options.defaultPermission;
78
- }
75
+ const params = { title, databaseType: options.type };
79
76
  const result = await client.createDatabase(resolvedAppId, params);
80
77
  if (options.json) {
81
78
  json(result);
@@ -84,8 +81,8 @@ Examples:
84
81
  success("Database created.");
85
82
  keyValue("Database ID", result.databaseId);
86
83
  keyValue("Title", result.title);
87
- if (result.defaultPermission) {
88
- keyValue("Default Permission", result.defaultPermission);
84
+ if (result.databaseType) {
85
+ keyValue("Database Type", result.databaseType);
89
86
  }
90
87
  }
91
88
  catch (err) {
@@ -111,9 +108,8 @@ Examples:
111
108
  }
112
109
  keyValue("Database ID", result.databaseId);
113
110
  keyValue("Title", result.title);
114
- keyValue("Default Permission", result.defaultPermission || "none");
115
- keyValue("Rule Set", result.ruleSetId || "none");
116
- keyValue("Permission", result.permission);
111
+ keyValue("Database Type", result.databaseType || "none");
112
+ keyValue("Permission", result.permission || "none");
117
113
  keyValue("Created", formatDate(result.createdAt));
118
114
  }
119
115
  catch (err) {
@@ -124,12 +120,11 @@ Examples:
124
120
  // Update database
125
121
  databases
126
122
  .command("update")
127
- .description("Update a database (title, default permission, rule set)")
123
+ .description("Update a database (title, type)")
128
124
  .argument("<database-id>", "Database ID")
129
125
  .option("--app <app-id>", "App ID")
130
126
  .option("--title <title>", "New title")
131
- .option("--default-permission <level>", "Default permission for all app users: reader, read-write, or none")
132
- .option("--rule-set-id <id>", "Attach a rule set (use 'none' to detach)")
127
+ .option("--type <database-type>", "Database type (use 'none' to clear)")
133
128
  .option("--json", "Output as JSON")
134
129
  .action(async (databaseId, options) => {
135
130
  const resolvedAppId = resolveAppId(undefined, options);
@@ -138,20 +133,14 @@ Examples:
138
133
  if (options.title) {
139
134
  params.title = options.title;
140
135
  }
141
- if (options.defaultPermission !== undefined) {
142
- params.defaultPermission =
143
- options.defaultPermission === "none"
144
- ? null
145
- : options.defaultPermission;
146
- }
147
- if (options.ruleSetId !== undefined) {
148
- params.ruleSetId =
149
- options.ruleSetId === "none"
136
+ if (options.type !== undefined) {
137
+ params.databaseType =
138
+ options.type === "none"
150
139
  ? null
151
- : options.ruleSetId;
140
+ : options.type;
152
141
  }
153
142
  if (Object.keys(params).length === 0) {
154
- error("Provide at least one of --title, --default-permission, or --rule-set-id.");
143
+ error("Provide at least one of --title or --type.");
155
144
  process.exit(1);
156
145
  }
157
146
  try {
@@ -163,8 +152,7 @@ Examples:
163
152
  success("Database updated.");
164
153
  keyValue("Database ID", result.databaseId);
165
154
  keyValue("Title", result.title);
166
- keyValue("Default Permission", result.defaultPermission || "none");
167
- keyValue("Rule Set", result.ruleSetId || "none");
155
+ keyValue("Database Type", result.databaseType || "none");
168
156
  }
169
157
  catch (err) {
170
158
  error(err.message);
@@ -248,7 +236,7 @@ Examples:
248
236
  .description("Grant a user permission on a database")
249
237
  .argument("<database-id>", "Database ID")
250
238
  .requiredOption("--user-id <user-id>", "User ID to grant permission to")
251
- .requiredOption("--permission <permission>", "Permission level: owner, read-write, or reader")
239
+ .requiredOption("--permission <permission>", "Permission level: manager")
252
240
  .option("--app <app-id>", "App ID")
253
241
  .option("--json", "Output as JSON")
254
242
  .action(async (databaseId, options) => {
@@ -305,10 +293,10 @@ Examples:
305
293
  process.exit(1);
306
294
  }
307
295
  });
308
- // ---- Records subcommand group ----
296
+ // ---- Records subcommand group (models & describe only) ----
309
297
  const records = databases
310
298
  .command("records")
311
- .description("Query, create, and manage records in a database");
299
+ .description("Inspect models and schemas in a database");
312
300
  // List models
313
301
  records
314
302
  .command("models")
@@ -371,64 +359,38 @@ Examples:
371
359
  process.exit(1);
372
360
  }
373
361
  });
374
- // Query records
375
- records
376
- .command("query")
377
- .description("Query records in a database")
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)")
378
369
  .argument("<database-id>", "Database ID")
379
- .argument("<model-name>", "Model name")
380
- .option("--filter <json>", "Filter as JSON", "{}")
381
- .option("--limit <n>", "Max records to return", "25")
382
- .option("--cursor <cursor>", "Pagination cursor")
370
+ .requiredOption("--data <json>", "Metadata fields as JSON (merged with existing)")
383
371
  .option("--app <app-id>", "App ID")
384
372
  .option("--json", "Output as JSON")
385
- .action(async (databaseId, modelName, options) => {
373
+ .action(async (databaseId, options) => {
386
374
  const resolvedAppId = resolveAppId(undefined, options);
387
375
  const client = new ApiClient();
388
- let filter = {};
376
+ let data;
389
377
  try {
390
- filter = JSON.parse(options.filter);
378
+ data = JSON.parse(options.data);
391
379
  }
392
380
  catch {
393
- error("Invalid --filter JSON.");
381
+ error("Invalid --data JSON.");
394
382
  process.exit(1);
383
+ return;
395
384
  }
396
385
  try {
397
- const body = {
398
- modelName,
399
- filter,
400
- options: { limit: parseInt(options.limit, 10) },
401
- };
402
- if (options.cursor)
403
- body.options.cursor = options.cursor;
404
- const result = await client.queryDatabaseRecords(resolvedAppId, databaseId, body);
405
- const data = result.data || [];
386
+ const result = await client.updateDatabaseMetadata(resolvedAppId, databaseId, data);
406
387
  if (options.json) {
407
388
  json(result);
408
389
  return;
409
390
  }
410
- if (data.length === 0) {
411
- info("No records found.");
412
- return;
413
- }
414
- // Derive columns from first record
415
- const cols = Object.keys(data[0]).filter((k) => k !== "type");
416
- console.log(formatTable(data, cols.map((c) => ({
417
- header: c.toUpperCase(),
418
- key: c,
419
- format: (v) => {
420
- if (v === null || v === undefined)
421
- return "—";
422
- if (typeof v === "object") {
423
- const s = JSON.stringify(v);
424
- return s.length > 50 ? s.slice(0, 47) + "..." : s;
425
- }
426
- const s = String(v);
427
- return s.length > 50 ? s.slice(0, 47) + "..." : s;
428
- },
429
- }))));
430
- if (result.hasMore) {
431
- info(`More records available. Use --cursor ${result.cursor} to continue.`);
391
+ success("Metadata updated.");
392
+ if (result.metadata) {
393
+ keyValue("Metadata", JSON.stringify(result.metadata));
432
394
  }
433
395
  }
434
396
  catch (err) {
@@ -436,116 +398,134 @@ Examples:
436
398
  process.exit(1);
437
399
  }
438
400
  });
439
- // Count records
440
- records
441
- .command("count")
442
- .description("Count records of a model")
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")
443
409
  .argument("<database-id>", "Database ID")
444
- .argument("<model-name>", "Model name")
445
- .option("--filter <json>", "Filter as JSON", "{}")
446
410
  .option("--app <app-id>", "App ID")
447
411
  .option("--json", "Output as JSON")
448
- .action(async (databaseId, modelName, options) => {
412
+ .action(async (databaseId, options) => {
449
413
  const resolvedAppId = resolveAppId(undefined, options);
450
414
  const client = new ApiClient();
451
- let filter = {};
452
- try {
453
- filter = JSON.parse(options.filter);
454
- }
455
- catch {
456
- error("Invalid --filter JSON.");
457
- process.exit(1);
458
- }
459
415
  try {
460
- const result = await client.countDatabaseRecords(resolvedAppId, databaseId, {
461
- modelName,
462
- filter,
463
- });
416
+ const result = await client.listDatabaseOperations(resolvedAppId, databaseId);
417
+ const list = Array.isArray(result) ? result : [];
464
418
  if (options.json) {
465
- json(result);
419
+ json(list);
420
+ return;
421
+ }
422
+ if (list.length === 0) {
423
+ info("No operations found.");
466
424
  return;
467
425
  }
468
- console.log(`Count: ${result.count}`);
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
+ ]));
469
432
  }
470
433
  catch (err) {
471
434
  error(err.message);
472
435
  process.exit(1);
473
436
  }
474
437
  });
475
- // Save record
476
- records
477
- .command("save")
478
- .description("Create or update a record")
438
+ // Execute operation
439
+ operations
440
+ .command("execute")
441
+ .description("Execute a registered operation on a database")
479
442
  .argument("<database-id>", "Database ID")
480
- .argument("<model-name>", "Model name")
481
- .option("--id <id>", "Record ID (auto-generated if omitted)")
482
- .requiredOption("--data <json>", "Record data as JSON")
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)")
483
448
  .option("--app <app-id>", "App ID")
484
449
  .option("--json", "Output as JSON")
485
- .action(async (databaseId, modelName, options) => {
450
+ .option("--timing", "Show server-side timing breakdown")
451
+ .action(async (databaseId, operationName, options) => {
486
452
  const resolvedAppId = resolveAppId(undefined, options);
487
453
  const client = new ApiClient();
488
- let data;
489
- try {
490
- data = JSON.parse(options.data);
491
- }
492
- catch {
493
- error("Invalid --data JSON.");
494
- process.exit(1);
495
- return;
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
+ }
496
464
  }
497
- const id = options.id || crypto.randomUUID().replace(/-/g, "").slice(0, 20);
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;
498
472
  try {
499
- const result = await client.saveDatabaseRecord(resolvedAppId, databaseId, {
500
- modelName,
501
- id,
502
- data,
503
- });
473
+ const result = await client.executeDatabaseOperation(resolvedAppId, databaseId, operationName, body, options.token, { timing: !!options.timing });
504
474
  if (options.json) {
505
475
  json(result);
506
476
  return;
507
477
  }
508
- success(`Record saved.`);
509
- keyValue("ID", result.id || id);
510
- }
511
- catch (err) {
512
- error(err.message);
513
- process.exit(1);
514
- }
515
- });
516
- // Delete record
517
- records
518
- .command("delete")
519
- .description("Delete a record")
520
- .argument("<database-id>", "Database ID")
521
- .argument("<model-name>", "Model name")
522
- .argument("<record-id>", "Record ID")
523
- .option("--app <app-id>", "App ID")
524
- .option("-y, --yes", "Skip confirmation prompt")
525
- .action(async (databaseId, modelName, recordId, options) => {
526
- const resolvedAppId = resolveAppId(undefined, options);
527
- if (!options.yes) {
528
- const inquirer = await import("inquirer");
529
- const { confirm } = await inquirer.default.prompt([
530
- {
531
- type: "confirm",
532
- name: "confirm",
533
- message: `Delete record ${recordId} from ${modelName}?`,
534
- default: false,
535
- },
536
- ]);
537
- if (!confirm) {
538
- info("Cancelled.");
539
- return;
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
+ }
540
528
  }
541
- }
542
- const client = new ApiClient();
543
- try {
544
- await client.deleteDatabaseRecord(resolvedAppId, databaseId, {
545
- modelName,
546
- id: recordId,
547
- });
548
- success(`Record ${recordId} deleted.`);
549
529
  }
550
530
  catch (err) {
551
531
  error(err.message);
@@ -661,108 +641,5 @@ Examples:
661
641
  process.exit(1);
662
642
  }
663
643
  });
664
- // ---- Group Permissions subcommand group ----
665
- const groupPermissions = databases
666
- .command("group-permissions")
667
- .description("Manage group permissions on a database");
668
- // List group permissions
669
- groupPermissions
670
- .command("list")
671
- .description("List group permissions for a database")
672
- .argument("<database-id>", "Database ID")
673
- .option("--app <app-id>", "App ID")
674
- .option("--json", "Output as JSON")
675
- .action(async (databaseId, options) => {
676
- const resolvedAppId = resolveAppId(undefined, options);
677
- const client = new ApiClient();
678
- try {
679
- const result = await client.listDatabaseGroupPermissions(resolvedAppId, databaseId);
680
- const list = Array.isArray(result) ? result : result?.permissions ?? [];
681
- if (options.json) {
682
- json(list);
683
- return;
684
- }
685
- if (list.length === 0) {
686
- info("No group permissions found.");
687
- return;
688
- }
689
- console.log(formatTable(list, [
690
- { header: "GROUP_TYPE", key: "groupType" },
691
- { header: "GROUP_ID", key: "groupId" },
692
- { header: "PERMISSION", key: "permission" },
693
- { header: "GRANTED", key: "grantedAt", format: formatDate },
694
- ]));
695
- }
696
- catch (err) {
697
- error(err.message);
698
- process.exit(1);
699
- }
700
- });
701
- // Grant group permission
702
- groupPermissions
703
- .command("grant")
704
- .description("Grant a group permission on a database")
705
- .argument("<database-id>", "Database ID")
706
- .requiredOption("--group-type <type>", "Group type")
707
- .requiredOption("--group-id <id>", "Group ID")
708
- .requiredOption("--permission <permission>", "Permission level: read-write or reader")
709
- .option("--app <app-id>", "App ID")
710
- .option("--json", "Output as JSON")
711
- .action(async (databaseId, options) => {
712
- const resolvedAppId = resolveAppId(undefined, options);
713
- const client = new ApiClient();
714
- try {
715
- const result = await client.grantDatabaseGroupPermission(resolvedAppId, databaseId, {
716
- groupType: options.groupType,
717
- groupId: options.groupId,
718
- permission: options.permission,
719
- });
720
- if (options.json) {
721
- json(result);
722
- return;
723
- }
724
- success(`Permission '${options.permission}' granted to group ${options.groupType}/${options.groupId}.`);
725
- }
726
- catch (err) {
727
- error(err.message);
728
- process.exit(1);
729
- }
730
- });
731
- // Revoke group permission
732
- groupPermissions
733
- .command("revoke")
734
- .description("Revoke a group's permission on a database")
735
- .argument("<database-id>", "Database ID")
736
- .argument("<group-type>", "Group type")
737
- .argument("<group-id>", "Group ID")
738
- .option("--app <app-id>", "App ID")
739
- .option("-y, --yes", "Skip confirmation prompt")
740
- .action(async (databaseId, groupType, groupId, options) => {
741
- const resolvedAppId = resolveAppId(undefined, options);
742
- if (!options.yes) {
743
- const inquirer = await import("inquirer");
744
- const { confirm } = await inquirer.default.prompt([
745
- {
746
- type: "confirm",
747
- name: "confirm",
748
- message: `Revoke permission for group ${groupType}/${groupId} on database ${databaseId}?`,
749
- default: false,
750
- },
751
- ]);
752
- if (!confirm) {
753
- info("Cancelled.");
754
- return;
755
- }
756
- }
757
- const client = new ApiClient();
758
- try {
759
- await client.revokeDatabaseGroupPermission(resolvedAppId, databaseId, groupType, groupId);
760
- success(`Permission revoked for group ${groupType}/${groupId}.`);
761
- }
762
- catch (err) {
763
- error(err.message);
764
- process.exit(1);
765
- }
766
- });
767
644
  }
768
645
  //# sourceMappingURL=databases.js.map