@parix/cli 0.1.5
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/README.md +123 -0
- package/dist/cli.cjs +30 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +28 -0
- package/dist/commands/api.cjs +81 -0
- package/dist/commands/api.d.cts +2 -0
- package/dist/commands/api.d.ts +2 -0
- package/dist/commands/api.js +78 -0
- package/dist/commands/auth.cjs +128 -0
- package/dist/commands/auth.d.cts +2 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +125 -0
- package/dist/commands/database.cjs +259 -0
- package/dist/commands/database.d.cts +2 -0
- package/dist/commands/database.d.ts +2 -0
- package/dist/commands/database.js +256 -0
- package/dist/commands/tb.cjs +242 -0
- package/dist/commands/tb.d.cts +2 -0
- package/dist/commands/tb.d.ts +2 -0
- package/dist/commands/tb.js +239 -0
- package/dist/lib/api-client.cjs +44 -0
- package/dist/lib/api-client.d.cts +10 -0
- package/dist/lib/api-client.d.ts +10 -0
- package/dist/lib/api-client.js +40 -0
- package/dist/lib/config.cjs +24 -0
- package/dist/lib/config.d.cts +10 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.js +18 -0
- package/dist/lib/loopback-server.cjs +158 -0
- package/dist/lib/loopback-server.d.cts +20 -0
- package/dist/lib/loopback-server.d.ts +20 -0
- package/dist/lib/loopback-server.js +155 -0
- package/dist/lib/oauth-api.cjs +73 -0
- package/dist/lib/oauth-api.d.cts +31 -0
- package/dist/lib/oauth-api.d.ts +31 -0
- package/dist/lib/oauth-api.js +68 -0
- package/dist/lib/oauth-session.cjs +108 -0
- package/dist/lib/oauth-session.d.cts +32 -0
- package/dist/lib/oauth-session.d.ts +32 -0
- package/dist/lib/oauth-session.js +103 -0
- package/dist/lib/oauth.cjs +58 -0
- package/dist/lib/oauth.d.cts +19 -0
- package/dist/lib/oauth.d.ts +19 -0
- package/dist/lib/oauth.js +49 -0
- package/dist/lib/open-url.cjs +38 -0
- package/dist/lib/open-url.d.cts +1 -0
- package/dist/lib/open-url.d.ts +1 -0
- package/dist/lib/open-url.js +32 -0
- package/dist/lib/output.cjs +20 -0
- package/dist/lib/output.d.cts +6 -0
- package/dist/lib/output.d.ts +6 -0
- package/dist/lib/output.js +15 -0
- package/dist/lib/parix-api.cjs +59 -0
- package/dist/lib/parix-api.d.cts +12 -0
- package/dist/lib/parix-api.d.ts +12 -0
- package/dist/lib/parix-api.js +55 -0
- package/dist/lib/session.cjs +80 -0
- package/dist/lib/session.d.cts +28 -0
- package/dist/lib/session.d.ts +28 -0
- package/dist/lib/session.js +74 -0
- package/dist/lib/tb-payloads.cjs +153 -0
- package/dist/lib/tb-payloads.d.cts +61 -0
- package/dist/lib/tb-payloads.d.ts +61 -0
- package/dist/lib/tb-payloads.js +144 -0
- package/package.json +74 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDatabaseCommand = createDatabaseCommand;
|
|
4
|
+
const prompts_1 = require("@clack/prompts");
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const output_1 = require("../lib/output.cjs");
|
|
7
|
+
const parix_api_1 = require("../lib/parix-api.cjs");
|
|
8
|
+
function createDatabaseCommand() {
|
|
9
|
+
const database = new commander_1.Command('database')
|
|
10
|
+
.description('Manage Parix databases')
|
|
11
|
+
.addHelpText('after', [
|
|
12
|
+
'',
|
|
13
|
+
'Examples:',
|
|
14
|
+
' parix database catalog --base-url http://localhost:5173',
|
|
15
|
+
' parix database create demo-ledger --provider gcp --region asia-southeast1',
|
|
16
|
+
' parix database list',
|
|
17
|
+
' parix database info id-123',
|
|
18
|
+
' parix database remove id-123 --yes',
|
|
19
|
+
].join('\n'));
|
|
20
|
+
database
|
|
21
|
+
.command('catalog')
|
|
22
|
+
.description('Show database creation catalog options')
|
|
23
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
24
|
+
.option('--json', 'Print raw JSON')
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
const response = await (0, parix_api_1.requestApiJson)({
|
|
27
|
+
baseUrl: options.baseUrl,
|
|
28
|
+
path: '/api/v1/catalog/create',
|
|
29
|
+
});
|
|
30
|
+
if (options.json) {
|
|
31
|
+
(0, output_1.printJson)(response);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
(0, output_1.printTable)({
|
|
35
|
+
columns: ['Provider', 'Regions'],
|
|
36
|
+
rows: response.catalog.providers.map((provider) => [
|
|
37
|
+
provider.slug,
|
|
38
|
+
provider.regions.map((region) => region.code).join(', '),
|
|
39
|
+
]),
|
|
40
|
+
});
|
|
41
|
+
(0, prompts_1.note)('Use `--provider <slug>` and `--region <code>` from this table.', 'Providers');
|
|
42
|
+
(0, output_1.printTable)({
|
|
43
|
+
columns: ['Config ID', 'Label', 'Nodes'],
|
|
44
|
+
rows: response.catalog.clusterConfigs.map((config) => [config.id, config.label, String(config.nodeCount)]),
|
|
45
|
+
});
|
|
46
|
+
(0, output_1.printTable)({
|
|
47
|
+
columns: ['Storage ID', 'Label', 'Multiplier'],
|
|
48
|
+
rows: response.catalog.storageTiers.map((tier) => [tier.id, tier.label, `${tier.priceMultiplier}x`]),
|
|
49
|
+
});
|
|
50
|
+
(0, output_1.printTable)({
|
|
51
|
+
columns: ['Size ID', 'Label', 'vCPU', 'Memory GB'],
|
|
52
|
+
rows: response.catalog.clusterSizes.map((size) => [
|
|
53
|
+
size.id,
|
|
54
|
+
size.label,
|
|
55
|
+
String(size.vcpu),
|
|
56
|
+
String(size.memoryGb),
|
|
57
|
+
]),
|
|
58
|
+
});
|
|
59
|
+
(0, prompts_1.outro)('Done');
|
|
60
|
+
});
|
|
61
|
+
database
|
|
62
|
+
.command('create')
|
|
63
|
+
.description('Create a new database')
|
|
64
|
+
.argument('<database>', 'Database name')
|
|
65
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
66
|
+
.option('--provider <slug>', 'Provider slug (for example: gcp, aws)')
|
|
67
|
+
.option('--region <code>', 'Region code')
|
|
68
|
+
.option('--cluster-config-id <id>', 'Cluster config id')
|
|
69
|
+
.option('--storage-tier-id <id>', 'Storage tier id')
|
|
70
|
+
.option('--cluster-size-id <id>', 'Cluster size id')
|
|
71
|
+
.option('--storage-gb <n>', 'Storage size in GB')
|
|
72
|
+
.option('--json', 'Print raw JSON')
|
|
73
|
+
.addHelpText('after', [
|
|
74
|
+
'',
|
|
75
|
+
'Examples:',
|
|
76
|
+
' parix database create payments-ledger --provider gcp --region asia-southeast1',
|
|
77
|
+
' parix database create payments-ledger --provider aws --region ap-southeast-1 --cluster-config-id config-single --cluster-size-id size-hx-5',
|
|
78
|
+
].join('\n'))
|
|
79
|
+
.action(async (databaseName, options) => {
|
|
80
|
+
const parsedStorageGb = options.storageGb ? Number.parseInt(options.storageGb, 10) : null;
|
|
81
|
+
if (options.storageGb &&
|
|
82
|
+
(typeof parsedStorageGb !== 'number' || !Number.isInteger(parsedStorageGb) || parsedStorageGb <= 0)) {
|
|
83
|
+
throw new Error('--storage-gb must be a positive integer.');
|
|
84
|
+
}
|
|
85
|
+
const response = await (0, parix_api_1.requestApiJson)({
|
|
86
|
+
baseUrl: options.baseUrl,
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
clusterConfigId: options.clusterConfigId,
|
|
89
|
+
clusterSizeId: options.clusterSizeId,
|
|
90
|
+
name: databaseName,
|
|
91
|
+
provider: options.provider,
|
|
92
|
+
region: options.region,
|
|
93
|
+
selectedStorageGb: parsedStorageGb ?? undefined,
|
|
94
|
+
storageTierId: options.storageTierId,
|
|
95
|
+
}),
|
|
96
|
+
headers: {
|
|
97
|
+
'content-type': 'application/json',
|
|
98
|
+
},
|
|
99
|
+
method: 'POST',
|
|
100
|
+
path: '/api/v1/databases',
|
|
101
|
+
});
|
|
102
|
+
if (options.json) {
|
|
103
|
+
(0, output_1.printJson)(response);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!response.success) {
|
|
107
|
+
(0, prompts_1.note)([
|
|
108
|
+
`Billing: ${response.billing?.message ?? response.message}`,
|
|
109
|
+
`Checkout URL: ${response.billing?.checkoutUrl ?? '-'}`,
|
|
110
|
+
].join('\n'), 'Provisioning blocked');
|
|
111
|
+
(0, prompts_1.outro)(response.message);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
(0, output_1.printKeyValues)([
|
|
115
|
+
['Database ID', response.database?.id ?? '-'],
|
|
116
|
+
['Database Name', response.database?.name ?? databaseName],
|
|
117
|
+
['Profile ID', response.profile?.id ?? '-'],
|
|
118
|
+
['Profile status', response.profile?.status ?? '-'],
|
|
119
|
+
['Provisioning', response.provisioning?.status ?? '-'],
|
|
120
|
+
['Workflow ID', response.job?.workflowId ?? '-'],
|
|
121
|
+
]);
|
|
122
|
+
if (response.provisioning) {
|
|
123
|
+
(0, prompts_1.note)([`Status: ${response.provisioning.status}`, `Message: ${response.provisioning.message ?? '-'}`].join('\n'), 'Provisioning');
|
|
124
|
+
}
|
|
125
|
+
(0, prompts_1.outro)(response.message);
|
|
126
|
+
});
|
|
127
|
+
database
|
|
128
|
+
.command('list')
|
|
129
|
+
.description('List databases in the active organization')
|
|
130
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
131
|
+
.option('--search <text>', 'Filter database names')
|
|
132
|
+
.option('--limit <n>', 'Maximum number of rows to return')
|
|
133
|
+
.option('--json', 'Print raw JSON')
|
|
134
|
+
.addHelpText('after', [
|
|
135
|
+
'',
|
|
136
|
+
'Examples:',
|
|
137
|
+
' parix database list',
|
|
138
|
+
' parix database list --search payments',
|
|
139
|
+
' parix database list --json',
|
|
140
|
+
].join('\n'))
|
|
141
|
+
.action(async (options) => {
|
|
142
|
+
const query = new URLSearchParams();
|
|
143
|
+
if (options.search) {
|
|
144
|
+
query.set('search', options.search);
|
|
145
|
+
}
|
|
146
|
+
if (options.limit) {
|
|
147
|
+
query.set('limit', options.limit);
|
|
148
|
+
}
|
|
149
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : '';
|
|
150
|
+
const response = await (0, parix_api_1.requestApiJson)({
|
|
151
|
+
baseUrl: options.baseUrl,
|
|
152
|
+
path: `/api/v1/databases${suffix}`,
|
|
153
|
+
});
|
|
154
|
+
if (options.json) {
|
|
155
|
+
(0, output_1.printJson)(response);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (response.databases.length === 0) {
|
|
159
|
+
(0, prompts_1.outro)('No databases found');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
(0, output_1.printTable)({
|
|
163
|
+
columns: ['ID', 'Name', 'Region', 'Provider', 'Status', 'Updated'],
|
|
164
|
+
rows: response.databases.map((database) => [
|
|
165
|
+
database.id,
|
|
166
|
+
database.name,
|
|
167
|
+
database.region ?? '-',
|
|
168
|
+
database.provider ?? '-',
|
|
169
|
+
database.status,
|
|
170
|
+
database.updatedAt,
|
|
171
|
+
]),
|
|
172
|
+
});
|
|
173
|
+
(0, prompts_1.note)(String(response.databases.length), 'Databases');
|
|
174
|
+
(0, prompts_1.outro)('Done');
|
|
175
|
+
});
|
|
176
|
+
database
|
|
177
|
+
.command('info')
|
|
178
|
+
.description('Show database details')
|
|
179
|
+
.argument('<database-id>', 'Database id')
|
|
180
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
181
|
+
.option('--json', 'Print raw JSON')
|
|
182
|
+
.addHelpText('after', ['', 'Examples:', ' parix database info db_123', ' parix database info db_123 --json'].join('\n'))
|
|
183
|
+
.action(async (databaseId, options) => {
|
|
184
|
+
const response = await (0, parix_api_1.requestApiJson)({
|
|
185
|
+
baseUrl: options.baseUrl,
|
|
186
|
+
path: `/api/v1/databases/${encodeURIComponent(databaseId)}`,
|
|
187
|
+
});
|
|
188
|
+
if (options.json) {
|
|
189
|
+
(0, output_1.printJson)(response);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
(0, output_1.printKeyValues)([
|
|
193
|
+
['Database ID', response.database.id],
|
|
194
|
+
['Database Name', response.database.name],
|
|
195
|
+
['Provider', response.provider ?? '-'],
|
|
196
|
+
['Region', response.region ?? '-'],
|
|
197
|
+
['Gateway URL', response.gatewayUrl ?? '-'],
|
|
198
|
+
['Status', response.decommissionState?.status ?? response.profile?.status ?? '-'],
|
|
199
|
+
['Version', response.profile?.tigerbeetleCurrentVersion ?? response.profile?.tigerbeetleVersion ?? '-'],
|
|
200
|
+
['Nodes', response.profile ? String(response.profile.selectedNodeCount) : '-'],
|
|
201
|
+
['CPU', response.profile ? String(response.profile.selectedVcpu) : '-'],
|
|
202
|
+
['Memory GB', response.profile ? String(response.profile.selectedMemoryGb) : '-'],
|
|
203
|
+
['Storage GB', response.profile?.selectedStorageGb != null ? String(response.profile.selectedStorageGb) : '-'],
|
|
204
|
+
['Updated', response.database.updatedAt],
|
|
205
|
+
]);
|
|
206
|
+
if (response.metricsSummary) {
|
|
207
|
+
(0, prompts_1.note)([
|
|
208
|
+
`CPU: ${response.metricsSummary.cpuUsagePct ?? '-'}%`,
|
|
209
|
+
`Memory: ${response.metricsSummary.memoryUsagePct ?? '-'}%`,
|
|
210
|
+
`Disk: ${response.metricsSummary.diskUsagePct ?? '-'}%`,
|
|
211
|
+
`Gateway P95: ${response.metricsSummary.gatewayP95LatencyMs ?? '-'}ms`,
|
|
212
|
+
`Last collected: ${response.metricsSummary.lastCollectedAt ?? '-'}`,
|
|
213
|
+
].join('\n'), 'Metrics');
|
|
214
|
+
}
|
|
215
|
+
if (response.latestProvisionJob) {
|
|
216
|
+
(0, prompts_1.note)([
|
|
217
|
+
`Provision job: ${response.latestProvisionJob.id}`,
|
|
218
|
+
`Provision status: ${response.latestProvisionJob.status}`,
|
|
219
|
+
`Workflow ID: ${response.latestProvisionJob.workflowId ?? '-'}`,
|
|
220
|
+
].join('\n'), 'Latest provision job');
|
|
221
|
+
}
|
|
222
|
+
(0, prompts_1.outro)('Done');
|
|
223
|
+
});
|
|
224
|
+
database
|
|
225
|
+
.command('remove')
|
|
226
|
+
.description('Queue database deletion')
|
|
227
|
+
.argument('<database-id>', 'Database id')
|
|
228
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
229
|
+
.option('--json', 'Print raw JSON')
|
|
230
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
231
|
+
.addHelpText('after', ['', 'Examples:', ' parix database remove db_123', ' parix database remove db_123 --yes --json'].join('\n'))
|
|
232
|
+
.action(async (databaseId, options) => {
|
|
233
|
+
if (!options.yes) {
|
|
234
|
+
const confirmed = await (0, prompts_1.confirm)({
|
|
235
|
+
message: `Queue deletion for ${databaseId}?`,
|
|
236
|
+
});
|
|
237
|
+
if ((0, prompts_1.isCancel)(confirmed) || !confirmed) {
|
|
238
|
+
(0, prompts_1.outro)('Cancelled');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const response = await (0, parix_api_1.requestApiJson)({
|
|
243
|
+
baseUrl: options.baseUrl,
|
|
244
|
+
method: 'DELETE',
|
|
245
|
+
path: `/api/v1/databases/${encodeURIComponent(databaseId)}`,
|
|
246
|
+
});
|
|
247
|
+
if (options.json) {
|
|
248
|
+
(0, output_1.printJson)(response);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
(0, output_1.printKeyValues)([
|
|
252
|
+
['Database ID', response.databaseId],
|
|
253
|
+
['Status', response.status],
|
|
254
|
+
['Workflow ID', response.workflowId],
|
|
255
|
+
]);
|
|
256
|
+
(0, prompts_1.outro)(response.message);
|
|
257
|
+
});
|
|
258
|
+
return database;
|
|
259
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { confirm, isCancel, note, outro } from '@clack/prompts';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { printJson, printKeyValues, printTable } from "../lib/output.js";
|
|
4
|
+
import { requestApiJson } from "../lib/parix-api.js";
|
|
5
|
+
export function createDatabaseCommand() {
|
|
6
|
+
const database = new Command('database')
|
|
7
|
+
.description('Manage Parix databases')
|
|
8
|
+
.addHelpText('after', [
|
|
9
|
+
'',
|
|
10
|
+
'Examples:',
|
|
11
|
+
' parix database catalog --base-url http://localhost:5173',
|
|
12
|
+
' parix database create demo-ledger --provider gcp --region asia-southeast1',
|
|
13
|
+
' parix database list',
|
|
14
|
+
' parix database info id-123',
|
|
15
|
+
' parix database remove id-123 --yes',
|
|
16
|
+
].join('\n'));
|
|
17
|
+
database
|
|
18
|
+
.command('catalog')
|
|
19
|
+
.description('Show database creation catalog options')
|
|
20
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
21
|
+
.option('--json', 'Print raw JSON')
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
const response = await requestApiJson({
|
|
24
|
+
baseUrl: options.baseUrl,
|
|
25
|
+
path: '/api/v1/catalog/create',
|
|
26
|
+
});
|
|
27
|
+
if (options.json) {
|
|
28
|
+
printJson(response);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
printTable({
|
|
32
|
+
columns: ['Provider', 'Regions'],
|
|
33
|
+
rows: response.catalog.providers.map((provider) => [
|
|
34
|
+
provider.slug,
|
|
35
|
+
provider.regions.map((region) => region.code).join(', '),
|
|
36
|
+
]),
|
|
37
|
+
});
|
|
38
|
+
note('Use `--provider <slug>` and `--region <code>` from this table.', 'Providers');
|
|
39
|
+
printTable({
|
|
40
|
+
columns: ['Config ID', 'Label', 'Nodes'],
|
|
41
|
+
rows: response.catalog.clusterConfigs.map((config) => [config.id, config.label, String(config.nodeCount)]),
|
|
42
|
+
});
|
|
43
|
+
printTable({
|
|
44
|
+
columns: ['Storage ID', 'Label', 'Multiplier'],
|
|
45
|
+
rows: response.catalog.storageTiers.map((tier) => [tier.id, tier.label, `${tier.priceMultiplier}x`]),
|
|
46
|
+
});
|
|
47
|
+
printTable({
|
|
48
|
+
columns: ['Size ID', 'Label', 'vCPU', 'Memory GB'],
|
|
49
|
+
rows: response.catalog.clusterSizes.map((size) => [
|
|
50
|
+
size.id,
|
|
51
|
+
size.label,
|
|
52
|
+
String(size.vcpu),
|
|
53
|
+
String(size.memoryGb),
|
|
54
|
+
]),
|
|
55
|
+
});
|
|
56
|
+
outro('Done');
|
|
57
|
+
});
|
|
58
|
+
database
|
|
59
|
+
.command('create')
|
|
60
|
+
.description('Create a new database')
|
|
61
|
+
.argument('<database>', 'Database name')
|
|
62
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
63
|
+
.option('--provider <slug>', 'Provider slug (for example: gcp, aws)')
|
|
64
|
+
.option('--region <code>', 'Region code')
|
|
65
|
+
.option('--cluster-config-id <id>', 'Cluster config id')
|
|
66
|
+
.option('--storage-tier-id <id>', 'Storage tier id')
|
|
67
|
+
.option('--cluster-size-id <id>', 'Cluster size id')
|
|
68
|
+
.option('--storage-gb <n>', 'Storage size in GB')
|
|
69
|
+
.option('--json', 'Print raw JSON')
|
|
70
|
+
.addHelpText('after', [
|
|
71
|
+
'',
|
|
72
|
+
'Examples:',
|
|
73
|
+
' parix database create payments-ledger --provider gcp --region asia-southeast1',
|
|
74
|
+
' parix database create payments-ledger --provider aws --region ap-southeast-1 --cluster-config-id config-single --cluster-size-id size-hx-5',
|
|
75
|
+
].join('\n'))
|
|
76
|
+
.action(async (databaseName, options) => {
|
|
77
|
+
const parsedStorageGb = options.storageGb ? Number.parseInt(options.storageGb, 10) : null;
|
|
78
|
+
if (options.storageGb &&
|
|
79
|
+
(typeof parsedStorageGb !== 'number' || !Number.isInteger(parsedStorageGb) || parsedStorageGb <= 0)) {
|
|
80
|
+
throw new Error('--storage-gb must be a positive integer.');
|
|
81
|
+
}
|
|
82
|
+
const response = await requestApiJson({
|
|
83
|
+
baseUrl: options.baseUrl,
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
clusterConfigId: options.clusterConfigId,
|
|
86
|
+
clusterSizeId: options.clusterSizeId,
|
|
87
|
+
name: databaseName,
|
|
88
|
+
provider: options.provider,
|
|
89
|
+
region: options.region,
|
|
90
|
+
selectedStorageGb: parsedStorageGb ?? undefined,
|
|
91
|
+
storageTierId: options.storageTierId,
|
|
92
|
+
}),
|
|
93
|
+
headers: {
|
|
94
|
+
'content-type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
method: 'POST',
|
|
97
|
+
path: '/api/v1/databases',
|
|
98
|
+
});
|
|
99
|
+
if (options.json) {
|
|
100
|
+
printJson(response);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!response.success) {
|
|
104
|
+
note([
|
|
105
|
+
`Billing: ${response.billing?.message ?? response.message}`,
|
|
106
|
+
`Checkout URL: ${response.billing?.checkoutUrl ?? '-'}`,
|
|
107
|
+
].join('\n'), 'Provisioning blocked');
|
|
108
|
+
outro(response.message);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
printKeyValues([
|
|
112
|
+
['Database ID', response.database?.id ?? '-'],
|
|
113
|
+
['Database Name', response.database?.name ?? databaseName],
|
|
114
|
+
['Profile ID', response.profile?.id ?? '-'],
|
|
115
|
+
['Profile status', response.profile?.status ?? '-'],
|
|
116
|
+
['Provisioning', response.provisioning?.status ?? '-'],
|
|
117
|
+
['Workflow ID', response.job?.workflowId ?? '-'],
|
|
118
|
+
]);
|
|
119
|
+
if (response.provisioning) {
|
|
120
|
+
note([`Status: ${response.provisioning.status}`, `Message: ${response.provisioning.message ?? '-'}`].join('\n'), 'Provisioning');
|
|
121
|
+
}
|
|
122
|
+
outro(response.message);
|
|
123
|
+
});
|
|
124
|
+
database
|
|
125
|
+
.command('list')
|
|
126
|
+
.description('List databases in the active organization')
|
|
127
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
128
|
+
.option('--search <text>', 'Filter database names')
|
|
129
|
+
.option('--limit <n>', 'Maximum number of rows to return')
|
|
130
|
+
.option('--json', 'Print raw JSON')
|
|
131
|
+
.addHelpText('after', [
|
|
132
|
+
'',
|
|
133
|
+
'Examples:',
|
|
134
|
+
' parix database list',
|
|
135
|
+
' parix database list --search payments',
|
|
136
|
+
' parix database list --json',
|
|
137
|
+
].join('\n'))
|
|
138
|
+
.action(async (options) => {
|
|
139
|
+
const query = new URLSearchParams();
|
|
140
|
+
if (options.search) {
|
|
141
|
+
query.set('search', options.search);
|
|
142
|
+
}
|
|
143
|
+
if (options.limit) {
|
|
144
|
+
query.set('limit', options.limit);
|
|
145
|
+
}
|
|
146
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : '';
|
|
147
|
+
const response = await requestApiJson({
|
|
148
|
+
baseUrl: options.baseUrl,
|
|
149
|
+
path: `/api/v1/databases${suffix}`,
|
|
150
|
+
});
|
|
151
|
+
if (options.json) {
|
|
152
|
+
printJson(response);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (response.databases.length === 0) {
|
|
156
|
+
outro('No databases found');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
printTable({
|
|
160
|
+
columns: ['ID', 'Name', 'Region', 'Provider', 'Status', 'Updated'],
|
|
161
|
+
rows: response.databases.map((database) => [
|
|
162
|
+
database.id,
|
|
163
|
+
database.name,
|
|
164
|
+
database.region ?? '-',
|
|
165
|
+
database.provider ?? '-',
|
|
166
|
+
database.status,
|
|
167
|
+
database.updatedAt,
|
|
168
|
+
]),
|
|
169
|
+
});
|
|
170
|
+
note(String(response.databases.length), 'Databases');
|
|
171
|
+
outro('Done');
|
|
172
|
+
});
|
|
173
|
+
database
|
|
174
|
+
.command('info')
|
|
175
|
+
.description('Show database details')
|
|
176
|
+
.argument('<database-id>', 'Database id')
|
|
177
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
178
|
+
.option('--json', 'Print raw JSON')
|
|
179
|
+
.addHelpText('after', ['', 'Examples:', ' parix database info db_123', ' parix database info db_123 --json'].join('\n'))
|
|
180
|
+
.action(async (databaseId, options) => {
|
|
181
|
+
const response = await requestApiJson({
|
|
182
|
+
baseUrl: options.baseUrl,
|
|
183
|
+
path: `/api/v1/databases/${encodeURIComponent(databaseId)}`,
|
|
184
|
+
});
|
|
185
|
+
if (options.json) {
|
|
186
|
+
printJson(response);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
printKeyValues([
|
|
190
|
+
['Database ID', response.database.id],
|
|
191
|
+
['Database Name', response.database.name],
|
|
192
|
+
['Provider', response.provider ?? '-'],
|
|
193
|
+
['Region', response.region ?? '-'],
|
|
194
|
+
['Gateway URL', response.gatewayUrl ?? '-'],
|
|
195
|
+
['Status', response.decommissionState?.status ?? response.profile?.status ?? '-'],
|
|
196
|
+
['Version', response.profile?.tigerbeetleCurrentVersion ?? response.profile?.tigerbeetleVersion ?? '-'],
|
|
197
|
+
['Nodes', response.profile ? String(response.profile.selectedNodeCount) : '-'],
|
|
198
|
+
['CPU', response.profile ? String(response.profile.selectedVcpu) : '-'],
|
|
199
|
+
['Memory GB', response.profile ? String(response.profile.selectedMemoryGb) : '-'],
|
|
200
|
+
['Storage GB', response.profile?.selectedStorageGb != null ? String(response.profile.selectedStorageGb) : '-'],
|
|
201
|
+
['Updated', response.database.updatedAt],
|
|
202
|
+
]);
|
|
203
|
+
if (response.metricsSummary) {
|
|
204
|
+
note([
|
|
205
|
+
`CPU: ${response.metricsSummary.cpuUsagePct ?? '-'}%`,
|
|
206
|
+
`Memory: ${response.metricsSummary.memoryUsagePct ?? '-'}%`,
|
|
207
|
+
`Disk: ${response.metricsSummary.diskUsagePct ?? '-'}%`,
|
|
208
|
+
`Gateway P95: ${response.metricsSummary.gatewayP95LatencyMs ?? '-'}ms`,
|
|
209
|
+
`Last collected: ${response.metricsSummary.lastCollectedAt ?? '-'}`,
|
|
210
|
+
].join('\n'), 'Metrics');
|
|
211
|
+
}
|
|
212
|
+
if (response.latestProvisionJob) {
|
|
213
|
+
note([
|
|
214
|
+
`Provision job: ${response.latestProvisionJob.id}`,
|
|
215
|
+
`Provision status: ${response.latestProvisionJob.status}`,
|
|
216
|
+
`Workflow ID: ${response.latestProvisionJob.workflowId ?? '-'}`,
|
|
217
|
+
].join('\n'), 'Latest provision job');
|
|
218
|
+
}
|
|
219
|
+
outro('Done');
|
|
220
|
+
});
|
|
221
|
+
database
|
|
222
|
+
.command('remove')
|
|
223
|
+
.description('Queue database deletion')
|
|
224
|
+
.argument('<database-id>', 'Database id')
|
|
225
|
+
.option('-b, --base-url <url>', 'Parix base URL')
|
|
226
|
+
.option('--json', 'Print raw JSON')
|
|
227
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
228
|
+
.addHelpText('after', ['', 'Examples:', ' parix database remove db_123', ' parix database remove db_123 --yes --json'].join('\n'))
|
|
229
|
+
.action(async (databaseId, options) => {
|
|
230
|
+
if (!options.yes) {
|
|
231
|
+
const confirmed = await confirm({
|
|
232
|
+
message: `Queue deletion for ${databaseId}?`,
|
|
233
|
+
});
|
|
234
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
235
|
+
outro('Cancelled');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const response = await requestApiJson({
|
|
240
|
+
baseUrl: options.baseUrl,
|
|
241
|
+
method: 'DELETE',
|
|
242
|
+
path: `/api/v1/databases/${encodeURIComponent(databaseId)}`,
|
|
243
|
+
});
|
|
244
|
+
if (options.json) {
|
|
245
|
+
printJson(response);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
printKeyValues([
|
|
249
|
+
['Database ID', response.databaseId],
|
|
250
|
+
['Status', response.status],
|
|
251
|
+
['Workflow ID', response.workflowId],
|
|
252
|
+
]);
|
|
253
|
+
outro(response.message);
|
|
254
|
+
});
|
|
255
|
+
return database;
|
|
256
|
+
}
|