@amerged/ohmyhost-mcp 0.1.43 → 0.1.45
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 +1 -1
- package/dist/{chunk-ACMEMS6N.js → chunk-AJV4T4LM.js} +7 -7
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +285 -19
- package/package.json +1 -1
- package/src/apps/mcp/package.json +1 -1
- package/src/apps/mcp/src/local-client.ts +60 -0
- package/src/apps/mcp/src/local-server.ts +114 -7
- package/src/apps/product-cli/package.json +1 -1
- package/src/apps/product-cli/src/cli.ts +64 -4
- package/src/apps/product-cli/src/command.ts +123 -2
- package/src/apps/product-cli/src/product-api.ts +86 -2
- package/src/packages/agent-skills/src/generated-skill-resources.ts +6 -6
- package/src/packages/contracts/src/database-events.ts +208 -11
- package/src/packages/contracts/src/database-migration-admission.ts +22 -0
- package/src/packages/contracts/src/database-migration-capture.ts +177 -0
- package/src/packages/contracts/src/database-migration-lexical.ts +1 -0
- package/src/packages/contracts/src/operation-failure.ts +4 -4
- package/src/packages/contracts/src/problem-policy.ts +36 -1
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/sdk-ts/src/generated/index.ts +25 -0
- package/src/packages/sdk-ts/src/generated/sdk.gen.ts +153 -3
- package/src/packages/sdk-ts/src/generated/types.gen.ts +274 -4
|
@@ -8,16 +8,22 @@ import {
|
|
|
8
8
|
DATABASE_SCHEDULE_MIN_EVERY_SECONDS,
|
|
9
9
|
DATABASE_SCHEDULE_MIN_TIMEOUT_MS,
|
|
10
10
|
DATABASE_SCHEDULE_STATEMENT_MAX_BYTES,
|
|
11
|
+
DATABASE_WATCH_OPERATIONS,
|
|
12
|
+
isDatabaseWatchSchema,
|
|
13
|
+
isDatabaseWebhookEventPattern,
|
|
11
14
|
type DatabaseExecutionUsagePage,
|
|
12
15
|
type DatabaseSchedule,
|
|
13
16
|
type DatabaseSchedulePage,
|
|
14
17
|
type DatabaseScheduleDeleted,
|
|
15
18
|
type DatabaseScheduleRunAccepted,
|
|
16
19
|
type DatabaseScheduleRunPage,
|
|
20
|
+
type DatabaseWatch,
|
|
21
|
+
type DatabaseWatchDeleted,
|
|
22
|
+
type DatabaseWatchPage,
|
|
17
23
|
type DatabaseWebhookCreated,
|
|
18
24
|
type DatabaseWebhookDeleted,
|
|
19
25
|
type DatabaseWebhookDeliveryPage,
|
|
20
|
-
type
|
|
26
|
+
type DatabaseWebhookEventPattern,
|
|
21
27
|
type DatabaseWebhookPage,
|
|
22
28
|
type DatabaseWebhookReplayAccepted,
|
|
23
29
|
type DatabaseWebhookTestResult,
|
|
@@ -323,11 +329,30 @@ export interface LocalMcpProductClient {
|
|
|
323
329
|
readonly projectId: string;
|
|
324
330
|
readonly environment?: DatabaseEnvironment;
|
|
325
331
|
}): Promise<DatabaseWebhookPage>;
|
|
332
|
+
listDatabaseWatches(input: {
|
|
333
|
+
readonly projectId: string;
|
|
334
|
+
readonly environment?: DatabaseEnvironment;
|
|
335
|
+
}): Promise<DatabaseWatchPage>;
|
|
336
|
+
createDatabaseWatch(input: {
|
|
337
|
+
readonly projectId: string;
|
|
338
|
+
readonly environment: DatabaseEnvironment;
|
|
339
|
+
readonly schema?: string;
|
|
340
|
+
readonly table: string;
|
|
341
|
+
readonly columns: readonly string[];
|
|
342
|
+
readonly operations?: readonly (typeof DATABASE_WATCH_OPERATIONS)[number][];
|
|
343
|
+
readonly idempotencyKey: string;
|
|
344
|
+
}): Promise<DatabaseWatch>;
|
|
345
|
+
deleteDatabaseWatch(input: {
|
|
346
|
+
readonly projectId: string;
|
|
347
|
+
readonly environment: DatabaseEnvironment;
|
|
348
|
+
readonly watchId: string;
|
|
349
|
+
readonly idempotencyKey: string;
|
|
350
|
+
}): Promise<DatabaseWatchDeleted>;
|
|
326
351
|
createDatabaseWebhook(input: {
|
|
327
352
|
readonly projectId: string;
|
|
328
353
|
readonly environment: DatabaseEnvironment;
|
|
329
354
|
readonly url: string;
|
|
330
|
-
readonly eventTypes: readonly
|
|
355
|
+
readonly eventTypes: readonly DatabaseWebhookEventPattern[];
|
|
331
356
|
readonly headers: Readonly<Record<string, string>>;
|
|
332
357
|
readonly idempotencyKey: string;
|
|
333
358
|
}): Promise<DatabaseWebhookCreated>;
|
|
@@ -539,6 +564,8 @@ interface ProjectDeleteInput {
|
|
|
539
564
|
const identifier = z.string().min(1).max(256);
|
|
540
565
|
const databaseEnvironment = z.enum(["dev", "prod"]);
|
|
541
566
|
const databaseWebhookId = z.string().regex(/^whk_[a-z0-9]{20}$/u);
|
|
567
|
+
const databaseWatchId = z.string().regex(/^pgcf_watch_[0-9a-f]{8}$/u);
|
|
568
|
+
const databaseIdentifier = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,62}$/u);
|
|
542
569
|
const databaseDeliveryId = z.string().regex(/^dlv_[0-9a-f]{20}$/u);
|
|
543
570
|
const databaseScheduleName = z.string().regex(/^[a-z][a-z0-9_-]{0,62}$/u);
|
|
544
571
|
/** Every product tool accepts the saved login to act as; it is an alias, never a credential. */
|
|
@@ -1122,6 +1149,75 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1122
1149
|
client.revokeDatabaseAccess({ projectId: input.project_id, accessId: input.access_id }),
|
|
1123
1150
|
destructiveAnnotations(),
|
|
1124
1151
|
);
|
|
1152
|
+
registerProductTool(
|
|
1153
|
+
server,
|
|
1154
|
+
dependencies,
|
|
1155
|
+
"database_watch_list",
|
|
1156
|
+
"List this project's database watches of one Dev or Prod environment (dev when omitted). Each watch shows its table, columns, operations and the event type data.<topic> it publishes. Listing wakes a sleeping database.",
|
|
1157
|
+
z.object({ project_id: identifier, environment: databaseEnvironment.optional() }),
|
|
1158
|
+
(client, input) =>
|
|
1159
|
+
client.listDatabaseWatches({
|
|
1160
|
+
projectId: input.project_id,
|
|
1161
|
+
...(input.environment === undefined ? {} : { environment: input.environment }),
|
|
1162
|
+
}),
|
|
1163
|
+
);
|
|
1164
|
+
registerProductTool(
|
|
1165
|
+
server,
|
|
1166
|
+
dependencies,
|
|
1167
|
+
"database_watch_create",
|
|
1168
|
+
"Publish row changes of one table of a Dev or Prod database as data events: only the listed columns (at most 32) of each insert, update or delete are published, with the event type data.<topic> returned by this call. Pass that event type to database_webhook_create. The table must exist as an ordinary table of the application, created by a deployed migration; a sleeping database wakes. Rows written before the watch exists are not published, and a published column must not be dropped while the watch exists. Requires confirmed: true and an idempotency key. Refused with project_budget_exceeded while the Stop budget is reached.",
|
|
1169
|
+
z.object({
|
|
1170
|
+
project_id: identifier,
|
|
1171
|
+
environment: databaseEnvironment,
|
|
1172
|
+
schema: databaseIdentifier.refine(isDatabaseWatchSchema).optional(),
|
|
1173
|
+
table: databaseIdentifier,
|
|
1174
|
+
columns: z
|
|
1175
|
+
.array(databaseIdentifier)
|
|
1176
|
+
.min(1)
|
|
1177
|
+
.max(32)
|
|
1178
|
+
.refine((columns) => new Set(columns).size === columns.length),
|
|
1179
|
+
operations: z
|
|
1180
|
+
.array(z.enum(DATABASE_WATCH_OPERATIONS))
|
|
1181
|
+
.min(1)
|
|
1182
|
+
.max(3)
|
|
1183
|
+
.refine((operations) => new Set(operations).size === operations.length)
|
|
1184
|
+
.optional(),
|
|
1185
|
+
idempotency_key: idempotencyKey,
|
|
1186
|
+
confirmed: z.literal(true),
|
|
1187
|
+
}),
|
|
1188
|
+
(client, input) =>
|
|
1189
|
+
client.createDatabaseWatch({
|
|
1190
|
+
projectId: input.project_id,
|
|
1191
|
+
environment: input.environment,
|
|
1192
|
+
...(input.schema === undefined ? {} : { schema: input.schema }),
|
|
1193
|
+
table: input.table,
|
|
1194
|
+
columns: input.columns,
|
|
1195
|
+
...(input.operations === undefined ? {} : { operations: input.operations }),
|
|
1196
|
+
idempotencyKey: input.idempotency_key,
|
|
1197
|
+
}),
|
|
1198
|
+
mutationAnnotations(),
|
|
1199
|
+
);
|
|
1200
|
+
registerProductTool(
|
|
1201
|
+
server,
|
|
1202
|
+
dependencies,
|
|
1203
|
+
"database_watch_delete",
|
|
1204
|
+
"Remove the capture trigger of one database watch: its table publishes no more data events. Destructive: requires confirmed: true and an idempotency key. Deleting an already deleted watch answers not found.",
|
|
1205
|
+
z.object({
|
|
1206
|
+
project_id: identifier,
|
|
1207
|
+
environment: databaseEnvironment,
|
|
1208
|
+
watch_id: databaseWatchId,
|
|
1209
|
+
idempotency_key: idempotencyKey,
|
|
1210
|
+
confirmed: z.literal(true),
|
|
1211
|
+
}),
|
|
1212
|
+
(client, input) =>
|
|
1213
|
+
client.deleteDatabaseWatch({
|
|
1214
|
+
projectId: input.project_id,
|
|
1215
|
+
environment: input.environment,
|
|
1216
|
+
watchId: input.watch_id,
|
|
1217
|
+
idempotencyKey: input.idempotency_key,
|
|
1218
|
+
}),
|
|
1219
|
+
destructiveAnnotations(),
|
|
1220
|
+
);
|
|
1125
1221
|
registerProductTool(
|
|
1126
1222
|
server,
|
|
1127
1223
|
dependencies,
|
|
@@ -1138,7 +1234,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1138
1234
|
server,
|
|
1139
1235
|
dependencies,
|
|
1140
1236
|
"database_webhook_create",
|
|
1141
|
-
"Subscribe one HTTPS receiver to database lifecycle events (database.created, ready, woke, slept, suspended, resized, deleted) of one Dev or Prod database. The signing secret is RETURNED EXACTLY ONCE: store it in a secret manager and never write it into files, notes, source, commit messages or later messages. Deliveries are signed, retried and dead-lettered when retries stop. At most five webhooks exist per project environment. Requires confirmed: true and an idempotency key; the same key with the same request returns the stored answer.",
|
|
1237
|
+
"Subscribe one HTTPS receiver to database lifecycle events (database.created, ready, woke, slept, suspended, resized, deleted) of one Dev or Prod database, or to data events: data.<topic> for the rows of one watched table (the event type database_watch_create returns) or data.* for every watched table; a bare * never matches data events. The signing secret is RETURNED EXACTLY ONCE: store it in a secret manager and never write it into files, notes, source, commit messages or later messages. Deliveries are signed, retried and dead-lettered when retries stop. At most five webhooks exist per project environment. Requires confirmed: true and an idempotency key; the same key with the same request returns the stored answer.",
|
|
1142
1238
|
z.object({
|
|
1143
1239
|
project_id: identifier,
|
|
1144
1240
|
environment: databaseEnvironment,
|
|
@@ -1146,7 +1242,18 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1146
1242
|
.string()
|
|
1147
1243
|
.max(2048)
|
|
1148
1244
|
.regex(/^https:\/\//u),
|
|
1149
|
-
event_types: z
|
|
1245
|
+
event_types: z
|
|
1246
|
+
.array(
|
|
1247
|
+
z.union([
|
|
1248
|
+
z.enum(DATABASE_WEBHOOK_EVENT_TYPES),
|
|
1249
|
+
z
|
|
1250
|
+
.string()
|
|
1251
|
+
.regex(/^data\.(?:\*|[a-z][a-z0-9_]{0,47})$/u)
|
|
1252
|
+
.refine(isDatabaseWebhookEventPattern),
|
|
1253
|
+
]),
|
|
1254
|
+
)
|
|
1255
|
+
.min(1)
|
|
1256
|
+
.max(16),
|
|
1150
1257
|
headers: z.record(z.string(), z.string()).optional(),
|
|
1151
1258
|
idempotency_key: idempotencyKey,
|
|
1152
1259
|
confirmed: z.literal(true),
|
|
@@ -1254,7 +1361,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1254
1361
|
server,
|
|
1255
1362
|
dependencies,
|
|
1256
1363
|
"database_schedule_set",
|
|
1257
|
-
"Create or replace a named database schedule that runs SQL statements repeatedly on a timer, as the application's own runtime role, so row-level security applies. Send exactly one of cron (five-field UTC expression) or every_seconds (60 to 2678400). Each run is cancelled after timeout_ms (1000 to 30000, default 10000). Statements run in order and must be safe to repeat: write idempotent statements. Destructive: requires confirmed: true.",
|
|
1364
|
+
"Create or replace a named database schedule that runs SQL statements repeatedly on a timer, as the application's own runtime role, so row-level security applies. Send exactly one of cron (five-field UTC expression) or every_seconds (60 to 2678400). Each run is cancelled after timeout_ms (1000 to 30000, default 10000). Statements run in order and must be safe to repeat: write idempotent statements. Destructive: requires confirmed: true. A project whose Stop budget is reached has its schedules disabled automatically, within about an hour of the billed usage reaching the budget: the credit service does this after its hourly pgcf pass, and Continue budgets are never touched. While the budget is reached, schedule set with enabled true and schedule run are refused with project_budget_exceeded (HTTP 402). Re-enabling is manual: raise the budget (or wait for the next UTC month) and run database schedule set --enabled true again with the schedule body (read it with schedule get).",
|
|
1258
1365
|
z
|
|
1259
1366
|
.object({
|
|
1260
1367
|
project_id: identifier,
|
|
@@ -1339,7 +1446,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1339
1446
|
server,
|
|
1340
1447
|
dependencies,
|
|
1341
1448
|
"database_schedule_run_now",
|
|
1342
|
-
"Queue one immediate run of a database schedule. Refused while the schedule is disabled or a run is already queued. Destructive: the statements execute against the database; requires confirmed: true. If the answer is lost, read database_schedule_run_list before calling again; a second call queues another run.",
|
|
1449
|
+
"Queue one immediate run of a database schedule. Refused while the schedule is disabled or a run is already queued. Destructive: the statements execute against the database; requires confirmed: true. If the answer is lost, read database_schedule_run_list before calling again; a second call queues another run. Refused with project_budget_exceeded while the project's Stop budget is reached.",
|
|
1343
1450
|
z.object({
|
|
1344
1451
|
project_id: identifier,
|
|
1345
1452
|
environment: databaseEnvironment,
|
|
@@ -1358,7 +1465,7 @@ export function createLocalOhmyhostMcpServer(dependencies: LocalOhmyhostMcpDepen
|
|
|
1358
1465
|
server,
|
|
1359
1466
|
dependencies,
|
|
1360
1467
|
"database_execution_usage_get",
|
|
1361
|
-
"Read metered database execution usage: schedule runs and webhook attempts per UTC hour or day window. from and to are UTC timestamps with milliseconds (Z), aligned to the granularity, at most 31 days apart. Only completed windows are final.
|
|
1468
|
+
"Read metered database execution usage: schedule runs and webhook attempts per UTC hour or day window. from and to are UTC timestamps with milliseconds (Z), aligned to the granularity, at most 31 days apart. Only completed windows are final. The counts are counters only: executions have no price of their own, and the awake compute time of the database is what is billed.",
|
|
1362
1469
|
z.object({
|
|
1363
1470
|
project_id: identifier,
|
|
1364
1471
|
environment: databaseEnvironment.optional(),
|
|
@@ -419,8 +419,14 @@ const HELP_USAGE = Object.freeze({
|
|
|
419
419
|
"ohmyhost database access list --project ULID [--environment dev|prod] --json",
|
|
420
420
|
"database access revoke":
|
|
421
421
|
"ohmyhost database access revoke --project ULID --access ULID --yes --json",
|
|
422
|
+
"database watch create":
|
|
423
|
+
"ohmyhost database watch create --project ULID --environment dev|prod --table NAME [--schema public] --columns COL[,COL] [--operations insert,update,delete] --idempotency-key KEY --yes --json (publishes only the listed columns, at most 32, of each row change as the event type data.<topic> printed in this answer; the table must exist as an ordinary table of the application; a sleeping database wakes; next, subscribe with 'ohmyhost database webhook create ... --event-types data.<topic>')",
|
|
424
|
+
"database watch list":
|
|
425
|
+
"ohmyhost database watch list --project ULID [--environment dev|prod] --json (dev when omitted; a sleeping database wakes)",
|
|
426
|
+
"database watch delete":
|
|
427
|
+
"ohmyhost database watch delete --project ULID --environment dev|prod --watch ID --idempotency-key KEY --yes --json",
|
|
422
428
|
"database webhook create":
|
|
423
|
-
"ohmyhost database webhook create --project ULID --environment dev|prod --url HTTPS_URL --event-types TYPE[,TYPE] [--headers-file PATH] --idempotency-key KEY --yes --json (the signing secret is shown once in this answer; types: database.created, database.ready, database.woke, database.slept, database.suspended, database.resized, database.deleted; the headers file is a UTF-8 JSON object of at most 8 KiB)",
|
|
429
|
+
"ohmyhost database webhook create --project ULID --environment dev|prod --url HTTPS_URL --event-types TYPE[,TYPE] [--headers-file PATH] --idempotency-key KEY --yes --json (the signing secret is shown once in this answer; types: database.created, database.ready, database.woke, database.slept, database.suspended, database.resized, database.deleted, data.<topic> for the rows of one watched table printed by database watch create, or data.* for every watched table; a bare * is not accepted and does not include data events; the headers file is a UTF-8 JSON object of at most 8 KiB)",
|
|
424
430
|
"database webhook list":
|
|
425
431
|
"ohmyhost database webhook list --project ULID [--environment dev|prod] --json (dev when omitted; the secret is never listed)",
|
|
426
432
|
"database webhook get":
|
|
@@ -434,7 +440,7 @@ const HELP_USAGE = Object.freeze({
|
|
|
434
440
|
"database webhook replay":
|
|
435
441
|
"ohmyhost database webhook replay --project ULID --environment dev|prod --delivery ID --yes --json",
|
|
436
442
|
"database schedule set":
|
|
437
|
-
"ohmyhost database schedule set --project ULID --environment dev|prod --name NAME (--cron EXPR | --every-seconds N) --statements-file PATH [--timeout-ms N] [--enabled true|false] --yes --json (runs the statements in the file, a UTF-8 JSON array of 1 to 20 SQL strings, as the runtime role of the application; cron has five fields in UTC; every-seconds is 60 to 2678400; timeout-ms is 1000 to 30000, default 10000)",
|
|
443
|
+
"ohmyhost database schedule set --project ULID --environment dev|prod --name NAME (--cron EXPR | --every-seconds N) --statements-file PATH [--timeout-ms N] [--enabled true|false] --yes --json (runs the statements in the file, a UTF-8 JSON array of 1 to 20 SQL strings, as the runtime role of the application; cron has five fields in UTC; every-seconds is 60 to 2678400; timeout-ms is 1000 to 30000, default 10000). A project whose Stop budget is reached has its schedules disabled automatically, within about an hour of the billed usage reaching the budget: the credit service does this after its hourly pgcf pass, and Continue budgets are never touched. While the budget is reached, schedule set with enabled true and schedule run are refused with project_budget_exceeded (HTTP 402). Re-enabling is manual: raise the budget (or wait for the next UTC month) and run database schedule set --enabled true again with the schedule body (read it with schedule get).",
|
|
438
444
|
"database schedule list":
|
|
439
445
|
"ohmyhost database schedule list --project ULID [--environment dev|prod] --json (dev when omitted)",
|
|
440
446
|
"database schedule get":
|
|
@@ -444,9 +450,9 @@ const HELP_USAGE = Object.freeze({
|
|
|
444
450
|
"database schedule runs":
|
|
445
451
|
"ohmyhost database schedule runs --project ULID [--environment dev|prod] --name NAME [--limit 1..50] --json",
|
|
446
452
|
"database schedule run":
|
|
447
|
-
"ohmyhost database schedule run --project ULID --environment dev|prod --name NAME --yes --json (queues one run now, in addition to the timer)",
|
|
453
|
+
"ohmyhost database schedule run --project ULID --environment dev|prod --name NAME --yes --json (queues one run now, in addition to the timer; refused with project_budget_exceeded (HTTP 402) while the project's Stop budget is reached)",
|
|
448
454
|
"database executions":
|
|
449
|
-
"ohmyhost database executions --project ULID [--environment dev|prod] --from ISO --to ISO --granularity hour|day [--limit 1..100] [--cursor CURSOR] --json (UTC timestamps like 2026-10-30T00:00:00.000Z aligned to the granularity, at most 31 days; counts schedule runs, webhook attempts and events per hour or day;
|
|
455
|
+
"ohmyhost database executions --project ULID [--environment dev|prod] --from ISO --to ISO --granularity hour|day [--limit 1..100] [--cursor CURSOR] --json (UTC timestamps like 2026-10-30T00:00:00.000Z aligned to the granularity, at most 31 days; counts schedule runs, webhook attempts and events per hour or day; these are counters only, executions have no price of their own and the awake compute time of the database is what is billed)",
|
|
450
456
|
"database psql":
|
|
451
457
|
"ohmyhost database psql --project ULID --environment dev|prod [--mode read|write] [--ttl 5m|1h|24h|SECONDS] [--json] (starts local psql with a temporary credential and revokes it on exit)",
|
|
452
458
|
link: "ohmyhost link --project ULID --repository-owner OWNER --repository-name REPOSITORY --idempotency-key KEY --json (uses the workspace GitHub connection and waits for the source-link operation)",
|
|
@@ -1191,6 +1197,12 @@ export class ProductCli {
|
|
|
1191
1197
|
return await this.listDatabaseAccess(command, signal);
|
|
1192
1198
|
if (command.kind === "database-access-revoke")
|
|
1193
1199
|
return await this.revokeDatabaseAccess(command, signal);
|
|
1200
|
+
if (command.kind === "database-watch-create")
|
|
1201
|
+
return await this.createDatabaseWatch(command, signal);
|
|
1202
|
+
if (command.kind === "database-watch-list")
|
|
1203
|
+
return await this.listDatabaseWatches(command, signal);
|
|
1204
|
+
if (command.kind === "database-watch-delete")
|
|
1205
|
+
return await this.deleteDatabaseWatch(command, signal);
|
|
1194
1206
|
if (command.kind === "database-webhook-create")
|
|
1195
1207
|
return await this.createDatabaseWebhook(command, signal);
|
|
1196
1208
|
if (command.kind === "database-webhook-list")
|
|
@@ -1952,6 +1964,51 @@ export class ProductCli {
|
|
|
1952
1964
|
return success("database access revoke", { status: "succeeded", access });
|
|
1953
1965
|
}
|
|
1954
1966
|
|
|
1967
|
+
/** The answer names the event type to subscribe to; the watch already carries the columns and the topic. */
|
|
1968
|
+
private async createDatabaseWatch(
|
|
1969
|
+
command: Extract<ProductCliCommand, { kind: "database-watch-create" }>,
|
|
1970
|
+
signal: AbortSignal,
|
|
1971
|
+
): Promise<CliResult> {
|
|
1972
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1973
|
+
const watch = await api.createProjectDatabaseWatch({
|
|
1974
|
+
projectId: command.projectId,
|
|
1975
|
+
environment: command.environment,
|
|
1976
|
+
idempotencyKey: command.idempotencyKey,
|
|
1977
|
+
request: command.request,
|
|
1978
|
+
});
|
|
1979
|
+
return success("database watch create", {
|
|
1980
|
+
status: "succeeded",
|
|
1981
|
+
watch,
|
|
1982
|
+
next_action: `Run '${this.dependencies.commandPrefix} database webhook create --project ${command.projectId} --environment ${command.environment} --url HTTPS_URL --event-types ${watch.event_type} --idempotency-key KEY --yes --json' to receive ${watch.event_type} events at an HTTPS receiver.`,
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
private async listDatabaseWatches(
|
|
1987
|
+
command: Extract<ProductCliCommand, { kind: "database-watch-list" }>,
|
|
1988
|
+
signal: AbortSignal,
|
|
1989
|
+
): Promise<CliResult> {
|
|
1990
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
1991
|
+
const page = await api.listProjectDatabaseWatches({
|
|
1992
|
+
projectId: command.projectId,
|
|
1993
|
+
environment: command.environment,
|
|
1994
|
+
});
|
|
1995
|
+
return success("database watch list", { status: "succeeded", watch: page.items });
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
private async deleteDatabaseWatch(
|
|
1999
|
+
command: Extract<ProductCliCommand, { kind: "database-watch-delete" }>,
|
|
2000
|
+
signal: AbortSignal,
|
|
2001
|
+
): Promise<CliResult> {
|
|
2002
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
2003
|
+
const deleted = await api.deleteProjectDatabaseWatch({
|
|
2004
|
+
projectId: command.projectId,
|
|
2005
|
+
environment: command.environment,
|
|
2006
|
+
idempotencyKey: command.idempotencyKey,
|
|
2007
|
+
watchId: command.watchId,
|
|
2008
|
+
});
|
|
2009
|
+
return success("database watch delete", { status: "succeeded", deleted });
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1955
2012
|
/** The signing secret is returned by create only; every other answer carries no secret. */
|
|
1956
2013
|
private async createDatabaseWebhook(
|
|
1957
2014
|
command: Extract<ProductCliCommand, { kind: "database-webhook-create" }>,
|
|
@@ -3526,6 +3583,9 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
|
|
|
3526
3583
|
if (command.kind === "database-access-list") return "database access list";
|
|
3527
3584
|
if (command.kind === "database-access-revoke") return "database access revoke";
|
|
3528
3585
|
if (command.kind === "database-psql") return "database psql";
|
|
3586
|
+
if (command.kind === "database-watch-create") return "database watch create";
|
|
3587
|
+
if (command.kind === "database-watch-list") return "database watch list";
|
|
3588
|
+
if (command.kind === "database-watch-delete") return "database watch delete";
|
|
3529
3589
|
if (command.kind === "database-webhook-create") return "database webhook create";
|
|
3530
3590
|
if (command.kind === "database-webhook-list") return "database webhook list";
|
|
3531
3591
|
if (command.kind === "database-webhook-get") return "database webhook get";
|
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
parseDatabaseListLimit,
|
|
7
7
|
parseDatabaseScheduleName,
|
|
8
8
|
parseDatabaseUsageWindow,
|
|
9
|
+
parseDatabaseWatchCreateRequest,
|
|
10
|
+
parseDatabaseWatchId,
|
|
9
11
|
parseDatabaseWebhookCreateRequest,
|
|
10
12
|
parseDatabaseWebhookId,
|
|
11
|
-
type
|
|
13
|
+
type DatabaseWatchRequest,
|
|
14
|
+
type DatabaseWebhookEventPattern,
|
|
12
15
|
} from "@ohmyhost/contracts/database-events";
|
|
13
16
|
import {
|
|
14
17
|
databaseSizeForMemory,
|
|
@@ -404,12 +407,36 @@ export type ProductCliCommand =
|
|
|
404
407
|
readonly yes: true;
|
|
405
408
|
readonly credentialStore: "native";
|
|
406
409
|
}
|
|
410
|
+
| {
|
|
411
|
+
readonly kind: "database-watch-create";
|
|
412
|
+
readonly projectId: string;
|
|
413
|
+
readonly environment: "dev" | "prod";
|
|
414
|
+
readonly request: DatabaseWatchRequest;
|
|
415
|
+
readonly idempotencyKey: string;
|
|
416
|
+
readonly yes: true;
|
|
417
|
+
readonly credentialStore: "native";
|
|
418
|
+
}
|
|
419
|
+
| {
|
|
420
|
+
readonly kind: "database-watch-list";
|
|
421
|
+
readonly projectId: string;
|
|
422
|
+
readonly environment: "dev" | "prod";
|
|
423
|
+
readonly credentialStore: "native";
|
|
424
|
+
}
|
|
425
|
+
| {
|
|
426
|
+
readonly kind: "database-watch-delete";
|
|
427
|
+
readonly projectId: string;
|
|
428
|
+
readonly environment: "dev" | "prod";
|
|
429
|
+
readonly watchId: string;
|
|
430
|
+
readonly idempotencyKey: string;
|
|
431
|
+
readonly yes: true;
|
|
432
|
+
readonly credentialStore: "native";
|
|
433
|
+
}
|
|
407
434
|
| {
|
|
408
435
|
readonly kind: "database-webhook-create";
|
|
409
436
|
readonly projectId: string;
|
|
410
437
|
readonly environment: "dev" | "prod";
|
|
411
438
|
readonly url: string;
|
|
412
|
-
readonly eventTypes: readonly
|
|
439
|
+
readonly eventTypes: readonly DatabaseWebhookEventPattern[];
|
|
413
440
|
readonly headersFile: string | null;
|
|
414
441
|
readonly idempotencyKey: string;
|
|
415
442
|
readonly yes: true;
|
|
@@ -1003,6 +1030,9 @@ export type RecognizedCommandLabel =
|
|
|
1003
1030
|
| "database access create"
|
|
1004
1031
|
| "database access list"
|
|
1005
1032
|
| "database access revoke"
|
|
1033
|
+
| "database watch create"
|
|
1034
|
+
| "database watch list"
|
|
1035
|
+
| "database watch delete"
|
|
1006
1036
|
| "database webhook create"
|
|
1007
1037
|
| "database webhook list"
|
|
1008
1038
|
| "database webhook get"
|
|
@@ -1174,6 +1204,11 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
1174
1204
|
if (values[1] === "list") return "database access list";
|
|
1175
1205
|
if (values[1] === "revoke") return "database access revoke";
|
|
1176
1206
|
}
|
|
1207
|
+
if (value === "database" && values[0] === "watch") {
|
|
1208
|
+
if (values[1] === "create") return "database watch create";
|
|
1209
|
+
if (values[1] === "list") return "database watch list";
|
|
1210
|
+
if (values[1] === "delete") return "database watch delete";
|
|
1211
|
+
}
|
|
1177
1212
|
if (value === "database" && values[0] === "webhook") {
|
|
1178
1213
|
if (values[1] === "create") return "database webhook create";
|
|
1179
1214
|
if (values[1] === "list") return "database webhook list";
|
|
@@ -2063,6 +2098,92 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
2063
2098
|
}
|
|
2064
2099
|
throw new InvalidCommandError();
|
|
2065
2100
|
}
|
|
2101
|
+
if (first === "database" && values[0] === "watch") {
|
|
2102
|
+
values.shift();
|
|
2103
|
+
const subcommand = values.shift();
|
|
2104
|
+
if (subcommand === "create") {
|
|
2105
|
+
if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
|
|
2106
|
+
const options = parseOptionalOptions(values, [
|
|
2107
|
+
"--project",
|
|
2108
|
+
"--environment",
|
|
2109
|
+
"--table",
|
|
2110
|
+
"--schema",
|
|
2111
|
+
"--columns",
|
|
2112
|
+
"--operations",
|
|
2113
|
+
"--idempotency-key",
|
|
2114
|
+
]);
|
|
2115
|
+
const projectId = options["--project"],
|
|
2116
|
+
environment = options["--environment"],
|
|
2117
|
+
table = options["--table"],
|
|
2118
|
+
schema = options["--schema"],
|
|
2119
|
+
columns = options["--columns"],
|
|
2120
|
+
operations = options["--operations"],
|
|
2121
|
+
idempotencyKey = options["--idempotency-key"];
|
|
2122
|
+
if (
|
|
2123
|
+
!isValue(projectId) ||
|
|
2124
|
+
!isValue(idempotencyKey) ||
|
|
2125
|
+
!isValue(table) ||
|
|
2126
|
+
!isValue(columns) ||
|
|
2127
|
+
(environment !== "dev" && environment !== "prod")
|
|
2128
|
+
)
|
|
2129
|
+
throw new InvalidCommandError();
|
|
2130
|
+
const request = contractValue(() =>
|
|
2131
|
+
parseDatabaseWatchCreateRequest({
|
|
2132
|
+
schema,
|
|
2133
|
+
table,
|
|
2134
|
+
columns: columns.split(","),
|
|
2135
|
+
...(operations === undefined ? {} : { operations: operations.split(",") }),
|
|
2136
|
+
}),
|
|
2137
|
+
);
|
|
2138
|
+
return {
|
|
2139
|
+
kind: "database-watch-create",
|
|
2140
|
+
projectId,
|
|
2141
|
+
environment,
|
|
2142
|
+
request,
|
|
2143
|
+
idempotencyKey,
|
|
2144
|
+
yes: true,
|
|
2145
|
+
credentialStore,
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
if (subcommand === "list") {
|
|
2149
|
+
if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
|
|
2150
|
+
const options = parseOptionalOptions(values, ["--project", "--environment"]);
|
|
2151
|
+
const projectId = options["--project"],
|
|
2152
|
+
environment = options["--environment"] ?? "dev";
|
|
2153
|
+
if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
|
|
2154
|
+
throw new InvalidCommandError();
|
|
2155
|
+
return { kind: "database-watch-list", projectId, environment, credentialStore };
|
|
2156
|
+
}
|
|
2157
|
+
if (subcommand === "delete") {
|
|
2158
|
+
if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
|
|
2159
|
+
const options = parseOptionalOptions(values, [
|
|
2160
|
+
"--project",
|
|
2161
|
+
"--environment",
|
|
2162
|
+
"--watch",
|
|
2163
|
+
"--idempotency-key",
|
|
2164
|
+
]);
|
|
2165
|
+
const projectId = options["--project"],
|
|
2166
|
+
environment = options["--environment"],
|
|
2167
|
+
watchId = options["--watch"],
|
|
2168
|
+
idempotencyKey = options["--idempotency-key"];
|
|
2169
|
+
if (
|
|
2170
|
+
!isValue(projectId) ||
|
|
2171
|
+
!isValue(idempotencyKey) ||
|
|
2172
|
+
(environment !== "dev" && environment !== "prod")
|
|
2173
|
+
)
|
|
2174
|
+
throw new InvalidCommandError();
|
|
2175
|
+
return {
|
|
2176
|
+
kind: "database-watch-delete",
|
|
2177
|
+
projectId,
|
|
2178
|
+
environment,
|
|
2179
|
+
watchId: contractValue(() => parseDatabaseWatchId(watchId)),
|
|
2180
|
+
idempotencyKey,
|
|
2181
|
+
yes: true,
|
|
2182
|
+
credentialStore,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
throw new InvalidCommandError();
|
|
2186
|
+
}
|
|
2066
2187
|
if (first === "database" && values[0] === "webhook") {
|
|
2067
2188
|
values.shift();
|
|
2068
2189
|
const subcommand = values.shift();
|
|
@@ -33,6 +33,9 @@ import {
|
|
|
33
33
|
assertDatabaseWebhookDeliveryPage,
|
|
34
34
|
assertDatabaseWebhookPage,
|
|
35
35
|
assertDatabaseWebhookReplayAccepted,
|
|
36
|
+
assertDatabaseWatch,
|
|
37
|
+
assertDatabaseWatchDeleted,
|
|
38
|
+
assertDatabaseWatchPage,
|
|
36
39
|
assertDatabaseWebhookTest,
|
|
37
40
|
type DatabaseExecutionUsagePage,
|
|
38
41
|
type DatabaseSchedule,
|
|
@@ -45,7 +48,11 @@ import {
|
|
|
45
48
|
type DatabaseWebhookCreated,
|
|
46
49
|
type DatabaseWebhookDeleted,
|
|
47
50
|
type DatabaseWebhookDeliveryPage,
|
|
48
|
-
type
|
|
51
|
+
type DatabaseWatch,
|
|
52
|
+
type DatabaseWatchDeleted,
|
|
53
|
+
type DatabaseWatchPage,
|
|
54
|
+
type DatabaseWatchRequest,
|
|
55
|
+
type DatabaseWebhookEventPattern,
|
|
49
56
|
type DatabaseWebhookPage,
|
|
50
57
|
type DatabaseWebhookReplayAccepted,
|
|
51
58
|
type DatabaseWebhookTestResult,
|
|
@@ -152,6 +159,9 @@ import {
|
|
|
152
159
|
getProjectDatabaseWebhook,
|
|
153
160
|
deleteProjectDatabaseWebhook,
|
|
154
161
|
testProjectDatabaseWebhook,
|
|
162
|
+
createProjectDatabaseWatch,
|
|
163
|
+
listProjectDatabaseWatches,
|
|
164
|
+
deleteProjectDatabaseWatch,
|
|
155
165
|
listProjectDatabaseWebhookDeliveries,
|
|
156
166
|
replayProjectDatabaseWebhookDelivery,
|
|
157
167
|
setProjectDatabaseSchedule,
|
|
@@ -328,7 +338,7 @@ export interface ProductApi {
|
|
|
328
338
|
readonly environment: "dev" | "prod";
|
|
329
339
|
readonly idempotencyKey: string;
|
|
330
340
|
readonly url: string;
|
|
331
|
-
readonly eventTypes: readonly
|
|
341
|
+
readonly eventTypes: readonly DatabaseWebhookEventPattern[];
|
|
332
342
|
readonly headers: Readonly<Record<string, string>>;
|
|
333
343
|
}): Promise<DatabaseWebhookCreated>;
|
|
334
344
|
listProjectDatabaseWebhooks(input: {
|
|
@@ -350,6 +360,22 @@ export interface ProductApi {
|
|
|
350
360
|
readonly environment: "dev" | "prod";
|
|
351
361
|
readonly webhookId: string;
|
|
352
362
|
}): Promise<DatabaseWebhookTestResult>;
|
|
363
|
+
createProjectDatabaseWatch(input: {
|
|
364
|
+
readonly projectId: string;
|
|
365
|
+
readonly environment: "dev" | "prod";
|
|
366
|
+
readonly idempotencyKey: string;
|
|
367
|
+
readonly request: DatabaseWatchRequest;
|
|
368
|
+
}): Promise<DatabaseWatch>;
|
|
369
|
+
listProjectDatabaseWatches(input: {
|
|
370
|
+
readonly projectId: string;
|
|
371
|
+
readonly environment: "dev" | "prod";
|
|
372
|
+
}): Promise<DatabaseWatchPage>;
|
|
373
|
+
deleteProjectDatabaseWatch(input: {
|
|
374
|
+
readonly projectId: string;
|
|
375
|
+
readonly environment: "dev" | "prod";
|
|
376
|
+
readonly idempotencyKey: string;
|
|
377
|
+
readonly watchId: string;
|
|
378
|
+
}): Promise<DatabaseWatchDeleted>;
|
|
353
379
|
listProjectDatabaseWebhookDeliveries(input: {
|
|
354
380
|
readonly projectId: string;
|
|
355
381
|
readonly environment: "dev" | "prod";
|
|
@@ -566,6 +592,7 @@ export interface ProductApi {
|
|
|
566
592
|
|
|
567
593
|
/** A webhook answer that names another environment, webhook or delivery than the request did. */
|
|
568
594
|
const WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
|
|
595
|
+
const WATCH_SCOPE_MISMATCH = "Watch response scope mismatch";
|
|
569
596
|
/** A schedule answer that names another environment or schedule than the request did. */
|
|
570
597
|
const SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
|
|
571
598
|
|
|
@@ -889,6 +916,63 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
889
916
|
return value;
|
|
890
917
|
}
|
|
891
918
|
|
|
919
|
+
public async createProjectDatabaseWatch(
|
|
920
|
+
input: Parameters<ProductApi["createProjectDatabaseWatch"]>[0],
|
|
921
|
+
): Promise<DatabaseWatch> {
|
|
922
|
+
const value = await createProjectDatabaseWatch(
|
|
923
|
+
{
|
|
924
|
+
project_id: input.projectId,
|
|
925
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
926
|
+
databaseWatchCreateRequest: {
|
|
927
|
+
environment: input.environment,
|
|
928
|
+
schema: input.request.schema,
|
|
929
|
+
table: input.request.table,
|
|
930
|
+
columns: [...input.request.columns],
|
|
931
|
+
operations: [...input.request.operations],
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
{ client: this.#client },
|
|
935
|
+
);
|
|
936
|
+
assertDatabaseWatch(value);
|
|
937
|
+
if (
|
|
938
|
+
value.environment !== input.environment ||
|
|
939
|
+
value.schema !== input.request.schema ||
|
|
940
|
+
value.table !== input.request.table
|
|
941
|
+
)
|
|
942
|
+
throw new TypeError(WATCH_SCOPE_MISMATCH);
|
|
943
|
+
return value;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
public async listProjectDatabaseWatches(
|
|
947
|
+
input: Parameters<ProductApi["listProjectDatabaseWatches"]>[0],
|
|
948
|
+
): Promise<DatabaseWatchPage> {
|
|
949
|
+
const value = await listProjectDatabaseWatches(
|
|
950
|
+
{ project_id: input.projectId, environment: input.environment },
|
|
951
|
+
{ client: this.#client },
|
|
952
|
+
);
|
|
953
|
+
assertDatabaseWatchPage(value);
|
|
954
|
+
if (value.items.some((item) => item.environment !== input.environment))
|
|
955
|
+
throw new TypeError(WATCH_SCOPE_MISMATCH);
|
|
956
|
+
return value;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
public async deleteProjectDatabaseWatch(
|
|
960
|
+
input: Parameters<ProductApi["deleteProjectDatabaseWatch"]>[0],
|
|
961
|
+
): Promise<DatabaseWatchDeleted> {
|
|
962
|
+
const value = await deleteProjectDatabaseWatch(
|
|
963
|
+
{
|
|
964
|
+
project_id: input.projectId,
|
|
965
|
+
environment: input.environment,
|
|
966
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
967
|
+
watch_id: input.watchId,
|
|
968
|
+
},
|
|
969
|
+
{ client: this.#client },
|
|
970
|
+
);
|
|
971
|
+
assertDatabaseWatchDeleted(value);
|
|
972
|
+
if (value.id !== input.watchId) throw new TypeError(WATCH_SCOPE_MISMATCH);
|
|
973
|
+
return value;
|
|
974
|
+
}
|
|
975
|
+
|
|
892
976
|
/** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
|
|
893
977
|
public async createProjectDatabaseWebhook(
|
|
894
978
|
input: Parameters<ProductApi["createProjectDatabaseWebhook"]>[0],
|