@amerged/ohmyhost-cli 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/bin.js +1 -1
- package/dist/{chunk-5TO5I2HF.js → chunk-C6KEAOJP.js} +505 -14
- package/dist/index.js +1 -1
- package/package.json +1 -1
- 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/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
|
@@ -38879,12 +38879,12 @@ var failures = {
|
|
|
38879
38879
|
"Do not repeat the write with a new key. Inspect the target data with a read query and retain this operation ID. The same key only reads this receipt."
|
|
38880
38880
|
],
|
|
38881
38881
|
database_compute_failed: [
|
|
38882
|
-
"The database size change
|
|
38883
|
-
"Read the current database compute and report this operation ID through feedback.
|
|
38882
|
+
"The database provider refused the size change or had no room for it. The database keeps its previous size and keeps running, and no credits are charged for the failed change.",
|
|
38883
|
+
"Read the current size with 'ohmyhost database compute get --project PROJECT_ULID --environment dev|prod --json' or MCP database_compute_get, then repeat the change with a new idempotency key; the old key only returns this failure. If it fails again after a few minutes, start a new deployment of the project, repeat the change and report this operation ID through feedback. Never reset the database to change its size."
|
|
38884
38884
|
],
|
|
38885
38885
|
database_compute_plan_changed: [
|
|
38886
|
-
"Paid access ended before the database size change started.",
|
|
38887
|
-
"Read the organization's plan
|
|
38886
|
+
"Paid access ended before the database size change started, so it was not sent to the database provider. The database keeps its previous size and keeps running, and no credits are charged for the change.",
|
|
38887
|
+
"Read the organization's plan with 'ohmyhost credits account --organization ORGANIZATION_ULID --json' or MCP organization_account_get. Sizes above 256 MiB need active Paid: ask the organization Owner to activate Paid through billing checkout, then read the current size with database compute get and repeat the change with a new idempotency key."
|
|
38888
38888
|
],
|
|
38889
38889
|
database_compute_rejected: [
|
|
38890
38890
|
"The database size change could not be started.",
|
|
@@ -40219,6 +40219,36 @@ var PROBLEM_POLICY = {
|
|
|
40219
40219
|
action: "The platform is still retrying this webhook delivery. Read the delivery in the delivery list; replay it only if it ends dead_lettered or the receiver needs the event again.",
|
|
40220
40220
|
title: "Webhook delivery still pending"
|
|
40221
40221
|
},
|
|
40222
|
+
database_watch_limit: {
|
|
40223
|
+
status: 409,
|
|
40224
|
+
retryable: false,
|
|
40225
|
+
action: "List the project's database watches for this environment and delete one you no longer need, then create the watch again.",
|
|
40226
|
+
title: "Database watch limit reached"
|
|
40227
|
+
},
|
|
40228
|
+
database_watch_table_unavailable: {
|
|
40229
|
+
status: 409,
|
|
40230
|
+
retryable: false,
|
|
40231
|
+
action: "The table must exist as an ordinary table owned by the application, created by a deployed migration; views, partitions, temporary tables and the schemas pgcf, ohmyhost, information_schema and pg_* cannot be watched. Fix the name or deploy the migration, then create the watch again.",
|
|
40232
|
+
title: "Table cannot be watched"
|
|
40233
|
+
},
|
|
40234
|
+
database_watch_column_unavailable: {
|
|
40235
|
+
status: 409,
|
|
40236
|
+
retryable: false,
|
|
40237
|
+
action: "List only existing column names exactly as written, names are case-sensitive. Delete the watch before a migration drops a published column, because the write would fail.",
|
|
40238
|
+
title: "Watched column does not exist"
|
|
40239
|
+
},
|
|
40240
|
+
database_watch_events_unavailable: {
|
|
40241
|
+
status: 409,
|
|
40242
|
+
retryable: false,
|
|
40243
|
+
action: "The platform event schema is installed when the database wakes: run any query against that environment, wait for the answer, then create the watch again. If it still fails, report the request_id through feedback.",
|
|
40244
|
+
title: "Database events not installed yet"
|
|
40245
|
+
},
|
|
40246
|
+
database_watch_conflict: {
|
|
40247
|
+
status: 409,
|
|
40248
|
+
retryable: false,
|
|
40249
|
+
action: "A migration already attaches a capture trigger to this table: keep it and skip the watch, or remove it from the migrations, deploy and create the watch again. A table carries one capture trigger.",
|
|
40250
|
+
title: "Table already has a capture trigger"
|
|
40251
|
+
},
|
|
40222
40252
|
project_notes_conflict: {
|
|
40223
40253
|
status: 409,
|
|
40224
40254
|
retryable: false,
|
|
@@ -40483,7 +40513,7 @@ var PROBLEM_POLICY = {
|
|
|
40483
40513
|
api_key_permissions_unavailable: {
|
|
40484
40514
|
status: 503,
|
|
40485
40515
|
retryable: false,
|
|
40486
|
-
action: "Report the platform user-key permission configuration through feedback. Repeated login
|
|
40516
|
+
action: "Report the platform user-key permission configuration through feedback with this request_id; it affects every token the same way, whatever the integration. Repeated login does not fix it. After the platform confirms the fix, repeat the same create with the same name and Idempotency-Key.",
|
|
40487
40517
|
title: "Token permissions unavailable"
|
|
40488
40518
|
},
|
|
40489
40519
|
reconciliation_exhausted: {
|
|
@@ -42389,6 +42419,9 @@ var DATABASE_WEBHOOK_EVENT_TYPES = [
|
|
|
42389
42419
|
"database.resized",
|
|
42390
42420
|
"database.deleted"
|
|
42391
42421
|
];
|
|
42422
|
+
var DATABASE_WATCH_OPERATIONS = ["insert", "update", "delete"];
|
|
42423
|
+
var DATABASE_WATCH_MAX_COLUMNS = 32;
|
|
42424
|
+
var DATABASE_WATCH_DEFAULT_SCHEMA = "public";
|
|
42392
42425
|
var DATABASE_WEBHOOK_MAX_EVENT_TYPES = 16;
|
|
42393
42426
|
var DATABASE_WEBHOOK_MAX_HEADERS = 8;
|
|
42394
42427
|
var DATABASE_WEBHOOK_URL_MAX_LENGTH = 2048;
|
|
@@ -42432,7 +42465,22 @@ var USAGE_MAX_RANGE_MS = 31 * 24 * USAGE_HOUR_MS;
|
|
|
42432
42465
|
var WEBHOOK_KEYS = ["id", "environment", "url", "event_types", "header_names", "created_at"];
|
|
42433
42466
|
var WEBHOOK_ID_PATTERN = /^whk_[a-z0-9]{20}$/u;
|
|
42434
42467
|
var DELIVERY_ID_PATTERN = /^dlv_[0-9a-f]{20}$/u;
|
|
42435
|
-
var EVENT_ID_PATTERN = /^evt_[0-9a-f]{20}$/u;
|
|
42468
|
+
var EVENT_ID_PATTERN = /^(?:evt_[0-9a-f]{20}|[a-z][a-z0-9]{19}:[1-9][0-9]{0,15}:[1-9][0-9]{0,15})$/u;
|
|
42469
|
+
var DATA_SUBSCRIPTION_PATTERN = /^data\.(?:\*|[a-z][a-z0-9_]{0,47})$/u;
|
|
42470
|
+
var DATA_EVENT_TYPE_PATTERN = /^data\.[a-z][a-z0-9_]{0,47}$/u;
|
|
42471
|
+
var DATABASE_WATCH_ID_PATTERN = /^pgcf_watch_[0-9a-f]{8}$/u;
|
|
42472
|
+
var DATABASE_WATCH_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/u;
|
|
42473
|
+
var DATABASE_WATCH_FORBIDDEN_SCHEMAS = ["information_schema", "pgcf", "ohmyhost"];
|
|
42474
|
+
var DATABASE_WATCH_KEYS = [
|
|
42475
|
+
"id",
|
|
42476
|
+
"environment",
|
|
42477
|
+
"schema",
|
|
42478
|
+
"table",
|
|
42479
|
+
"columns",
|
|
42480
|
+
"operations",
|
|
42481
|
+
"topic",
|
|
42482
|
+
"event_type"
|
|
42483
|
+
];
|
|
42436
42484
|
var SCHEDULE_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,62}$/u;
|
|
42437
42485
|
var CURSOR_PATTERN = /^[A-Za-z0-9._|:=+/-]{1,200}$/u;
|
|
42438
42486
|
var HEADER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]{0,63}$/u;
|
|
@@ -42454,11 +42502,53 @@ function parseDatabaseWebhookId(value) {
|
|
|
42454
42502
|
throw new TypeError("Invalid database webhook id");
|
|
42455
42503
|
return value;
|
|
42456
42504
|
}
|
|
42505
|
+
function parseDatabaseWatchId(value) {
|
|
42506
|
+
if (!isDatabaseWatchTriggerName(value))
|
|
42507
|
+
throw new TypeError("Invalid database watch id");
|
|
42508
|
+
return value;
|
|
42509
|
+
}
|
|
42457
42510
|
function parseDatabaseDeliveryId(value) {
|
|
42458
42511
|
if (!matches(value, DELIVERY_ID_PATTERN))
|
|
42459
42512
|
throw new TypeError("Invalid database delivery id");
|
|
42460
42513
|
return value;
|
|
42461
42514
|
}
|
|
42515
|
+
function isDatabaseWatchTriggerName(value) {
|
|
42516
|
+
return matches(value, DATABASE_WATCH_ID_PATTERN);
|
|
42517
|
+
}
|
|
42518
|
+
function isDatabaseWatchIdentifier(value) {
|
|
42519
|
+
return matches(value, DATABASE_WATCH_IDENTIFIER_PATTERN);
|
|
42520
|
+
}
|
|
42521
|
+
function isDatabaseWatchSchema(value) {
|
|
42522
|
+
if (!isDatabaseWatchIdentifier(value))
|
|
42523
|
+
return false;
|
|
42524
|
+
const lower = value.toLowerCase();
|
|
42525
|
+
return !lower.startsWith("pg_") && !DATABASE_WATCH_FORBIDDEN_SCHEMAS.includes(lower);
|
|
42526
|
+
}
|
|
42527
|
+
function dataEventTopic(tableName) {
|
|
42528
|
+
const mapped = Array.from(tableName, (character) => {
|
|
42529
|
+
const lower = Array.from(character.toLowerCase())[0] ?? "_";
|
|
42530
|
+
return /^[a-z0-9_]$/u.test(lower) ? lower : "_";
|
|
42531
|
+
}).join("");
|
|
42532
|
+
const topic = /^[a-z]/u.test(mapped) ? mapped : `t_${mapped}`;
|
|
42533
|
+
return topic.slice(0, 48);
|
|
42534
|
+
}
|
|
42535
|
+
function parseDatabaseWatchCreateRequest(value) {
|
|
42536
|
+
if (!record2(value) || !onlyKeys(value, ["schema", "table", "columns", "operations"]) || !isDatabaseWatchIdentifier(value["table"]) || !Array.isArray(value["columns"]) || value["columns"].length < 1 || value["columns"].length > DATABASE_WATCH_MAX_COLUMNS || !value["columns"].every(isDatabaseWatchIdentifier) || new Set(value["columns"]).size !== value["columns"].length)
|
|
42537
|
+
throw new TypeError("Invalid database watch request");
|
|
42538
|
+
return {
|
|
42539
|
+
...parseDatabaseWatchTarget({ schema: value["schema"], table: value["table"] }),
|
|
42540
|
+
columns: value["columns"],
|
|
42541
|
+
operations: parseWatchOperations(value["operations"])
|
|
42542
|
+
};
|
|
42543
|
+
}
|
|
42544
|
+
function parseDatabaseWatchTarget(value) {
|
|
42545
|
+
if (!record2(value) || !onlyKeys(value, ["schema", "table"]) || !isDatabaseWatchIdentifier(value["table"]))
|
|
42546
|
+
throw new TypeError("Invalid database watch target");
|
|
42547
|
+
const schema = value["schema"] === void 0 ? DATABASE_WATCH_DEFAULT_SCHEMA : value["schema"];
|
|
42548
|
+
if (!isDatabaseWatchSchema(schema))
|
|
42549
|
+
throw new TypeError("Invalid database watch target");
|
|
42550
|
+
return { schema, table: value["table"] };
|
|
42551
|
+
}
|
|
42462
42552
|
function parseDatabaseScheduleName(value) {
|
|
42463
42553
|
if (!matches(value, SCHEDULE_NAME_PATTERN))
|
|
42464
42554
|
throw new TypeError("Invalid database schedule name");
|
|
@@ -42515,6 +42605,20 @@ function assertDatabaseWebhook(value) {
|
|
|
42515
42605
|
throw new TypeError("Invalid database webhook");
|
|
42516
42606
|
assertWebhookFields(value);
|
|
42517
42607
|
}
|
|
42608
|
+
function assertDatabaseWatch(value) {
|
|
42609
|
+
if (!record2(value) || !exact2(value, DATABASE_WATCH_KEYS) || !matches(value["id"], DATABASE_WATCH_ID_PATTERN) || !oneOf(["dev", "prod"], value["environment"]) || !isDatabaseWatchSchema(value["schema"]) || !isDatabaseWatchIdentifier(value["table"]) || !Array.isArray(value["columns"]) || value["columns"].length < 1 || value["columns"].length > DATABASE_WATCH_MAX_COLUMNS || !value["columns"].every(isDatabaseWatchIdentifier) || !isCanonicalOperations(value["operations"]) || value["topic"] !== dataEventTopic(value["table"]) || value["event_type"] !== `data.${value["topic"]}`)
|
|
42610
|
+
throw new TypeError("Invalid database watch");
|
|
42611
|
+
}
|
|
42612
|
+
function assertDatabaseWatchPage(value) {
|
|
42613
|
+
if (!record2(value) || !exact2(value, ["items"]) || !pageItems(value["items"]))
|
|
42614
|
+
throw new TypeError("Invalid database watch page");
|
|
42615
|
+
for (const item of value["items"])
|
|
42616
|
+
assertDatabaseWatch(item);
|
|
42617
|
+
}
|
|
42618
|
+
function assertDatabaseWatchDeleted(value) {
|
|
42619
|
+
if (!record2(value) || !exact2(value, ["id", "deleted"]) || !matches(value["id"], DATABASE_WATCH_ID_PATTERN) || value["deleted"] !== true)
|
|
42620
|
+
throw new TypeError("Invalid database watch deletion");
|
|
42621
|
+
}
|
|
42518
42622
|
function assertDatabaseWebhookCreated(value) {
|
|
42519
42623
|
if (!record2(value) || !exact2(value, [...WEBHOOK_KEYS, "secret"]) || typeof value["secret"] !== "string" || value["secret"].length === 0)
|
|
42520
42624
|
throw new TypeError("Invalid database webhook");
|
|
@@ -42543,7 +42647,7 @@ function assertDatabaseWebhookDelivery(value) {
|
|
|
42543
42647
|
"delivered_at",
|
|
42544
42648
|
"last_status",
|
|
42545
42649
|
"last_error"
|
|
42546
|
-
]) || !matches(value["id"], DELIVERY_ID_PATTERN) || !matches(value["event_id"], EVENT_ID_PATTERN) || !
|
|
42650
|
+
]) || !matches(value["id"], DELIVERY_ID_PATTERN) || !matches(value["event_id"], EVENT_ID_PATTERN) || !isDeliveredEventType(value["event_type"]) || !matches(value["webhook_id"], WEBHOOK_ID_PATTERN) || !oneOf(["pending", "delivered", "dead_lettered"], value["state"]) || !nonNegativeInteger(value["attempts"]) || !timestamp2(value["created_at"]) || !nullable(value["next_attempt_at"], timestamp2) || !nullable(value["delivered_at"], timestamp2) || !nullable(value["last_status"], Number.isSafeInteger) || !nullable(value["last_error"], (error) => oneOf(["timeout", "network", "http", "expired", "permanent"], error)))
|
|
42547
42651
|
throw new TypeError("Invalid database webhook delivery");
|
|
42548
42652
|
}
|
|
42549
42653
|
function assertDatabaseWebhookDeliveryPage(value) {
|
|
@@ -42626,7 +42730,7 @@ function assertUsageRow(value) {
|
|
|
42626
42730
|
throw new TypeError("Invalid database execution usage row");
|
|
42627
42731
|
}
|
|
42628
42732
|
function assertWebhookFields(value) {
|
|
42629
|
-
if (!matches(value["id"], WEBHOOK_ID_PATTERN) || !oneOf(["dev", "prod"], value["environment"]) || !isHttpsUrl2(value["url"]) || !Array.isArray(value["event_types"]) || value["event_types"].length < 1 || value["event_types"].length > DATABASE_WEBHOOK_MAX_EVENT_TYPES || !value["event_types"].every(
|
|
42733
|
+
if (!matches(value["id"], WEBHOOK_ID_PATTERN) || !oneOf(["dev", "prod"], value["environment"]) || !isHttpsUrl2(value["url"]) || !Array.isArray(value["event_types"]) || value["event_types"].length < 1 || value["event_types"].length > DATABASE_WEBHOOK_MAX_EVENT_TYPES || !value["event_types"].every(isDatabaseWebhookEventPattern) || !Array.isArray(value["header_names"]) || value["header_names"].length > DATABASE_WEBHOOK_MAX_HEADERS || !value["header_names"].every((name) => matches(name, HEADER_NAME_PATTERN)) || !timestamp2(value["created_at"]))
|
|
42630
42734
|
throw new TypeError("Invalid database webhook");
|
|
42631
42735
|
}
|
|
42632
42736
|
function parseHttpsUrl(value) {
|
|
@@ -42643,11 +42747,33 @@ function isHttpsUrl2(value) {
|
|
|
42643
42747
|
return false;
|
|
42644
42748
|
}
|
|
42645
42749
|
}
|
|
42750
|
+
function isDatabaseWebhookEventPattern(value) {
|
|
42751
|
+
return oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value) || matches(value, DATA_SUBSCRIPTION_PATTERN);
|
|
42752
|
+
}
|
|
42753
|
+
function isDeliveredEventType(value) {
|
|
42754
|
+
return oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value) || matches(value, DATA_EVENT_TYPE_PATTERN);
|
|
42755
|
+
}
|
|
42756
|
+
function isDatabaseWebhookEventTypes(value) {
|
|
42757
|
+
return Array.isArray(value) && value.length >= 1 && value.length <= DATABASE_WEBHOOK_MAX_EVENT_TYPES && value.every(isDatabaseWebhookEventPattern) && new Set(value).size === value.length;
|
|
42758
|
+
}
|
|
42646
42759
|
function parseEventTypes(value) {
|
|
42647
|
-
if (!
|
|
42760
|
+
if (!isDatabaseWebhookEventTypes(value))
|
|
42648
42761
|
throw new TypeError("Invalid database webhook event types");
|
|
42649
42762
|
return value;
|
|
42650
42763
|
}
|
|
42764
|
+
function parseWatchOperations(value) {
|
|
42765
|
+
if (value === void 0)
|
|
42766
|
+
return DATABASE_WATCH_OPERATIONS;
|
|
42767
|
+
if (!Array.isArray(value) || value.length < 1 || !value.every((operation) => oneOf(DATABASE_WATCH_OPERATIONS, operation)) || new Set(value).size !== value.length)
|
|
42768
|
+
throw new TypeError("Invalid database watch request");
|
|
42769
|
+
return DATABASE_WATCH_OPERATIONS.filter((operation) => value.includes(operation));
|
|
42770
|
+
}
|
|
42771
|
+
function isCanonicalOperations(value) {
|
|
42772
|
+
if (!Array.isArray(value) || value.length < 1)
|
|
42773
|
+
return false;
|
|
42774
|
+
const canonical = DATABASE_WATCH_OPERATIONS.filter((operation) => value.includes(operation));
|
|
42775
|
+
return canonical.length === value.length && canonical.every((op, index) => op === value[index]);
|
|
42776
|
+
}
|
|
42651
42777
|
function parseHeaders(value) {
|
|
42652
42778
|
if (!record2(value) || Object.keys(value).length > DATABASE_WEBHOOK_MAX_HEADERS || new Set(Object.keys(value).map((name) => name.toLowerCase())).size !== Object.keys(value).length)
|
|
42653
42779
|
throw new TypeError("Invalid database webhook headers");
|
|
@@ -43083,6 +43209,14 @@ var recognizedCommandLabel = (argv) => {
|
|
|
43083
43209
|
if (values[1] === "revoke")
|
|
43084
43210
|
return "database access revoke";
|
|
43085
43211
|
}
|
|
43212
|
+
if (value === "database" && values[0] === "watch") {
|
|
43213
|
+
if (values[1] === "create")
|
|
43214
|
+
return "database watch create";
|
|
43215
|
+
if (values[1] === "list")
|
|
43216
|
+
return "database watch list";
|
|
43217
|
+
if (values[1] === "delete")
|
|
43218
|
+
return "database watch delete";
|
|
43219
|
+
}
|
|
43086
43220
|
if (value === "database" && values[0] === "webhook") {
|
|
43087
43221
|
if (values[1] === "create")
|
|
43088
43222
|
return "database webhook create";
|
|
@@ -43932,6 +44066,73 @@ var parseProductCliCommand = (argv) => {
|
|
|
43932
44066
|
}
|
|
43933
44067
|
throw new InvalidCommandError();
|
|
43934
44068
|
}
|
|
44069
|
+
if (first === "database" && values[0] === "watch") {
|
|
44070
|
+
values.shift();
|
|
44071
|
+
const subcommand = values.shift();
|
|
44072
|
+
if (subcommand === "create") {
|
|
44073
|
+
if (!yes || wait || follow || revoke || stdin)
|
|
44074
|
+
throw new InvalidCommandError();
|
|
44075
|
+
const options = parseOptionalOptions(values, [
|
|
44076
|
+
"--project",
|
|
44077
|
+
"--environment",
|
|
44078
|
+
"--table",
|
|
44079
|
+
"--schema",
|
|
44080
|
+
"--columns",
|
|
44081
|
+
"--operations",
|
|
44082
|
+
"--idempotency-key"
|
|
44083
|
+
]);
|
|
44084
|
+
const projectId = options["--project"], environment = options["--environment"], table = options["--table"], schema = options["--schema"], columns = options["--columns"], operations = options["--operations"], idempotencyKey = options["--idempotency-key"];
|
|
44085
|
+
if (!isValue(projectId) || !isValue(idempotencyKey) || !isValue(table) || !isValue(columns) || environment !== "dev" && environment !== "prod")
|
|
44086
|
+
throw new InvalidCommandError();
|
|
44087
|
+
const request = contractValue(() => parseDatabaseWatchCreateRequest({
|
|
44088
|
+
schema,
|
|
44089
|
+
table,
|
|
44090
|
+
columns: columns.split(","),
|
|
44091
|
+
...operations === void 0 ? {} : { operations: operations.split(",") }
|
|
44092
|
+
}));
|
|
44093
|
+
return {
|
|
44094
|
+
kind: "database-watch-create",
|
|
44095
|
+
projectId,
|
|
44096
|
+
environment,
|
|
44097
|
+
request,
|
|
44098
|
+
idempotencyKey,
|
|
44099
|
+
yes: true,
|
|
44100
|
+
credentialStore
|
|
44101
|
+
};
|
|
44102
|
+
}
|
|
44103
|
+
if (subcommand === "list") {
|
|
44104
|
+
if (yes || wait || follow || revoke || stdin)
|
|
44105
|
+
throw new InvalidCommandError();
|
|
44106
|
+
const options = parseOptionalOptions(values, ["--project", "--environment"]);
|
|
44107
|
+
const projectId = options["--project"], environment = options["--environment"] ?? "dev";
|
|
44108
|
+
if (!isValue(projectId) || environment !== "dev" && environment !== "prod")
|
|
44109
|
+
throw new InvalidCommandError();
|
|
44110
|
+
return { kind: "database-watch-list", projectId, environment, credentialStore };
|
|
44111
|
+
}
|
|
44112
|
+
if (subcommand === "delete") {
|
|
44113
|
+
if (!yes || wait || follow || revoke || stdin)
|
|
44114
|
+
throw new InvalidCommandError();
|
|
44115
|
+
const options = parseOptionalOptions(values, [
|
|
44116
|
+
"--project",
|
|
44117
|
+
"--environment",
|
|
44118
|
+
"--watch",
|
|
44119
|
+
"--idempotency-key"
|
|
44120
|
+
]);
|
|
44121
|
+
const projectId = options["--project"], environment = options["--environment"], watchId = options["--watch"], idempotencyKey = options["--idempotency-key"];
|
|
44122
|
+
if (!isValue(projectId) || !isValue(idempotencyKey) || environment !== "dev" && environment !== "prod")
|
|
44123
|
+
throw new InvalidCommandError();
|
|
44124
|
+
return {
|
|
44125
|
+
kind: "database-watch-delete",
|
|
44126
|
+
projectId,
|
|
44127
|
+
environment,
|
|
44128
|
+
watchId: contractValue(() => parseDatabaseWatchId(watchId)),
|
|
44129
|
+
idempotencyKey,
|
|
44130
|
+
yes: true,
|
|
44131
|
+
credentialStore
|
|
44132
|
+
};
|
|
44133
|
+
}
|
|
44134
|
+
throw new InvalidCommandError();
|
|
44135
|
+
}
|
|
43935
44136
|
if (first === "database" && values[0] === "webhook") {
|
|
43936
44137
|
values.shift();
|
|
43937
44138
|
const subcommand = values.shift();
|
|
@@ -45317,7 +45518,7 @@ function exact4(v, keys) {
|
|
|
45317
45518
|
// apps/product-cli/package.json
|
|
45318
45519
|
var package_default = {
|
|
45319
45520
|
name: "@ohmyhost/product-cli",
|
|
45320
|
-
version: "0.1.
|
|
45521
|
+
version: "0.1.45",
|
|
45321
45522
|
private: true,
|
|
45322
45523
|
ohmyhost: {
|
|
45323
45524
|
deployment: "production",
|
|
@@ -45662,20 +45863,23 @@ var HELP_USAGE = Object.freeze({
|
|
|
45662
45863
|
"database access create": "ohmyhost database access create --project ULID --environment dev|prod [--mode read|write] [--ttl 5m|1h|24h|SECONDS] [--label TEXT] --yes --json\n The connection URI is shown once. Use it immediately, never store it in files, notes or source, and revoke it when finished.",
|
|
45663
45864
|
"database access list": "ohmyhost database access list --project ULID [--environment dev|prod] --json",
|
|
45664
45865
|
"database access revoke": "ohmyhost database access revoke --project ULID --access ULID --yes --json",
|
|
45665
|
-
"database
|
|
45866
|
+
"database watch create": "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>')",
|
|
45867
|
+
"database watch list": "ohmyhost database watch list --project ULID [--environment dev|prod] --json (dev when omitted; a sleeping database wakes)",
|
|
45868
|
+
"database watch delete": "ohmyhost database watch delete --project ULID --environment dev|prod --watch ID --idempotency-key KEY --yes --json",
|
|
45869
|
+
"database webhook create": "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)",
|
|
45666
45870
|
"database webhook list": "ohmyhost database webhook list --project ULID [--environment dev|prod] --json (dev when omitted; the secret is never listed)",
|
|
45667
45871
|
"database webhook get": "ohmyhost database webhook get --project ULID [--environment dev|prod] --webhook ID --json",
|
|
45668
45872
|
"database webhook delete": "ohmyhost database webhook delete --project ULID --environment dev|prod --webhook ID --yes --json",
|
|
45669
45873
|
"database webhook test": "ohmyhost database webhook test --project ULID --environment dev|prod --webhook ID --yes --json (sends one signed test request to the receiver; nothing is stored)",
|
|
45670
45874
|
"database webhook deliveries": "ohmyhost database webhook deliveries --project ULID [--environment dev|prod] [--webhook ID] [--limit 1..100] [--cursor CURSOR] --json",
|
|
45671
45875
|
"database webhook replay": "ohmyhost database webhook replay --project ULID --environment dev|prod --delivery ID --yes --json",
|
|
45672
|
-
"database schedule set": "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)",
|
|
45876
|
+
"database schedule set": "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).",
|
|
45673
45877
|
"database schedule list": "ohmyhost database schedule list --project ULID [--environment dev|prod] --json (dev when omitted)",
|
|
45674
45878
|
"database schedule get": "ohmyhost database schedule get --project ULID [--environment dev|prod] --name NAME --json",
|
|
45675
45879
|
"database schedule delete": "ohmyhost database schedule delete --project ULID --environment dev|prod --name NAME --yes --json",
|
|
45676
45880
|
"database schedule runs": "ohmyhost database schedule runs --project ULID [--environment dev|prod] --name NAME [--limit 1..50] --json",
|
|
45677
|
-
"database schedule run": "ohmyhost database schedule run --project ULID --environment dev|prod --name NAME --yes --json (queues one run now, in addition to the timer)",
|
|
45678
|
-
"database executions": "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;
|
|
45881
|
+
"database schedule run": "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)",
|
|
45882
|
+
"database executions": "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)",
|
|
45679
45883
|
"database psql": "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)",
|
|
45680
45884
|
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)",
|
|
45681
45885
|
"source auto-deploy set": "ohmyhost source auto-deploy set --project ULID --branch BRANCH --enabled true|false --idempotency-key KEY --json",
|
|
@@ -46153,6 +46357,12 @@ var ProductCli = class {
|
|
|
46153
46357
|
return await this.listDatabaseAccess(command, signal);
|
|
46154
46358
|
if (command.kind === "database-access-revoke")
|
|
46155
46359
|
return await this.revokeDatabaseAccess(command, signal);
|
|
46360
|
+
if (command.kind === "database-watch-create")
|
|
46361
|
+
return await this.createDatabaseWatch(command, signal);
|
|
46362
|
+
if (command.kind === "database-watch-list")
|
|
46363
|
+
return await this.listDatabaseWatches(command, signal);
|
|
46364
|
+
if (command.kind === "database-watch-delete")
|
|
46365
|
+
return await this.deleteDatabaseWatch(command, signal);
|
|
46156
46366
|
if (command.kind === "database-webhook-create")
|
|
46157
46367
|
return await this.createDatabaseWebhook(command, signal);
|
|
46158
46368
|
if (command.kind === "database-webhook-list")
|
|
@@ -46684,6 +46894,39 @@ var ProductCli = class {
|
|
|
46684
46894
|
assertProjectDatabaseAccess(access);
|
|
46685
46895
|
return success("database access revoke", { status: "succeeded", access });
|
|
46686
46896
|
}
|
|
46897
|
+
/** The answer names the event type to subscribe to; the watch already carries the columns and the topic. */
|
|
46898
|
+
async createDatabaseWatch(command, signal) {
|
|
46899
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
46900
|
+
const watch = await api.createProjectDatabaseWatch({
|
|
46901
|
+
projectId: command.projectId,
|
|
46902
|
+
environment: command.environment,
|
|
46903
|
+
idempotencyKey: command.idempotencyKey,
|
|
46904
|
+
request: command.request
|
|
46905
|
+
});
|
|
46906
|
+
return success("database watch create", {
|
|
46907
|
+
status: "succeeded",
|
|
46908
|
+
watch,
|
|
46909
|
+
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.`
|
|
46910
|
+
});
|
|
46911
|
+
}
|
|
46912
|
+
async listDatabaseWatches(command, signal) {
|
|
46913
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
46914
|
+
const page = await api.listProjectDatabaseWatches({
|
|
46915
|
+
projectId: command.projectId,
|
|
46916
|
+
environment: command.environment
|
|
46917
|
+
});
|
|
46918
|
+
return success("database watch list", { status: "succeeded", watch: page.items });
|
|
46919
|
+
}
|
|
46920
|
+
async deleteDatabaseWatch(command, signal) {
|
|
46921
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
46922
|
+
const deleted = await api.deleteProjectDatabaseWatch({
|
|
46923
|
+
projectId: command.projectId,
|
|
46924
|
+
environment: command.environment,
|
|
46925
|
+
idempotencyKey: command.idempotencyKey,
|
|
46926
|
+
watchId: command.watchId
|
|
46927
|
+
});
|
|
46928
|
+
return success("database watch delete", { status: "succeeded", deleted });
|
|
46929
|
+
}
|
|
46687
46930
|
/** The signing secret is returned by create only; every other answer carries no secret. */
|
|
46688
46931
|
async createDatabaseWebhook(command, signal) {
|
|
46689
46932
|
const request = await webhookCreateRequest(command);
|
|
@@ -47714,6 +47957,12 @@ var parsedCommandName = (command) => {
|
|
|
47714
47957
|
return "database access revoke";
|
|
47715
47958
|
if (command.kind === "database-psql")
|
|
47716
47959
|
return "database psql";
|
|
47960
|
+
if (command.kind === "database-watch-create")
|
|
47961
|
+
return "database watch create";
|
|
47962
|
+
if (command.kind === "database-watch-list")
|
|
47963
|
+
return "database watch list";
|
|
47964
|
+
if (command.kind === "database-watch-delete")
|
|
47965
|
+
return "database watch delete";
|
|
47717
47966
|
if (command.kind === "database-webhook-create")
|
|
47718
47967
|
return "database webhook create";
|
|
47719
47968
|
if (command.kind === "database-webhook-list")
|
|
@@ -50586,6 +50835,68 @@ var revokeProjectDatabaseAccess = (parameters, options) => {
|
|
|
50586
50835
|
...params
|
|
50587
50836
|
});
|
|
50588
50837
|
};
|
|
50838
|
+
var listProjectDatabaseWatches = (parameters, options) => {
|
|
50839
|
+
const params = buildClientParams([parameters], [
|
|
50840
|
+
{
|
|
50841
|
+
args: [
|
|
50842
|
+
{ in: "headers", key: "X-Request-Id" },
|
|
50843
|
+
{ in: "path", key: "project_id" },
|
|
50844
|
+
{ in: "query", key: "environment" }
|
|
50845
|
+
]
|
|
50846
|
+
}
|
|
50847
|
+
]);
|
|
50848
|
+
return (options?.client ?? client).get({
|
|
50849
|
+
responseStyle: "data",
|
|
50850
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
50851
|
+
url: "/v1/projects/{project_id}/database/watches",
|
|
50852
|
+
...options,
|
|
50853
|
+
...params
|
|
50854
|
+
});
|
|
50855
|
+
};
|
|
50856
|
+
var createProjectDatabaseWatch = (parameters, options) => {
|
|
50857
|
+
const params = buildClientParams([parameters], [
|
|
50858
|
+
{
|
|
50859
|
+
args: [
|
|
50860
|
+
{ in: "headers", key: "X-Request-Id" },
|
|
50861
|
+
{ in: "headers", key: "Idempotency-Key" },
|
|
50862
|
+
{ in: "path", key: "project_id" },
|
|
50863
|
+
{ key: "databaseWatchCreateRequest", map: "body" }
|
|
50864
|
+
]
|
|
50865
|
+
}
|
|
50866
|
+
]);
|
|
50867
|
+
return (options?.client ?? client).post({
|
|
50868
|
+
responseStyle: "data",
|
|
50869
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
50870
|
+
url: "/v1/projects/{project_id}/database/watches",
|
|
50871
|
+
...options,
|
|
50872
|
+
...params,
|
|
50873
|
+
headers: {
|
|
50874
|
+
"Content-Type": "application/json",
|
|
50875
|
+
...options?.headers,
|
|
50876
|
+
...params.headers
|
|
50877
|
+
}
|
|
50878
|
+
});
|
|
50879
|
+
};
|
|
50880
|
+
var deleteProjectDatabaseWatch = (parameters, options) => {
|
|
50881
|
+
const params = buildClientParams([parameters], [
|
|
50882
|
+
{
|
|
50883
|
+
args: [
|
|
50884
|
+
{ in: "headers", key: "X-Request-Id" },
|
|
50885
|
+
{ in: "headers", key: "Idempotency-Key" },
|
|
50886
|
+
{ in: "path", key: "project_id" },
|
|
50887
|
+
{ in: "path", key: "watch_id" },
|
|
50888
|
+
{ in: "query", key: "environment" }
|
|
50889
|
+
]
|
|
50890
|
+
}
|
|
50891
|
+
]);
|
|
50892
|
+
return (options?.client ?? client).delete({
|
|
50893
|
+
responseStyle: "data",
|
|
50894
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
50895
|
+
url: "/v1/projects/{project_id}/database/watches/{watch_id}",
|
|
50896
|
+
...options,
|
|
50897
|
+
...params
|
|
50898
|
+
});
|
|
50899
|
+
};
|
|
50589
50900
|
var listProjectDatabaseWebhooks = (parameters, options) => {
|
|
50590
50901
|
const params = buildClientParams([parameters], [
|
|
50591
50902
|
{
|
|
@@ -51959,6 +52270,7 @@ function checkedResponse(kind, value) {
|
|
|
51959
52270
|
|
|
51960
52271
|
// apps/product-cli/dist/product-api.js
|
|
51961
52272
|
var WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
|
|
52273
|
+
var WATCH_SCOPE_MISMATCH = "Watch response scope mismatch";
|
|
51962
52274
|
var SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
|
|
51963
52275
|
var GeneratedSdkProductApi = class {
|
|
51964
52276
|
#client;
|
|
@@ -52156,6 +52468,42 @@ var GeneratedSdkProductApi = class {
|
|
|
52156
52468
|
assertProjectDatabaseAccess(value);
|
|
52157
52469
|
return value;
|
|
52158
52470
|
}
|
|
52471
|
+
async createProjectDatabaseWatch(input) {
|
|
52472
|
+
const value = await createProjectDatabaseWatch({
|
|
52473
|
+
project_id: input.projectId,
|
|
52474
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
52475
|
+
databaseWatchCreateRequest: {
|
|
52476
|
+
environment: input.environment,
|
|
52477
|
+
schema: input.request.schema,
|
|
52478
|
+
table: input.request.table,
|
|
52479
|
+
columns: [...input.request.columns],
|
|
52480
|
+
operations: [...input.request.operations]
|
|
52481
|
+
}
|
|
52482
|
+
}, { client: this.#client });
|
|
52483
|
+
assertDatabaseWatch(value);
|
|
52484
|
+
if (value.environment !== input.environment || value.schema !== input.request.schema || value.table !== input.request.table)
|
|
52485
|
+
throw new TypeError(WATCH_SCOPE_MISMATCH);
|
|
52486
|
+
return value;
|
|
52487
|
+
}
|
|
52488
|
+
async listProjectDatabaseWatches(input) {
|
|
52489
|
+
const value = await listProjectDatabaseWatches({ project_id: input.projectId, environment: input.environment }, { client: this.#client });
|
|
52490
|
+
assertDatabaseWatchPage(value);
|
|
52491
|
+
if (value.items.some((item) => item.environment !== input.environment))
|
|
52492
|
+
throw new TypeError(WATCH_SCOPE_MISMATCH);
|
|
52493
|
+
return value;
|
|
52494
|
+
}
|
|
52495
|
+
async deleteProjectDatabaseWatch(input) {
|
|
52496
|
+
const value = await deleteProjectDatabaseWatch({
|
|
52497
|
+
project_id: input.projectId,
|
|
52498
|
+
environment: input.environment,
|
|
52499
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
52500
|
+
watch_id: input.watchId
|
|
52501
|
+
}, { client: this.#client });
|
|
52502
|
+
assertDatabaseWatchDeleted(value);
|
|
52503
|
+
if (value.id !== input.watchId)
|
|
52504
|
+
throw new TypeError(WATCH_SCOPE_MISMATCH);
|
|
52505
|
+
return value;
|
|
52506
|
+
}
|
|
52159
52507
|
/** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
|
|
52160
52508
|
async createProjectDatabaseWebhook(input) {
|
|
52161
52509
|
const value = await createProjectDatabaseWebhook({
|
|
@@ -54123,6 +54471,8 @@ function rejectMigrationSqlReferences(tokens, allowIdentityHelpers = false) {
|
|
|
54123
54471
|
if (token?.kind !== "word" && token?.kind !== "identifier")
|
|
54124
54472
|
continue;
|
|
54125
54473
|
const value = token.value.toLowerCase();
|
|
54474
|
+
if (value === "pgcf")
|
|
54475
|
+
throw new DatabaseMigrationAdmissionError("unsupported");
|
|
54126
54476
|
if (value === "ohmyhost") {
|
|
54127
54477
|
if (allowIdentityHelpers && token.value === "ohmyhost" && tokens[index + 1]?.kind === "symbol" && tokens[index + 1]?.value === "." && ["user_id", "claims"].includes(tokens[index + 2]?.value ?? "") && ["word", "identifier"].includes(tokens[index + 2]?.kind ?? "") && tokens[index + 3]?.kind === "symbol" && tokens[index + 3]?.value === "(" && tokens[index + 4]?.kind === "symbol" && tokens[index + 4]?.value === ")")
|
|
54128
54478
|
continue;
|
|
@@ -54134,6 +54484,139 @@ function rejectMigrationSqlReferences(tokens, allowIdentityHelpers = false) {
|
|
|
54134
54484
|
}
|
|
54135
54485
|
}
|
|
54136
54486
|
|
|
54487
|
+
// packages/contracts/dist/database-migration-capture.js
|
|
54488
|
+
var MAX_NAME_BYTES = 63;
|
|
54489
|
+
function readMigrationCaptureTrigger(tokens) {
|
|
54490
|
+
if (!isSqlKeyword(tokens[0], "create") || !isSqlKeyword(tokens[1], "trigger"))
|
|
54491
|
+
return void 0;
|
|
54492
|
+
if (!tokens.some((token) => isNamedPgcf(token, true)))
|
|
54493
|
+
return void 0;
|
|
54494
|
+
return new CaptureReader(tokens).read();
|
|
54495
|
+
}
|
|
54496
|
+
var CaptureReader = class {
|
|
54497
|
+
tokens;
|
|
54498
|
+
position = 2;
|
|
54499
|
+
constructor(tokens) {
|
|
54500
|
+
this.tokens = tokens;
|
|
54501
|
+
}
|
|
54502
|
+
read() {
|
|
54503
|
+
const trigger = this.triggerName();
|
|
54504
|
+
this.expectKeyword("after");
|
|
54505
|
+
const operations = this.operations();
|
|
54506
|
+
this.expectKeyword("on");
|
|
54507
|
+
const schema = this.name();
|
|
54508
|
+
this.expectSymbol(".");
|
|
54509
|
+
const table = this.name();
|
|
54510
|
+
if (!isDatabaseWatchSchema(schema))
|
|
54511
|
+
throw unsupported();
|
|
54512
|
+
for (const keyword of ["for", "each", "row", "execute", "function"]) {
|
|
54513
|
+
this.expectKeyword(keyword);
|
|
54514
|
+
}
|
|
54515
|
+
const header = this.tokens.slice(0, this.position);
|
|
54516
|
+
if (!isNamedPgcf(this.token(), false))
|
|
54517
|
+
throw unsupported();
|
|
54518
|
+
this.position += 1;
|
|
54519
|
+
this.expectSymbol(".");
|
|
54520
|
+
this.expectCapture();
|
|
54521
|
+
this.expectSymbol("(");
|
|
54522
|
+
const columns = this.columns();
|
|
54523
|
+
this.expectSymbol(")");
|
|
54524
|
+
if (this.position !== this.tokens.length)
|
|
54525
|
+
throw unsupported();
|
|
54526
|
+
return Object.freeze({
|
|
54527
|
+
target: Object.freeze({ trigger, schema, table, operations, columns }),
|
|
54528
|
+
header
|
|
54529
|
+
});
|
|
54530
|
+
}
|
|
54531
|
+
triggerName() {
|
|
54532
|
+
const token = this.token();
|
|
54533
|
+
if (token.kind !== "word" && token.kind !== "identifier" || !isDatabaseWatchIdentifier(token.value)) {
|
|
54534
|
+
throw unsupported();
|
|
54535
|
+
}
|
|
54536
|
+
this.position += 1;
|
|
54537
|
+
return token.value;
|
|
54538
|
+
}
|
|
54539
|
+
/** Table and schema names may be quoted identifiers; the executor checks the catalog for the exact name. */
|
|
54540
|
+
name() {
|
|
54541
|
+
const token = this.token();
|
|
54542
|
+
const valid = token.kind === "word" ? isDatabaseWatchIdentifier(token.value) : token.kind === "identifier" && isQuotedName(token.value);
|
|
54543
|
+
if (!valid)
|
|
54544
|
+
throw unsupported();
|
|
54545
|
+
this.position += 1;
|
|
54546
|
+
return token.value;
|
|
54547
|
+
}
|
|
54548
|
+
operations() {
|
|
54549
|
+
const events = [];
|
|
54550
|
+
do {
|
|
54551
|
+
const token = this.token();
|
|
54552
|
+
const operation = DATABASE_WATCH_OPERATIONS.find((name) => isSqlKeyword(token, name));
|
|
54553
|
+
if (operation === void 0 || events.includes(operation))
|
|
54554
|
+
throw unsupported();
|
|
54555
|
+
this.position += 1;
|
|
54556
|
+
events.push(operation);
|
|
54557
|
+
} while (this.takeKeyword("or"));
|
|
54558
|
+
return DATABASE_WATCH_OPERATIONS.filter((operation) => events.includes(operation));
|
|
54559
|
+
}
|
|
54560
|
+
expectCapture() {
|
|
54561
|
+
const token = this.token();
|
|
54562
|
+
if (token.kind !== "word" && token.kind !== "identifier" || token.value !== "capture") {
|
|
54563
|
+
throw unsupported();
|
|
54564
|
+
}
|
|
54565
|
+
this.position += 1;
|
|
54566
|
+
}
|
|
54567
|
+
columns() {
|
|
54568
|
+
const columns = [];
|
|
54569
|
+
do {
|
|
54570
|
+
const token = this.token();
|
|
54571
|
+
if (columns.length >= DATABASE_WATCH_MAX_COLUMNS || token.kind !== "string" || token.text !== `'${token.value}'` || !isDatabaseWatchIdentifier(token.value) || columns.includes(token.value)) {
|
|
54572
|
+
throw unsupported();
|
|
54573
|
+
}
|
|
54574
|
+
this.position += 1;
|
|
54575
|
+
columns.push(token.value);
|
|
54576
|
+
} while (this.takeSymbol(","));
|
|
54577
|
+
return columns;
|
|
54578
|
+
}
|
|
54579
|
+
token() {
|
|
54580
|
+
const token = this.tokens[this.position];
|
|
54581
|
+
if (token === void 0)
|
|
54582
|
+
throw unsupported();
|
|
54583
|
+
return token;
|
|
54584
|
+
}
|
|
54585
|
+
takeKeyword(keyword) {
|
|
54586
|
+
if (!isSqlKeyword(this.tokens[this.position], keyword))
|
|
54587
|
+
return false;
|
|
54588
|
+
this.position += 1;
|
|
54589
|
+
return true;
|
|
54590
|
+
}
|
|
54591
|
+
expectKeyword(keyword) {
|
|
54592
|
+
if (!this.takeKeyword(keyword))
|
|
54593
|
+
throw unsupported();
|
|
54594
|
+
}
|
|
54595
|
+
takeSymbol(symbol) {
|
|
54596
|
+
const token = this.tokens[this.position];
|
|
54597
|
+
if (token?.kind !== "symbol" || token.value !== symbol)
|
|
54598
|
+
return false;
|
|
54599
|
+
this.position += 1;
|
|
54600
|
+
return true;
|
|
54601
|
+
}
|
|
54602
|
+
expectSymbol(symbol) {
|
|
54603
|
+
if (!this.takeSymbol(symbol))
|
|
54604
|
+
throw unsupported();
|
|
54605
|
+
}
|
|
54606
|
+
};
|
|
54607
|
+
function isNamedPgcf(token, ignoreCase) {
|
|
54608
|
+
if (token.kind !== "word" && token.kind !== "identifier")
|
|
54609
|
+
return false;
|
|
54610
|
+
return ignoreCase ? token.value.toLowerCase() === "pgcf" : token.value === "pgcf";
|
|
54611
|
+
}
|
|
54612
|
+
function isQuotedName(value) {
|
|
54613
|
+
const bytes = new TextEncoder().encode(value).byteLength;
|
|
54614
|
+
return bytes >= 1 && bytes <= MAX_NAME_BYTES;
|
|
54615
|
+
}
|
|
54616
|
+
function unsupported() {
|
|
54617
|
+
return new DatabaseMigrationAdmissionError("unsupported");
|
|
54618
|
+
}
|
|
54619
|
+
|
|
54137
54620
|
// packages/contracts/dist/database-migration-policies.js
|
|
54138
54621
|
var PolicyReader = class {
|
|
54139
54622
|
tokens;
|
|
@@ -54287,6 +54770,9 @@ function admitExpandOnlyMigrations(migrations) {
|
|
|
54287
54770
|
throw new DatabaseMigrationAdmissionError("invalid");
|
|
54288
54771
|
}
|
|
54289
54772
|
const statements = parseDatabaseMigrationStatements(sql);
|
|
54773
|
+
if (statements.some((statement) => statement.captureTrigger !== void 0 && isDatabaseWatchTriggerName(statement.captureTrigger.trigger))) {
|
|
54774
|
+
throw new DatabaseMigrationAdmissionError("unsupported");
|
|
54775
|
+
}
|
|
54290
54776
|
statementCount += statements.length;
|
|
54291
54777
|
return Object.freeze({ path: migration.path, statementCount: statements.length });
|
|
54292
54778
|
});
|
|
@@ -54300,6 +54786,11 @@ function parseDatabaseMigrationStatements(sql) {
|
|
|
54300
54786
|
if (statements.length === 0)
|
|
54301
54787
|
throw new DatabaseMigrationAdmissionError("invalid");
|
|
54302
54788
|
return Object.freeze(statements.map((statement) => {
|
|
54789
|
+
const capture = readMigrationCaptureTrigger(statement.tokens);
|
|
54790
|
+
if (capture !== void 0) {
|
|
54791
|
+
rejectMigrationSqlReferences(capture.header);
|
|
54792
|
+
return Object.freeze({ sql: statement.sql, captureTrigger: capture.target });
|
|
54793
|
+
}
|
|
54303
54794
|
rejectMigrationSqlReferences(statement.tokens, true);
|
|
54304
54795
|
const rlsTarget = readMigrationRlsTarget(statement.tokens);
|
|
54305
54796
|
if (rlsTarget !== void 0)
|