@amerged/ohmyhost-cli 0.1.44 → 0.1.46

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.
@@ -26645,7 +26645,7 @@ var require_discriminator = __commonJS({
26645
26645
  error,
26646
26646
  code(cxt) {
26647
26647
  const { gen, data, schema, parentSchema, it } = cxt;
26648
- const { oneOf: oneOf2 } = parentSchema;
26648
+ const { oneOf: oneOf3 } = parentSchema;
26649
26649
  if (!it.opts.discriminator) {
26650
26650
  throw new Error("discriminator: requires discriminator option");
26651
26651
  }
@@ -26654,7 +26654,7 @@ var require_discriminator = __commonJS({
26654
26654
  throw new Error("discriminator: requires propertyName");
26655
26655
  if (schema.mapping)
26656
26656
  throw new Error("discriminator: mapping is not supported");
26657
- if (!oneOf2)
26657
+ if (!oneOf3)
26658
26658
  throw new Error("discriminator: requires oneOf keyword");
26659
26659
  const valid = gen.let("valid", false);
26660
26660
  const tag = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
@@ -26682,8 +26682,8 @@ var require_discriminator = __commonJS({
26682
26682
  const oneOfMapping = {};
26683
26683
  const topRequired = hasRequired(parentSchema);
26684
26684
  let tagRequired = true;
26685
- for (let i = 0; i < oneOf2.length; i++) {
26686
- let sch = oneOf2[i];
26685
+ for (let i = 0; i < oneOf3.length; i++) {
26686
+ let sch = oneOf3[i];
26687
26687
  if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
26688
26688
  const ref = sch.$ref;
26689
26689
  sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
@@ -38776,14 +38776,14 @@ function isFrameworkConversionDiagnosticCode(value) {
38776
38776
  function parseFrameworkConversionDiagnostic(value) {
38777
38777
  if (typeof value !== "object" || value === null || Array.isArray(value))
38778
38778
  return null;
38779
- const record7 = value;
38780
- const code = Object.hasOwn(record7, "code") ? record7["code"] : void 0;
38781
- const path2 = Object.hasOwn(record7, "path") ? record7["path"] : void 0;
38779
+ const record8 = value;
38780
+ const code = Object.hasOwn(record8, "code") ? record8["code"] : void 0;
38781
+ const path2 = Object.hasOwn(record8, "path") ? record8["path"] : void 0;
38782
38782
  if (!isFrameworkConversionDiagnosticCode(code) || typeof path2 !== "string")
38783
38783
  return null;
38784
38784
  const definition = DETAIL_DEFINITIONS[code];
38785
- const fields = Object.entries(definition.fields ?? {}).map(([name, grammar]) => [name, grammar, Object.hasOwn(record7, name) ? record7[name] : void 0]);
38786
- if (Object.keys(record7).length !== fields.length + 2 || pathClassOf(definition, path2) === null || fields.some(([, grammar, field]) => !fieldMatches(grammar, field)))
38785
+ const fields = Object.entries(definition.fields ?? {}).map(([name, grammar]) => [name, grammar, Object.hasOwn(record8, name) ? record8[name] : void 0]);
38786
+ if (Object.keys(record8).length !== fields.length + 2 || pathClassOf(definition, path2) === null || fields.some(([, grammar, field]) => !fieldMatches(grammar, field)))
38787
38787
  return null;
38788
38788
  return Object.freeze(Object.fromEntries([
38789
38789
  ["code", code],
@@ -40219,6 +40219,48 @@ 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
+ },
40252
+ database_table_conflict: {
40253
+ status: 409,
40254
+ retryable: false,
40255
+ action: "A table with this name already exists with another definition. Choose another table name, or create the table again with the same columns, types, nullability, identity, defaults and primary key it has.",
40256
+ title: "Table already exists with another definition"
40257
+ },
40258
+ database_table_limit: {
40259
+ status: 409,
40260
+ retryable: false,
40261
+ action: "This database already holds the maximum number of tables in its public schema, so no table can be added. Use an existing table, or remove a table you no longer need through a deployed migration, then create the table again.",
40262
+ title: "Database table limit reached"
40263
+ },
40222
40264
  project_notes_conflict: {
40223
40265
  status: 409,
40224
40266
  retryable: false,
@@ -40483,7 +40525,7 @@ var PROBLEM_POLICY = {
40483
40525
  api_key_permissions_unavailable: {
40484
40526
  status: 503,
40485
40527
  retryable: false,
40486
- action: "Report the platform user-key permission configuration through feedback. Repeated login or creation will not fix it.",
40528
+ 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
40529
  title: "Token permissions unavailable"
40488
40530
  },
40489
40531
  reconciliation_exhausted: {
@@ -41354,8 +41396,8 @@ var isPaidValidationRecord = (value) => {
41354
41396
  if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).sort().join(",") !== "content,name,purpose,type") {
41355
41397
  return false;
41356
41398
  }
41357
- const record7 = value;
41358
- return (record7["type"] === "CNAME" || record7["type"] === "TXT") && boundedString(record7["name"], 1, 253) && boundedString(record7["content"], 1, 2048) && ["traffic", "ownership", "ssl", "dcv_delegation"].includes(String(record7["purpose"]));
41399
+ const record8 = value;
41400
+ return (record8["type"] === "CNAME" || record8["type"] === "TXT") && boundedString(record8["name"], 1, 253) && boundedString(record8["content"], 1, 2048) && ["traffic", "ownership", "ssl", "dcv_delegation"].includes(String(record8["purpose"]));
41359
41401
  };
41360
41402
  var isHttpsUrlValue = (value) => typeof value === "string" && isHttpsUrl(value);
41361
41403
 
@@ -41500,7 +41542,7 @@ async function prepareLocalSourcePublication(input) {
41500
41542
  request = { ...request, expected_commit_sha: link2.source.last_confirmed_commit_sha };
41501
41543
  }
41502
41544
  parseManagedSourceRequest("upload", request);
41503
- const record7 = {
41545
+ const record8 = {
41504
41546
  version: 1,
41505
41547
  ...scope,
41506
41548
  operation_id: null,
@@ -41509,16 +41551,16 @@ async function prepareLocalSourcePublication(input) {
41509
41551
  request
41510
41552
  };
41511
41553
  if (original !== null) {
41512
- assertSameAttempt(original, record7);
41554
+ assertSameAttempt(original, record8);
41513
41555
  } else
41514
- await writePending(record7, true);
41556
+ await writePending(record8, true);
41515
41557
  return {
41516
41558
  request,
41517
41559
  accepted: async (operation) => {
41518
41560
  const current = await readPending(scope, replay);
41519
41561
  if (current === null)
41520
41562
  throw new LocalSourcePublicationError("source_publication_missing");
41521
- assertSameAttempt(current, record7);
41563
+ assertSameAttempt(current, record8);
41522
41564
  if (current.operation_id !== null && current.operation_id !== operation.id)
41523
41565
  throw new ResponseContractError();
41524
41566
  if (!replay)
@@ -41537,7 +41579,7 @@ async function prepareLocalSourcePublication(input) {
41537
41579
  const current = await readPending(scope);
41538
41580
  if (current === null || current.operation_id !== null)
41539
41581
  return;
41540
- assertSameAttempt(current, record7);
41582
+ assertSameAttempt(current, record8);
41541
41583
  } catch (error2) {
41542
41584
  if (error2 instanceof LocalSourcePublicationError && (error2.code === "source_publication_scope_mismatch" || error2.code === "source_publication_pending"))
41543
41585
  return;
@@ -41740,12 +41782,12 @@ async function readRecord(path2) {
41740
41782
  await handle?.close();
41741
41783
  }
41742
41784
  }
41743
- async function writePending(record7, create, completed = false) {
41744
- const parent = join3(record7.directory, ".ohmyhost");
41785
+ async function writePending(record8, create, completed = false) {
41786
+ const parent = join3(record8.directory, ".ohmyhost");
41745
41787
  for (const directory of [
41746
41788
  parent,
41747
- record7.metadata_directory,
41748
- join3(record7.metadata_directory, "publications")
41789
+ record8.metadata_directory,
41790
+ join3(record8.metadata_directory, "publications")
41749
41791
  ]) {
41750
41792
  try {
41751
41793
  await fs2.mkdir(directory, { mode: 448 });
@@ -41756,10 +41798,10 @@ async function writePending(record7, create, completed = false) {
41756
41798
  await checkedDirectory(directory);
41757
41799
  await fs2.chmod(directory, 448);
41758
41800
  }
41759
- const target = completed ? completedPath(record7) : pendingPath(record7);
41801
+ const target = completed ? completedPath(record8) : pendingPath(record8);
41760
41802
  const temporary = create ? target : `${target}.${randomUUID()}.tmp`;
41761
41803
  try {
41762
- await fs2.writeFile(temporary, `${JSON.stringify(record7)}
41804
+ await fs2.writeFile(temporary, `${JSON.stringify(record8)}
41763
41805
  `, { mode: 384, flag: "wx" });
41764
41806
  if (!create)
41765
41807
  await fs2.rename(temporary, target);
@@ -42389,6 +42431,9 @@ var DATABASE_WEBHOOK_EVENT_TYPES = [
42389
42431
  "database.resized",
42390
42432
  "database.deleted"
42391
42433
  ];
42434
+ var DATABASE_WATCH_OPERATIONS = ["insert", "update", "delete"];
42435
+ var DATABASE_WATCH_MAX_COLUMNS = 32;
42436
+ var DATABASE_WATCH_DEFAULT_SCHEMA = "public";
42392
42437
  var DATABASE_WEBHOOK_MAX_EVENT_TYPES = 16;
42393
42438
  var DATABASE_WEBHOOK_MAX_HEADERS = 8;
42394
42439
  var DATABASE_WEBHOOK_URL_MAX_LENGTH = 2048;
@@ -42432,7 +42477,22 @@ var USAGE_MAX_RANGE_MS = 31 * 24 * USAGE_HOUR_MS;
42432
42477
  var WEBHOOK_KEYS = ["id", "environment", "url", "event_types", "header_names", "created_at"];
42433
42478
  var WEBHOOK_ID_PATTERN = /^whk_[a-z0-9]{20}$/u;
42434
42479
  var DELIVERY_ID_PATTERN = /^dlv_[0-9a-f]{20}$/u;
42435
- var EVENT_ID_PATTERN = /^evt_[0-9a-f]{20}$/u;
42480
+ 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;
42481
+ var DATA_SUBSCRIPTION_PATTERN = /^data\.(?:\*|[a-z][a-z0-9_]{0,47})$/u;
42482
+ var DATA_EVENT_TYPE_PATTERN = /^data\.[a-z][a-z0-9_]{0,47}$/u;
42483
+ var DATABASE_WATCH_ID_PATTERN = /^pgcf_watch_[0-9a-f]{8}$/u;
42484
+ var DATABASE_WATCH_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/u;
42485
+ var DATABASE_WATCH_FORBIDDEN_SCHEMAS = ["information_schema", "pgcf", "ohmyhost"];
42486
+ var DATABASE_WATCH_KEYS = [
42487
+ "id",
42488
+ "environment",
42489
+ "schema",
42490
+ "table",
42491
+ "columns",
42492
+ "operations",
42493
+ "topic",
42494
+ "event_type"
42495
+ ];
42436
42496
  var SCHEDULE_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,62}$/u;
42437
42497
  var CURSOR_PATTERN = /^[A-Za-z0-9._|:=+/-]{1,200}$/u;
42438
42498
  var HEADER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]{0,63}$/u;
@@ -42454,11 +42514,53 @@ function parseDatabaseWebhookId(value) {
42454
42514
  throw new TypeError("Invalid database webhook id");
42455
42515
  return value;
42456
42516
  }
42517
+ function parseDatabaseWatchId(value) {
42518
+ if (!isDatabaseWatchTriggerName(value))
42519
+ throw new TypeError("Invalid database watch id");
42520
+ return value;
42521
+ }
42457
42522
  function parseDatabaseDeliveryId(value) {
42458
42523
  if (!matches(value, DELIVERY_ID_PATTERN))
42459
42524
  throw new TypeError("Invalid database delivery id");
42460
42525
  return value;
42461
42526
  }
42527
+ function isDatabaseWatchTriggerName(value) {
42528
+ return matches(value, DATABASE_WATCH_ID_PATTERN);
42529
+ }
42530
+ function isDatabaseWatchIdentifier(value) {
42531
+ return matches(value, DATABASE_WATCH_IDENTIFIER_PATTERN);
42532
+ }
42533
+ function isDatabaseWatchSchema(value) {
42534
+ if (!isDatabaseWatchIdentifier(value))
42535
+ return false;
42536
+ const lower = value.toLowerCase();
42537
+ return !lower.startsWith("pg_") && !DATABASE_WATCH_FORBIDDEN_SCHEMAS.includes(lower);
42538
+ }
42539
+ function dataEventTopic(tableName) {
42540
+ const mapped = Array.from(tableName, (character) => {
42541
+ const lower = Array.from(character.toLowerCase())[0] ?? "_";
42542
+ return /^[a-z0-9_]$/u.test(lower) ? lower : "_";
42543
+ }).join("");
42544
+ const topic = /^[a-z]/u.test(mapped) ? mapped : `t_${mapped}`;
42545
+ return topic.slice(0, 48);
42546
+ }
42547
+ function parseDatabaseWatchCreateRequest(value) {
42548
+ 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)
42549
+ throw new TypeError("Invalid database watch request");
42550
+ return {
42551
+ ...parseDatabaseWatchTarget({ schema: value["schema"], table: value["table"] }),
42552
+ columns: value["columns"],
42553
+ operations: parseWatchOperations(value["operations"])
42554
+ };
42555
+ }
42556
+ function parseDatabaseWatchTarget(value) {
42557
+ if (!record2(value) || !onlyKeys(value, ["schema", "table"]) || !isDatabaseWatchIdentifier(value["table"]))
42558
+ throw new TypeError("Invalid database watch target");
42559
+ const schema = value["schema"] === void 0 ? DATABASE_WATCH_DEFAULT_SCHEMA : value["schema"];
42560
+ if (!isDatabaseWatchSchema(schema))
42561
+ throw new TypeError("Invalid database watch target");
42562
+ return { schema, table: value["table"] };
42563
+ }
42462
42564
  function parseDatabaseScheduleName(value) {
42463
42565
  if (!matches(value, SCHEDULE_NAME_PATTERN))
42464
42566
  throw new TypeError("Invalid database schedule name");
@@ -42515,6 +42617,20 @@ function assertDatabaseWebhook(value) {
42515
42617
  throw new TypeError("Invalid database webhook");
42516
42618
  assertWebhookFields(value);
42517
42619
  }
42620
+ function assertDatabaseWatch(value) {
42621
+ 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"]}`)
42622
+ throw new TypeError("Invalid database watch");
42623
+ }
42624
+ function assertDatabaseWatchPage(value) {
42625
+ if (!record2(value) || !exact2(value, ["items"]) || !pageItems(value["items"]))
42626
+ throw new TypeError("Invalid database watch page");
42627
+ for (const item of value["items"])
42628
+ assertDatabaseWatch(item);
42629
+ }
42630
+ function assertDatabaseWatchDeleted(value) {
42631
+ if (!record2(value) || !exact2(value, ["id", "deleted"]) || !matches(value["id"], DATABASE_WATCH_ID_PATTERN) || value["deleted"] !== true)
42632
+ throw new TypeError("Invalid database watch deletion");
42633
+ }
42518
42634
  function assertDatabaseWebhookCreated(value) {
42519
42635
  if (!record2(value) || !exact2(value, [...WEBHOOK_KEYS, "secret"]) || typeof value["secret"] !== "string" || value["secret"].length === 0)
42520
42636
  throw new TypeError("Invalid database webhook");
@@ -42543,7 +42659,7 @@ function assertDatabaseWebhookDelivery(value) {
42543
42659
  "delivered_at",
42544
42660
  "last_status",
42545
42661
  "last_error"
42546
- ]) || !matches(value["id"], DELIVERY_ID_PATTERN) || !matches(value["event_id"], EVENT_ID_PATTERN) || !oneOf(DATABASE_WEBHOOK_EVENT_TYPES, 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)))
42662
+ ]) || !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
42663
  throw new TypeError("Invalid database webhook delivery");
42548
42664
  }
42549
42665
  function assertDatabaseWebhookDeliveryPage(value) {
@@ -42626,7 +42742,7 @@ function assertUsageRow(value) {
42626
42742
  throw new TypeError("Invalid database execution usage row");
42627
42743
  }
42628
42744
  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((type) => oneOf(DATABASE_WEBHOOK_EVENT_TYPES, type)) || !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"]))
42745
+ 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
42746
  throw new TypeError("Invalid database webhook");
42631
42747
  }
42632
42748
  function parseHttpsUrl(value) {
@@ -42643,11 +42759,33 @@ function isHttpsUrl2(value) {
42643
42759
  return false;
42644
42760
  }
42645
42761
  }
42762
+ function isDatabaseWebhookEventPattern(value) {
42763
+ return oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value) || matches(value, DATA_SUBSCRIPTION_PATTERN);
42764
+ }
42765
+ function isDeliveredEventType(value) {
42766
+ return oneOf(DATABASE_WEBHOOK_EVENT_TYPES, value) || matches(value, DATA_EVENT_TYPE_PATTERN);
42767
+ }
42768
+ function isDatabaseWebhookEventTypes(value) {
42769
+ return Array.isArray(value) && value.length >= 1 && value.length <= DATABASE_WEBHOOK_MAX_EVENT_TYPES && value.every(isDatabaseWebhookEventPattern) && new Set(value).size === value.length;
42770
+ }
42646
42771
  function parseEventTypes(value) {
42647
- if (!Array.isArray(value) || value.length < 1 || value.length > DATABASE_WEBHOOK_MAX_EVENT_TYPES || !value.every((type) => oneOf(DATABASE_WEBHOOK_EVENT_TYPES, type)) || new Set(value).size !== value.length)
42772
+ if (!isDatabaseWebhookEventTypes(value))
42648
42773
  throw new TypeError("Invalid database webhook event types");
42649
42774
  return value;
42650
42775
  }
42776
+ function parseWatchOperations(value) {
42777
+ if (value === void 0)
42778
+ return DATABASE_WATCH_OPERATIONS;
42779
+ if (!Array.isArray(value) || value.length < 1 || !value.every((operation) => oneOf(DATABASE_WATCH_OPERATIONS, operation)) || new Set(value).size !== value.length)
42780
+ throw new TypeError("Invalid database watch request");
42781
+ return DATABASE_WATCH_OPERATIONS.filter((operation) => value.includes(operation));
42782
+ }
42783
+ function isCanonicalOperations(value) {
42784
+ if (!Array.isArray(value) || value.length < 1)
42785
+ return false;
42786
+ const canonical = DATABASE_WATCH_OPERATIONS.filter((operation) => value.includes(operation));
42787
+ return canonical.length === value.length && canonical.every((op, index) => op === value[index]);
42788
+ }
42651
42789
  function parseHeaders(value) {
42652
42790
  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
42791
  throw new TypeError("Invalid database webhook headers");
@@ -42723,6 +42861,170 @@ function exact2(value, keys) {
42723
42861
  return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
42724
42862
  }
42725
42863
 
42864
+ // packages/contracts/dist/database-tables.js
42865
+ var DATABASE_TABLE_COLUMN_TYPES = [
42866
+ "text",
42867
+ "integer",
42868
+ "bigint",
42869
+ "boolean",
42870
+ "numeric",
42871
+ "double precision",
42872
+ "timestamptz",
42873
+ "date",
42874
+ "uuid",
42875
+ "jsonb"
42876
+ ];
42877
+ var DATABASE_TABLE_DEFAULTS = ["now", "uuid"];
42878
+ var DATABASE_TABLE_MAX_COLUMNS = 64;
42879
+ var DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS = 8;
42880
+ var DATABASE_TABLE_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_]{0,62}$/u;
42881
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
42882
+ "all",
42883
+ "analyse",
42884
+ "analyze",
42885
+ "and",
42886
+ "any",
42887
+ "array",
42888
+ "as",
42889
+ "asc",
42890
+ "asymmetric",
42891
+ "both",
42892
+ "case",
42893
+ "cast",
42894
+ "check",
42895
+ "collate",
42896
+ "column",
42897
+ "constraint",
42898
+ "create",
42899
+ "current_catalog",
42900
+ "current_date",
42901
+ "current_role",
42902
+ "current_time",
42903
+ "current_timestamp",
42904
+ "current_user",
42905
+ "default",
42906
+ "deferrable",
42907
+ "desc",
42908
+ "distinct",
42909
+ "do",
42910
+ "else",
42911
+ "end",
42912
+ "except",
42913
+ "false",
42914
+ "fetch",
42915
+ "for",
42916
+ "foreign",
42917
+ "from",
42918
+ "grant",
42919
+ "group",
42920
+ "having",
42921
+ "in",
42922
+ "initially",
42923
+ "intersect",
42924
+ "into",
42925
+ "lateral",
42926
+ "leading",
42927
+ "limit",
42928
+ "localtime",
42929
+ "localtimestamp",
42930
+ "not",
42931
+ "null",
42932
+ "offset",
42933
+ "on",
42934
+ "only",
42935
+ "or",
42936
+ "order",
42937
+ "placing",
42938
+ "primary",
42939
+ "references",
42940
+ "returning",
42941
+ "select",
42942
+ "session_user",
42943
+ "some",
42944
+ "symmetric",
42945
+ "table",
42946
+ "then",
42947
+ "to",
42948
+ "trailing",
42949
+ "true",
42950
+ "union",
42951
+ "unique",
42952
+ "user",
42953
+ "using",
42954
+ "variadic",
42955
+ "when",
42956
+ "where",
42957
+ "window",
42958
+ "with"
42959
+ ]);
42960
+ function isDatabaseTableIdentifier(value) {
42961
+ return typeof value === "string" && DATABASE_TABLE_IDENTIFIER_PATTERN.test(value) && !value.startsWith("pg_") && !RESERVED_WORDS.has(value);
42962
+ }
42963
+ function parseDatabaseTableCreateRequest(value) {
42964
+ if (!record3(value) || !onlyKeys2(value, ["table", "columns", "primaryKey"]) || !isDatabaseTableIdentifier(value["table"]) || !Array.isArray(value["columns"]) || value["columns"].length < 1 || value["columns"].length > DATABASE_TABLE_MAX_COLUMNS)
42965
+ throw new TypeError("Invalid database table request");
42966
+ const inputs = value["columns"].map(parseColumn);
42967
+ const names = inputs.map((column) => column.name);
42968
+ if (new Set(names).size !== names.length || inputs.filter((column) => column.identity).length > 1)
42969
+ throw new TypeError("Invalid database table request");
42970
+ const primaryKey = parsePrimaryKey(value["primaryKey"], names);
42971
+ return {
42972
+ table: value["table"],
42973
+ columns: inputs.map((column) => completeColumn(column, primaryKey.includes(column.name))),
42974
+ primaryKey
42975
+ };
42976
+ }
42977
+ function parseColumn(value) {
42978
+ if (!record3(value) || !onlyKeys2(value, ["name", "type", "nullable", "identity", "default"]) || !isDatabaseTableIdentifier(value["name"]) || !oneOf2(DATABASE_TABLE_COLUMN_TYPES, value["type"]) || value["nullable"] !== void 0 && typeof value["nullable"] !== "boolean" || value["identity"] !== void 0 && typeof value["identity"] !== "boolean" || value["default"] !== void 0 && !oneOf2(DATABASE_TABLE_DEFAULTS, value["default"]))
42979
+ throw new TypeError("Invalid database table request");
42980
+ const type = value["type"];
42981
+ const identity = value["identity"] === true;
42982
+ const defaultValue = value["default"] ?? null;
42983
+ if (identity && type !== "integer" && type !== "bigint")
42984
+ throw new TypeError("Invalid database table request");
42985
+ if (identity && defaultValue !== null)
42986
+ throw new TypeError("Invalid database table request");
42987
+ if (defaultValue === "now" && type !== "timestamptz")
42988
+ throw new TypeError("Invalid database table request");
42989
+ if (defaultValue === "uuid" && type !== "uuid")
42990
+ throw new TypeError("Invalid database table request");
42991
+ return {
42992
+ name: value["name"],
42993
+ type,
42994
+ nullable: value["nullable"],
42995
+ identity,
42996
+ default: defaultValue
42997
+ };
42998
+ }
42999
+ function parsePrimaryKey(value, names) {
43000
+ if (value === void 0)
43001
+ return [];
43002
+ if (!Array.isArray(value) || value.length < 1 || value.length > DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS || new Set(value).size !== value.length || !value.every((name) => typeof name === "string" && names.includes(name)))
43003
+ throw new TypeError("Invalid database table request");
43004
+ return value;
43005
+ }
43006
+ function completeColumn(column, keyed) {
43007
+ const required = column.identity || keyed;
43008
+ if (required && column.nullable === true)
43009
+ throw new TypeError("Invalid database table request");
43010
+ return {
43011
+ name: column.name,
43012
+ type: column.type,
43013
+ nullable: required ? false : column.nullable ?? true,
43014
+ identity: column.identity,
43015
+ default: column.default
43016
+ };
43017
+ }
43018
+ function oneOf2(allowed, value) {
43019
+ return typeof value === "string" && allowed.includes(value);
43020
+ }
43021
+ function record3(value) {
43022
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43023
+ }
43024
+ function onlyKeys2(value, allowed) {
43025
+ return Object.keys(value).every((key) => allowed.includes(key));
43026
+ }
43027
+
42726
43028
  // packages/contracts/dist/database-sizes.js
42727
43029
  var DATABASE_SIZES = Object.freeze([
42728
43030
  Object.freeze({
@@ -43083,6 +43385,16 @@ var recognizedCommandLabel = (argv) => {
43083
43385
  if (values[1] === "revoke")
43084
43386
  return "database access revoke";
43085
43387
  }
43388
+ if (value === "database" && values[0] === "watch") {
43389
+ if (values[1] === "create")
43390
+ return "database watch create";
43391
+ if (values[1] === "list")
43392
+ return "database watch list";
43393
+ if (values[1] === "delete")
43394
+ return "database watch delete";
43395
+ }
43396
+ if (value === "database" && values[0] === "table" && values[1] === "create")
43397
+ return "database table create";
43086
43398
  if (value === "database" && values[0] === "webhook") {
43087
43399
  if (values[1] === "create")
43088
43400
  return "database webhook create";
@@ -43932,6 +44244,105 @@ var parseProductCliCommand = (argv) => {
43932
44244
  }
43933
44245
  throw new InvalidCommandError();
43934
44246
  }
44247
+ if (first === "database" && values[0] === "watch") {
44248
+ values.shift();
44249
+ const subcommand = values.shift();
44250
+ if (subcommand === "create") {
44251
+ if (!yes || wait || follow || revoke || stdin)
44252
+ throw new InvalidCommandError();
44253
+ const options = parseOptionalOptions(values, [
44254
+ "--project",
44255
+ "--environment",
44256
+ "--table",
44257
+ "--schema",
44258
+ "--columns",
44259
+ "--operations",
44260
+ "--idempotency-key"
44261
+ ]);
44262
+ const projectId = options["--project"], environment = options["--environment"], table = options["--table"], schema = options["--schema"], columns = options["--columns"], operations = options["--operations"], idempotencyKey = options["--idempotency-key"];
44263
+ if (!isValue(projectId) || !isValue(idempotencyKey) || !isValue(table) || !isValue(columns) || environment !== "dev" && environment !== "prod")
44264
+ throw new InvalidCommandError();
44265
+ const request = contractValue(() => parseDatabaseWatchCreateRequest({
44266
+ schema,
44267
+ table,
44268
+ columns: columns.split(","),
44269
+ ...operations === void 0 ? {} : { operations: operations.split(",") }
44270
+ }));
44271
+ return {
44272
+ kind: "database-watch-create",
44273
+ projectId,
44274
+ environment,
44275
+ request,
44276
+ idempotencyKey,
44277
+ yes: true,
44278
+ credentialStore
44279
+ };
44280
+ }
44281
+ if (subcommand === "list") {
44282
+ if (yes || wait || follow || revoke || stdin)
44283
+ throw new InvalidCommandError();
44284
+ const options = parseOptionalOptions(values, ["--project", "--environment"]);
44285
+ const projectId = options["--project"], environment = options["--environment"] ?? "dev";
44286
+ if (!isValue(projectId) || environment !== "dev" && environment !== "prod")
44287
+ throw new InvalidCommandError();
44288
+ return { kind: "database-watch-list", projectId, environment, credentialStore };
44289
+ }
44290
+ if (subcommand === "delete") {
44291
+ if (!yes || wait || follow || revoke || stdin)
44292
+ throw new InvalidCommandError();
44293
+ const options = parseOptionalOptions(values, [
44294
+ "--project",
44295
+ "--environment",
44296
+ "--watch",
44297
+ "--idempotency-key"
44298
+ ]);
44299
+ const projectId = options["--project"], environment = options["--environment"], watchId = options["--watch"], idempotencyKey = options["--idempotency-key"];
44300
+ if (!isValue(projectId) || !isValue(idempotencyKey) || environment !== "dev" && environment !== "prod")
44301
+ throw new InvalidCommandError();
44302
+ return {
44303
+ kind: "database-watch-delete",
44304
+ projectId,
44305
+ environment,
44306
+ watchId: contractValue(() => parseDatabaseWatchId(watchId)),
44307
+ idempotencyKey,
44308
+ yes: true,
44309
+ credentialStore
44310
+ };
44311
+ }
44312
+ throw new InvalidCommandError();
44313
+ }
44314
+ if (first === "database" && values[0] === "table") {
44315
+ values.shift();
44316
+ const subcommand = values.shift();
44317
+ if (subcommand === "create") {
44318
+ if (!yes || wait || follow || revoke || stdin)
44319
+ throw new InvalidCommandError();
44320
+ const options = parseOptionalOptions(values, [
44321
+ "--project",
44322
+ "--environment",
44323
+ "--table",
44324
+ "--columns-file",
44325
+ "--primary-key",
44326
+ "--idempotency-key"
44327
+ ]);
44328
+ const projectId = options["--project"], environment = options["--environment"], table = options["--table"], columnsFile = options["--columns-file"], primaryKey = options["--primary-key"], idempotencyKey = options["--idempotency-key"];
44329
+ const keyNames = primaryKey === void 0 ? [] : primaryKey.split(",");
44330
+ if (!isValue(projectId) || !isValue(idempotencyKey) || !isValue(table) || !isValue(columnsFile) || primaryKey !== void 0 && !keyNames.every(isDatabaseTableIdentifier) || keyNames.length > DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS || !isDatabaseTableIdentifier(table) || environment !== "dev" && environment !== "prod")
44331
+ throw new InvalidCommandError();
44332
+ return {
44333
+ kind: "database-table-create",
44334
+ projectId,
44335
+ environment,
44336
+ table,
44337
+ columnsFile,
44338
+ primaryKey: keyNames,
44339
+ idempotencyKey,
44340
+ yes: true,
44341
+ credentialStore
44342
+ };
44343
+ }
44344
+ throw new InvalidCommandError();
44345
+ }
43935
44346
  if (first === "database" && values[0] === "webhook") {
43936
44347
  values.shift();
43937
44348
  const subcommand = values.shift();
@@ -45078,7 +45489,7 @@ import { join as join5, resolve as resolve4 } from "node:path";
45078
45489
 
45079
45490
  // packages/contracts/dist/database-access.js
45080
45491
  function parseProjectDatabaseWrite(value) {
45081
- if (!record3(value) || !exact3(value, ["environment", "statement", "parameters"]) || !["dev", "prod"].includes(String(value["environment"])) || typeof value["statement"] !== "string" || !/^\s*(?:insert|update|delete)\b/iu.test(value["statement"]) || new TextEncoder().encode(value["statement"]).byteLength > 65536 || value["statement"].includes("\0") || !Array.isArray(value["parameters"]) || value["parameters"].length > 100)
45492
+ if (!record4(value) || !exact3(value, ["environment", "statement", "parameters"]) || !["dev", "prod"].includes(String(value["environment"])) || typeof value["statement"] !== "string" || !/^\s*(?:insert|update|delete)\b/iu.test(value["statement"]) || new TextEncoder().encode(value["statement"]).byteLength > 65536 || value["statement"].includes("\0") || !Array.isArray(value["parameters"]) || value["parameters"].length > 100)
45082
45493
  throw new TypeError("Invalid database write");
45083
45494
  const budget = { nodes: 0 };
45084
45495
  for (const parameter of value["parameters"])
@@ -45092,7 +45503,7 @@ function parseProjectDatabaseWrite(value) {
45092
45503
  };
45093
45504
  }
45094
45505
  function assertProjectDatabaseWriteReceipt(value) {
45095
- if (!record3(value) || !exact3(value, ["operation_id", "environment", "state", "command", "affected_rows", "error"]) || typeof value["operation_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["operation_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["running", "succeeded", "failed"].includes(String(value["state"])))
45506
+ if (!record4(value) || !exact3(value, ["operation_id", "environment", "state", "command", "affected_rows", "error"]) || typeof value["operation_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["operation_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["running", "succeeded", "failed"].includes(String(value["state"])))
45096
45507
  throw new TypeError("Invalid database write receipt");
45097
45508
  if (value["state"] === "succeeded") {
45098
45509
  if (!["INSERT", "UPDATE", "DELETE"].includes(String(value["command"])) || !Number.isSafeInteger(value["affected_rows"]) || Number(value["affected_rows"]) < 0 || Number(value["affected_rows"]) > 1e3 || value["error"] !== null)
@@ -45105,7 +45516,7 @@ function assertProjectDatabaseWriteReceipt(value) {
45105
45516
  throw new TypeError("Invalid database write result");
45106
45517
  } else {
45107
45518
  const error = value["error"];
45108
- if (!record3(error) || typeof error["code"] !== "string" || !error["code"].startsWith("database_write_") || JSON.stringify(error) !== JSON.stringify(publicOperationFailure(error["code"])))
45519
+ if (!record4(error) || typeof error["code"] !== "string" || !error["code"].startsWith("database_write_") || JSON.stringify(error) !== JSON.stringify(publicOperationFailure(error["code"])))
45109
45520
  throw new TypeError("Invalid database write error");
45110
45521
  }
45111
45522
  }
@@ -45129,12 +45540,12 @@ var ACCESS_KEYS = [
45129
45540
  ];
45130
45541
  var ACCESS_ROLE_PATTERN = /^ohmyho_da_[0-7][0-9a-hjkmnp-tv-z]{25}_[0-9a-z]{8}$/u;
45131
45542
  function assertProjectDatabaseAccess(value) {
45132
- if (!record3(value) || !exact3(value, ACCESS_KEYS))
45543
+ if (!record4(value) || !exact3(value, ACCESS_KEYS))
45133
45544
  throw new TypeError("Invalid database access");
45134
45545
  assertAccessFields(value);
45135
45546
  }
45136
45547
  function assertProjectDatabaseAccessCredential(value) {
45137
- if (!record3(value) || !exact3(value, [...ACCESS_KEYS, "connection_uri", "psql_command"]))
45548
+ if (!record4(value) || !exact3(value, [...ACCESS_KEYS, "connection_uri", "psql_command"]))
45138
45549
  throw new TypeError("Invalid database access credential");
45139
45550
  assertAccessFields(value);
45140
45551
  const uri = value["connection_uri"];
@@ -45150,7 +45561,7 @@ function assertProjectDatabaseAccessCredential(value) {
45150
45561
  throw new TypeError("Invalid database access credential");
45151
45562
  }
45152
45563
  function assertProjectDatabaseAccessPage(value) {
45153
- if (!record3(value) || !exact3(value, ["items"]) || !Array.isArray(value["items"]) || value["items"].length > 50)
45564
+ if (!record4(value) || !exact3(value, ["items"]) || !Array.isArray(value["items"]) || value["items"].length > 50)
45154
45565
  throw new TypeError("Invalid database access page");
45155
45566
  for (const item of value["items"])
45156
45567
  assertProjectDatabaseAccess(item);
@@ -45185,14 +45596,14 @@ function json(value, depth, budget) {
45185
45596
  json(entry, depth + 1, budget);
45186
45597
  return;
45187
45598
  }
45188
- if (record3(value)) {
45599
+ if (record4(value)) {
45189
45600
  for (const entry of Object.values(value))
45190
45601
  json(entry, depth + 1, budget);
45191
45602
  return;
45192
45603
  }
45193
45604
  throw new TypeError("Invalid database parameter");
45194
45605
  }
45195
- function record3(value) {
45606
+ function record4(value) {
45196
45607
  return typeof value === "object" && value !== null && !Array.isArray(value);
45197
45608
  }
45198
45609
  function exact3(value, keys) {
@@ -45201,7 +45612,7 @@ function exact3(value, keys) {
45201
45612
 
45202
45613
  // packages/contracts/dist/credit-pricing.js
45203
45614
  function assertOrganizationCreditUsage(value) {
45204
- const record7 = (input, keys) => {
45615
+ const record8 = (input, keys) => {
45205
45616
  if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).sort().join() !== keys.split(",").sort().join())
45206
45617
  throw new TypeError("Invalid usage fields");
45207
45618
  return input;
@@ -45209,30 +45620,30 @@ function assertOrganizationCreditUsage(value) {
45209
45620
  const identifier2 = (input) => typeof input === "string" && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(input);
45210
45621
  const amount = (input) => typeof input === "string" && /^(0|[1-9][0-9]{0,37})$/u.test(input);
45211
45622
  const instant2 = (input) => typeof input === "string" && Number.isFinite(Date.parse(input)) && new Date(input).toISOString() === input;
45212
- const report = record7(value, "organization_id,as_of,unit,period,from,projects,days");
45623
+ const report = record8(value, "organization_id,as_of,unit,period,from,projects,days");
45213
45624
  if (!identifier2(report["organization_id"]) || !instant2(report["as_of"]) || !instant2(report["from"]) || report["unit"] !== "microcredits" || typeof report["period"] !== "string" || !/^20[0-9]{2}-(0[1-9]|1[0-2])$/u.test(report["period"]) || !Array.isArray(report["projects"]) || report["projects"].length > 1e3 || !Array.isArray(report["days"]) || report["days"].length > 15)
45214
45625
  throw new TypeError("Invalid usage report");
45215
45626
  let previous = "";
45216
45627
  for (const input of report["projects"]) {
45217
- const project = record7(input, "project_id,used_micros");
45628
+ const project = record8(input, "project_id,used_micros");
45218
45629
  if (!identifier2(project["project_id"]) || String(project["project_id"]) <= previous || !amount(project["used_micros"]))
45219
45630
  throw new TypeError("Invalid usage project");
45220
45631
  previous = String(project["project_id"]);
45221
45632
  }
45222
45633
  let previousDay = "";
45223
45634
  for (const input of report["days"]) {
45224
- const day = record7(input, "day,projects");
45635
+ const day = record8(input, "day,projects");
45225
45636
  if (typeof day["day"] !== "string" || !/^20[0-9]{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/u.test(day["day"]) || day["day"] <= previousDay || !Array.isArray(day["projects"]) || day["projects"].length > 1e3)
45226
45637
  throw new TypeError("Invalid usage day");
45227
45638
  previousDay = day["day"];
45228
45639
  previous = "";
45229
45640
  for (const projectInput of day["projects"]) {
45230
- const project = record7(projectInput, "project_id,meters");
45641
+ const project = record8(projectInput, "project_id,meters");
45231
45642
  if (!identifier2(project["project_id"]) || String(project["project_id"]) <= previous || !Array.isArray(project["meters"]) || project["meters"].length > 1e3)
45232
45643
  throw new TypeError("Invalid usage project");
45233
45644
  previous = String(project["project_id"]);
45234
45645
  for (const meterInput of project["meters"]) {
45235
- const meter = record7(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
45646
+ const meter = record8(meterInput, "environment_id,meter,unit,rate_card_id,quantity,charged_micros");
45236
45647
  if (meter["environment_id"] !== null && !identifier2(meter["environment_id"]))
45237
45648
  throw new TypeError("Invalid usage environment");
45238
45649
  for (const key of ["meter", "unit", "rate_card_id"])
@@ -45267,16 +45678,16 @@ function assertOrganizationAccount(value) {
45267
45678
 
45268
45679
  // packages/contracts/dist/database-compute.js
45269
45680
  function assertDatabaseCompute(value) {
45270
- if (!record4(value) || !exact4(value, ["data_mode", "database", "environment", "observed_at", "project_id"]) || typeof value["project_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["project_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["shared", "isolated"].includes(String(value["data_mode"])) || typeof value["observed_at"] !== "string" || !Number.isFinite(Date.parse(value["observed_at"])) || new Date(value["observed_at"]).toISOString() !== value["observed_at"])
45681
+ if (!record5(value) || !exact4(value, ["data_mode", "database", "environment", "observed_at", "project_id"]) || typeof value["project_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value["project_id"]) || !["dev", "prod"].includes(String(value["environment"])) || !["shared", "isolated"].includes(String(value["data_mode"])) || typeof value["observed_at"] !== "string" || !Number.isFinite(Date.parse(value["observed_at"])) || new Date(value["observed_at"]).toISOString() !== value["observed_at"])
45271
45682
  throw new TypeError("Invalid database compute report");
45272
45683
  const db = value["database"];
45273
45684
  if (db === null)
45274
45685
  return;
45275
- if (record4(db) && db["provider"] === "pgcf") {
45686
+ if (record5(db) && db["provider"] === "pgcf") {
45276
45687
  assertPgcfCompute(db);
45277
45688
  return;
45278
45689
  }
45279
- if (!record4(db) || !exact4(db, [
45690
+ if (!record5(db) || !exact4(db, [
45280
45691
  "disabled",
45281
45692
  "max_cu",
45282
45693
  "max_memory_gb",
@@ -45307,7 +45718,7 @@ function assertPgcfCompute(db) {
45307
45718
  ]) || db["memory_mib"] !== size.memoryMiB || db["cpu_millicores"] !== size.cpuMillicores || db["storage_mib"] !== size.storageMiB || db["max_connections"] !== size.maxConnections || !Number.isSafeInteger(db["suspend_timeout_seconds"]) || Number(db["suspend_timeout_seconds"]) < 60 || Number(db["suspend_timeout_seconds"]) > 900 || !states.includes(String(db["state"])) || db["pending_state"] !== null && !states.includes(String(db["pending_state"])) || typeof db["disabled"] !== "boolean" || !["us", "eu"].includes(String(db["region"])))
45308
45719
  throw new TypeError("Invalid fixed database compute configuration");
45309
45720
  }
45310
- function record4(v) {
45721
+ function record5(v) {
45311
45722
  return typeof v === "object" && v !== null && !Array.isArray(v);
45312
45723
  }
45313
45724
  function exact4(v, keys) {
@@ -45317,7 +45728,7 @@ function exact4(v, keys) {
45317
45728
  // apps/product-cli/package.json
45318
45729
  var package_default = {
45319
45730
  name: "@ohmyhost/product-cli",
45320
- version: "0.1.44",
45731
+ version: "0.1.46",
45321
45732
  private: true,
45322
45733
  ohmyhost: {
45323
45734
  deployment: "production",
@@ -45364,32 +45775,32 @@ var package_default = {
45364
45775
  };
45365
45776
 
45366
45777
  // packages/contracts/dist/project-context.js
45367
- var record5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
45778
+ var record6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
45368
45779
  var timestamp4 = (v) => typeof v === "string" && Number.isFinite(Date.parse(v)) && new Date(v).toISOString() === v;
45369
45780
  var version = (v) => Number.isInteger(v) && Number(v) >= 0 && Number(v) <= 2147483647;
45370
45781
  var bounded = (v, lines, bytes) => typeof v === "string" && !/[\r\u2028\u2029]/u.test(v) && !new RegExp("\\p{Cc}", "u").test(v.replace(/[\n\t]/gu, "")) && v.split("\n").length <= lines && new TextEncoder().encode(v).length <= bytes;
45371
45782
  function assertProjectNotesReceipt(v) {
45372
- if (!record5(v) || Object.keys(v).sort().join(",") !== "updated_at,version" || !version(v["version"]) || v["version"] === 0 || !timestamp4(v["updated_at"]))
45783
+ if (!record6(v) || Object.keys(v).sort().join(",") !== "updated_at,version" || !version(v["version"]) || v["version"] === 0 || !timestamp4(v["updated_at"]))
45373
45784
  throw new TypeError("Invalid project notes receipt");
45374
45785
  }
45375
45786
  function assertProjectContext(v) {
45376
- if (!record5(v) || Object.keys(v).sort().join(",") !== "credit_access,generated_at,markdown,notes,project_id" || typeof v["project_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(v["project_id"]) || !timestamp4(v["generated_at"]) || !["included", "not_authorized"].includes(String(v["credit_access"])) || !bounded(v["markdown"], 500, 32768))
45787
+ if (!record6(v) || Object.keys(v).sort().join(",") !== "credit_access,generated_at,markdown,notes,project_id" || typeof v["project_id"] !== "string" || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(v["project_id"]) || !timestamp4(v["generated_at"]) || !["included", "not_authorized"].includes(String(v["credit_access"])) || !bounded(v["markdown"], 500, 32768))
45377
45788
  throw new TypeError("Invalid project context");
45378
45789
  const notes = v["notes"];
45379
- if (!record5(notes) || Object.keys(notes).sort().join(",") !== "markdown,updated_at,version" || !version(notes["version"]) || !bounded(notes["markdown"], 250, 16384) || (notes["version"] === 0 ? notes["updated_at"] !== null || notes["markdown"] !== "" : !timestamp4(notes["updated_at"])))
45790
+ if (!record6(notes) || Object.keys(notes).sort().join(",") !== "markdown,updated_at,version" || !version(notes["version"]) || !bounded(notes["markdown"], 250, 16384) || (notes["version"] === 0 ? notes["updated_at"] !== null || notes["markdown"] !== "" : !timestamp4(notes["updated_at"])))
45380
45791
  throw new TypeError("Invalid project notes");
45381
45792
  }
45382
45793
 
45383
45794
  // packages/contracts/dist/project-exports.js
45384
45795
  function assertProjectExport(value) {
45385
- if (!record6(value) || Object.keys(value).sort().join(",") !== "archive,error,id,next_poll_after_seconds,next_request_at,project_id,requested_at,state" || typeof value["id"] !== "string" || !id.test(value["id"]) || typeof value["project_id"] !== "string" || !id.test(value["project_id"]) || !["queued", "running", "succeeded", "failed", "cancelled"].includes(String(value["state"])) || !timestamp5(value["requested_at"]) || !timestamp5(value["next_request_at"]))
45796
+ if (!record7(value) || Object.keys(value).sort().join(",") !== "archive,error,id,next_poll_after_seconds,next_request_at,project_id,requested_at,state" || typeof value["id"] !== "string" || !id.test(value["id"]) || typeof value["project_id"] !== "string" || !id.test(value["project_id"]) || !["queued", "running", "succeeded", "failed", "cancelled"].includes(String(value["state"])) || !timestamp5(value["requested_at"]) || !timestamp5(value["next_request_at"]))
45386
45797
  invalid4();
45387
45798
  const active = value["state"] === "queued" || value["state"] === "running";
45388
45799
  if (value["next_poll_after_seconds"] !== (active ? 5 : null))
45389
45800
  invalid4();
45390
45801
  if (value["state"] === "failed") {
45391
45802
  const error = value["error"];
45392
- if (!record6(error) || Object.keys(error).sort().join(",") !== "code,message,retryable,suggested_action" || typeof error["code"] !== "string")
45803
+ if (!record7(error) || Object.keys(error).sort().join(",") !== "code,message,retryable,suggested_action" || typeof error["code"] !== "string")
45393
45804
  invalid4();
45394
45805
  const expected = publicOperationFailure(error["code"]);
45395
45806
  for (const key of Object.keys(expected))
@@ -45403,7 +45814,7 @@ function assertProjectExport(value) {
45403
45814
  invalid4();
45404
45815
  return;
45405
45816
  }
45406
- if (value["state"] !== "succeeded" || !record6(archive) || Object.keys(archive).sort().join(",") !== "bytes,captured_at,download_expires_at,download_url,expires_at,sha256,sql_files" || !Number.isSafeInteger(archive["bytes"]) || Number(archive["bytes"]) < 1 || Number(archive["bytes"]) > 536870912 || typeof archive["sha256"] !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(archive["sha256"]) || !['["dev.sql"]', '["prod.sql"]', '["dev.sql","prod.sql"]', '["shared.sql"]'].includes(JSON.stringify(archive["sql_files"])) || !timestamp5(archive["captured_at"]) || !timestamp5(archive["expires_at"]))
45817
+ if (value["state"] !== "succeeded" || !record7(archive) || Object.keys(archive).sort().join(",") !== "bytes,captured_at,download_expires_at,download_url,expires_at,sha256,sql_files" || !Number.isSafeInteger(archive["bytes"]) || Number(archive["bytes"]) < 1 || Number(archive["bytes"]) > 536870912 || typeof archive["sha256"] !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(archive["sha256"]) || !['["dev.sql"]', '["prod.sql"]', '["dev.sql","prod.sql"]', '["shared.sql"]'].includes(JSON.stringify(archive["sql_files"])) || !timestamp5(archive["captured_at"]) || !timestamp5(archive["expires_at"]))
45407
45818
  invalid4();
45408
45819
  if (archive["download_url"] === null) {
45409
45820
  if (archive["download_expires_at"] !== null)
@@ -45422,7 +45833,7 @@ function assertProjectExport(value) {
45422
45833
  invalid4();
45423
45834
  }
45424
45835
  var id = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
45425
- function record6(value) {
45836
+ function record7(value) {
45426
45837
  return typeof value === "object" && value !== null && !Array.isArray(value);
45427
45838
  }
45428
45839
  function timestamp5(value) {
@@ -45662,7 +46073,11 @@ var HELP_USAGE = Object.freeze({
45662
46073
  "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
46074
  "database access list": "ohmyhost database access list --project ULID [--environment dev|prod] --json",
45664
46075
  "database access revoke": "ohmyhost database access revoke --project ULID --access ULID --yes --json",
45665
- "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; the headers file is a UTF-8 JSON object of at most 8 KiB)",
46076
+ "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>')",
46077
+ "database watch list": "ohmyhost database watch list --project ULID [--environment dev|prod] --json (dev when omitted; a sleeping database wakes)",
46078
+ "database watch delete": "ohmyhost database watch delete --project ULID --environment dev|prod --watch ID --idempotency-key KEY --yes --json",
46079
+ "database table create": "ohmyhost database table create --project ULID --environment dev|prod --table NAME --columns-file PATH [--primary-key COL[,COL]] --idempotency-key KEY --yes --json (the table is created in the public schema as the migration role, with the same access as migration tables, so a database watch can attach later; the columns file is a UTF-8 JSON array of 1 to 64 column objects, at most 64 KiB, each with name, type and optional nullable, identity and default; types: text, integer, bigint, boolean, numeric, double precision, timestamptz, date, uuid, jsonb; identity needs integer or bigint; default now needs timestamptz and default uuid needs uuid; identical repeats answer created false; at most 100 tables, 64 columns per table)",
46080
+ "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
46081
  "database webhook list": "ohmyhost database webhook list --project ULID [--environment dev|prod] --json (dev when omitted; the secret is never listed)",
45667
46082
  "database webhook get": "ohmyhost database webhook get --project ULID [--environment dev|prod] --webhook ID --json",
45668
46083
  "database webhook delete": "ohmyhost database webhook delete --project ULID --environment dev|prod --webhook ID --yes --json",
@@ -45787,6 +46202,7 @@ var LOGOUT_UNCONFIRMED_ACTION = "The saved login was removed from this computer,
45787
46202
  var HEADERS_FILE_INVALID_ACTION = "Put one JSON object of header names and values in the file named by --headers-file: UTF-8, at most 8 KiB, unique names, values of visible ASCII characters. Names such as content-type, host, content-length, connection, transfer-encoding, user-agent, te, upgrade and expect, and names starting with pgcf-, cf- or x-forwarded-, are refused. Nothing was sent; fix the file and run the command again.";
45788
46203
  var SCHEDULE_STATEMENTS_FILE_INVALID_ACTION = "Put one JSON array of 1 to 20 non-empty SQL strings in the file named by --statements-file: UTF-8, at most 64 KiB in total and 16 KiB per statement, without NUL characters. Nothing was saved; fix the file and run the command again.";
45789
46204
  var SCHEDULE_INVALID_ACTION = "Check the schedule request: --cron takes five fields in UTC (9 to 120 characters), or --every-seconds takes 60 to 2678400, never both; --timeout-ms takes 1000 to 30000; the statements file must hold 1 to 20 non-blank SQL strings of at most 16 KiB each and 64 KiB in total. Nothing was saved; fix it and run the command again.";
46205
+ var COLUMNS_FILE_INVALID_ACTION = "Put one JSON array of 1 to 64 column objects in the file named by --columns-file: UTF-8, at most 64 KiB, each with a lower-case name, a type (text, integer, bigint, boolean, numeric, double precision, timestamptz, date, uuid or jsonb) and optional nullable, identity (integer or bigint only) and default (now for timestamptz, uuid for uuid). Each --primary-key column must be one of them, at most 8 names. Nothing was created; fix the file or the flags and run the command again.";
45790
46206
  var STATEMENT_FILE_INVALID_ACTION = "Put one INSERT, UPDATE or DELETE statement that starts with that keyword (no leading comment or WITH), UTF-8, at most 64 KiB, in the file named by --statement-file, then rerun with the same --idempotency-key.";
45791
46207
  var signInCommand = (commandPrefix, profile) => profile === void 0 ? `${commandPrefix} login --json` : `${commandPrefix} login${profile.organizationId === null ? "" : ` --organization ${profile.organizationId}`} --user ${profile.userId} --profile-name ${profile.name} --json`;
45792
46208
  var endedLoginAction = (commandPrefix, profile) => `The sign-in of ${profile === void 0 ? "this computer's saved login" : `saved login ${profile.name} (user ${profile.userId}${profile.organizationId === null ? "" : `, organization ${profile.organizationId}`})`} ended and it was removed. Run '${signInCommand(commandPrefix, profile)}' to sign it in again.`;
@@ -46153,6 +46569,14 @@ var ProductCli = class {
46153
46569
  return await this.listDatabaseAccess(command, signal);
46154
46570
  if (command.kind === "database-access-revoke")
46155
46571
  return await this.revokeDatabaseAccess(command, signal);
46572
+ if (command.kind === "database-watch-create")
46573
+ return await this.createDatabaseWatch(command, signal);
46574
+ if (command.kind === "database-watch-list")
46575
+ return await this.listDatabaseWatches(command, signal);
46576
+ if (command.kind === "database-watch-delete")
46577
+ return await this.deleteDatabaseWatch(command, signal);
46578
+ if (command.kind === "database-table-create")
46579
+ return await this.createDatabaseTable(command, signal);
46156
46580
  if (command.kind === "database-webhook-create")
46157
46581
  return await this.createDatabaseWebhook(command, signal);
46158
46582
  if (command.kind === "database-webhook-list")
@@ -46684,6 +47108,51 @@ var ProductCli = class {
46684
47108
  assertProjectDatabaseAccess(access);
46685
47109
  return success("database access revoke", { status: "succeeded", access });
46686
47110
  }
47111
+ /** The answer names the event type to subscribe to; the watch already carries the columns and the topic. */
47112
+ async createDatabaseWatch(command, signal) {
47113
+ const api = await this.authenticatedApi(command.credentialStore, signal);
47114
+ const watch = await api.createProjectDatabaseWatch({
47115
+ projectId: command.projectId,
47116
+ environment: command.environment,
47117
+ idempotencyKey: command.idempotencyKey,
47118
+ request: command.request
47119
+ });
47120
+ return success("database watch create", {
47121
+ status: "succeeded",
47122
+ watch,
47123
+ 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.`
47124
+ });
47125
+ }
47126
+ async listDatabaseWatches(command, signal) {
47127
+ const api = await this.authenticatedApi(command.credentialStore, signal);
47128
+ const page = await api.listProjectDatabaseWatches({
47129
+ projectId: command.projectId,
47130
+ environment: command.environment
47131
+ });
47132
+ return success("database watch list", { status: "succeeded", watch: page.items });
47133
+ }
47134
+ async deleteDatabaseWatch(command, signal) {
47135
+ const api = await this.authenticatedApi(command.credentialStore, signal);
47136
+ const deleted = await api.deleteProjectDatabaseWatch({
47137
+ projectId: command.projectId,
47138
+ environment: command.environment,
47139
+ idempotencyKey: command.idempotencyKey,
47140
+ watchId: command.watchId
47141
+ });
47142
+ return success("database watch delete", { status: "succeeded", deleted });
47143
+ }
47144
+ /** The columns file and the key are checked before any request; a repeat answers the same table with created false. */
47145
+ async createDatabaseTable(command, signal) {
47146
+ const request = await tableCreateRequest(command);
47147
+ const api = await this.authenticatedApi(command.credentialStore, signal);
47148
+ const answer = await api.createProjectDatabaseTable({
47149
+ projectId: command.projectId,
47150
+ environment: command.environment,
47151
+ idempotencyKey: command.idempotencyKey,
47152
+ request
47153
+ });
47154
+ return success("database table create", { status: "succeeded", table: answer.table });
47155
+ }
46687
47156
  /** The signing secret is returned by create only; every other answer carries no secret. */
46688
47157
  async createDatabaseWebhook(command, signal) {
46689
47158
  const request = await webhookCreateRequest(command);
@@ -47575,6 +48044,37 @@ var scheduleSetRequest = async (command) => {
47575
48044
  throw new ProductCliFailure(2, "schedule_invalid", false, SCHEDULE_INVALID_ACTION);
47576
48045
  }
47577
48046
  };
48047
+ var tableCreateRequest = async (command) => {
48048
+ const columnsFailure = () => new ProductCliFailure(2, "columns_file_invalid", false, COLUMNS_FILE_INVALID_ACTION);
48049
+ const columns = await readBoundedUtf8File(command.columnsFile, 65536, columnsFailure).then((text) => {
48050
+ try {
48051
+ return JSON.parse(text);
48052
+ } catch {
48053
+ throw columnsFailure();
48054
+ }
48055
+ });
48056
+ try {
48057
+ const parsed = parseDatabaseTableCreateRequest({
48058
+ table: command.table,
48059
+ columns,
48060
+ ...command.primaryKey.length === 0 ? {} : { primaryKey: command.primaryKey }
48061
+ });
48062
+ return {
48063
+ table: parsed.table,
48064
+ columns: parsed.columns.map(tableColumnInput),
48065
+ primaryKey: parsed.primaryKey
48066
+ };
48067
+ } catch {
48068
+ throw columnsFailure();
48069
+ }
48070
+ };
48071
+ var tableColumnInput = (column) => ({
48072
+ name: column.name,
48073
+ type: column.type,
48074
+ nullable: column.nullable,
48075
+ identity: column.identity,
48076
+ ...column.default === null ? {} : { default: column.default }
48077
+ });
47578
48078
  var psqlUnavailable = () => failure("database psql", 9, "psql_unavailable", false, "Install the PostgreSQL client (psql) and make it available on PATH, or run 'ohmyhost database access create' and connect with your own SQL client.");
47579
48079
  var success = (command, body) => ({
47580
48080
  exitCode: 0,
@@ -47714,6 +48214,14 @@ var parsedCommandName = (command) => {
47714
48214
  return "database access revoke";
47715
48215
  if (command.kind === "database-psql")
47716
48216
  return "database psql";
48217
+ if (command.kind === "database-watch-create")
48218
+ return "database watch create";
48219
+ if (command.kind === "database-watch-list")
48220
+ return "database watch list";
48221
+ if (command.kind === "database-watch-delete")
48222
+ return "database watch delete";
48223
+ if (command.kind === "database-table-create")
48224
+ return "database table create";
47717
48225
  if (command.kind === "database-webhook-create")
47718
48226
  return "database webhook create";
47719
48227
  if (command.kind === "database-webhook-list")
@@ -47936,12 +48444,12 @@ function buildClientParams(args, fields) {
47936
48444
  };
47937
48445
  const map = buildKeyMap(fields);
47938
48446
  function writeSlot(slot, key, value) {
47939
- let record7 = params[slot];
47940
- if (record7 === void 0) {
47941
- record7 = /* @__PURE__ */ Object.create(null);
47942
- params[slot] = record7;
48447
+ let record8 = params[slot];
48448
+ if (record8 === void 0) {
48449
+ record8 = /* @__PURE__ */ Object.create(null);
48450
+ params[slot] = record8;
47943
48451
  }
47944
- record7[key] = value;
48452
+ record8[key] = value;
47945
48453
  }
47946
48454
  let config;
47947
48455
  for (const [index, arg] of args.entries()) {
@@ -50586,6 +51094,92 @@ var revokeProjectDatabaseAccess = (parameters, options) => {
50586
51094
  ...params
50587
51095
  });
50588
51096
  };
51097
+ var createProjectDatabaseTable = (parameters, options) => {
51098
+ const params = buildClientParams([parameters], [
51099
+ {
51100
+ args: [
51101
+ { in: "headers", key: "X-Request-Id" },
51102
+ { in: "headers", key: "Idempotency-Key" },
51103
+ { in: "path", key: "project_id" },
51104
+ { key: "databaseTableCreateRequest", map: "body" }
51105
+ ]
51106
+ }
51107
+ ]);
51108
+ return (options?.client ?? client).post({
51109
+ responseStyle: "data",
51110
+ security: [{ scheme: "bearer", type: "http" }],
51111
+ url: "/v1/projects/{project_id}/database/tables",
51112
+ ...options,
51113
+ ...params,
51114
+ headers: {
51115
+ "Content-Type": "application/json",
51116
+ ...options?.headers,
51117
+ ...params.headers
51118
+ }
51119
+ });
51120
+ };
51121
+ var listProjectDatabaseWatches = (parameters, options) => {
51122
+ const params = buildClientParams([parameters], [
51123
+ {
51124
+ args: [
51125
+ { in: "headers", key: "X-Request-Id" },
51126
+ { in: "path", key: "project_id" },
51127
+ { in: "query", key: "environment" }
51128
+ ]
51129
+ }
51130
+ ]);
51131
+ return (options?.client ?? client).get({
51132
+ responseStyle: "data",
51133
+ security: [{ scheme: "bearer", type: "http" }],
51134
+ url: "/v1/projects/{project_id}/database/watches",
51135
+ ...options,
51136
+ ...params
51137
+ });
51138
+ };
51139
+ var createProjectDatabaseWatch = (parameters, options) => {
51140
+ const params = buildClientParams([parameters], [
51141
+ {
51142
+ args: [
51143
+ { in: "headers", key: "X-Request-Id" },
51144
+ { in: "headers", key: "Idempotency-Key" },
51145
+ { in: "path", key: "project_id" },
51146
+ { key: "databaseWatchCreateRequest", map: "body" }
51147
+ ]
51148
+ }
51149
+ ]);
51150
+ return (options?.client ?? client).post({
51151
+ responseStyle: "data",
51152
+ security: [{ scheme: "bearer", type: "http" }],
51153
+ url: "/v1/projects/{project_id}/database/watches",
51154
+ ...options,
51155
+ ...params,
51156
+ headers: {
51157
+ "Content-Type": "application/json",
51158
+ ...options?.headers,
51159
+ ...params.headers
51160
+ }
51161
+ });
51162
+ };
51163
+ var deleteProjectDatabaseWatch = (parameters, options) => {
51164
+ const params = buildClientParams([parameters], [
51165
+ {
51166
+ args: [
51167
+ { in: "headers", key: "X-Request-Id" },
51168
+ { in: "headers", key: "Idempotency-Key" },
51169
+ { in: "path", key: "project_id" },
51170
+ { in: "path", key: "watch_id" },
51171
+ { in: "query", key: "environment" }
51172
+ ]
51173
+ }
51174
+ ]);
51175
+ return (options?.client ?? client).delete({
51176
+ responseStyle: "data",
51177
+ security: [{ scheme: "bearer", type: "http" }],
51178
+ url: "/v1/projects/{project_id}/database/watches/{watch_id}",
51179
+ ...options,
51180
+ ...params
51181
+ });
51182
+ };
50589
51183
  var listProjectDatabaseWebhooks = (parameters, options) => {
50590
51184
  const params = buildClientParams([parameters], [
50591
51185
  {
@@ -51959,7 +52553,25 @@ function checkedResponse(kind, value) {
51959
52553
 
51960
52554
  // apps/product-cli/dist/product-api.js
51961
52555
  var WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
52556
+ var WATCH_SCOPE_MISMATCH = "Watch response scope mismatch";
52557
+ var TABLE_SCOPE_MISMATCH = "Table response scope mismatch";
51962
52558
  var SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
52559
+ function assertDatabaseTableAnswer(value) {
52560
+ const table = isRecordValue(value) ? value["table"] : void 0;
52561
+ const columns = isRecordValue(table) ? table["columns"] : void 0;
52562
+ const primaryKey = isRecordValue(table) ? table["primary_key"] : void 0;
52563
+ if (!isRecordValue(table) || !hasExactKeys(table, ["environment", "schema", "table", "columns", "primary_key", "created"]) || table["environment"] !== "dev" && table["environment"] !== "prod" || table["schema"] !== "public" || !isDatabaseTableIdentifier(table["table"]) || typeof table["created"] !== "boolean" || !Array.isArray(columns) || columns.length < 1 || !columns.every(isDatabaseTableAnswerColumn) || !Array.isArray(primaryKey) || !primaryKey.every((name) => columns.some((column) => column["name"] === name)))
52564
+ throw new TypeError("Invalid database table answer");
52565
+ }
52566
+ function isDatabaseTableAnswerColumn(value) {
52567
+ return isRecordValue(value) && hasExactKeys(value, ["name", "type", "nullable", "identity", "default"]) && isDatabaseTableIdentifier(value["name"]) && DATABASE_TABLE_COLUMN_TYPES.includes(value["type"]) && typeof value["nullable"] === "boolean" && typeof value["identity"] === "boolean" && (value["default"] === null || DATABASE_TABLE_DEFAULTS.includes(value["default"]));
52568
+ }
52569
+ function isRecordValue(value) {
52570
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52571
+ }
52572
+ function hasExactKeys(value, keys) {
52573
+ return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
52574
+ }
51963
52575
  var GeneratedSdkProductApi = class {
51964
52576
  #client;
51965
52577
  managedSource;
@@ -52156,6 +52768,58 @@ var GeneratedSdkProductApi = class {
52156
52768
  assertProjectDatabaseAccess(value);
52157
52769
  return value;
52158
52770
  }
52771
+ async createProjectDatabaseWatch(input) {
52772
+ const value = await createProjectDatabaseWatch({
52773
+ project_id: input.projectId,
52774
+ "Idempotency-Key": input.idempotencyKey,
52775
+ databaseWatchCreateRequest: {
52776
+ environment: input.environment,
52777
+ schema: input.request.schema,
52778
+ table: input.request.table,
52779
+ columns: [...input.request.columns],
52780
+ operations: [...input.request.operations]
52781
+ }
52782
+ }, { client: this.#client });
52783
+ assertDatabaseWatch(value);
52784
+ if (value.environment !== input.environment || value.schema !== input.request.schema || value.table !== input.request.table)
52785
+ throw new TypeError(WATCH_SCOPE_MISMATCH);
52786
+ return value;
52787
+ }
52788
+ async listProjectDatabaseWatches(input) {
52789
+ const value = await listProjectDatabaseWatches({ project_id: input.projectId, environment: input.environment }, { client: this.#client });
52790
+ assertDatabaseWatchPage(value);
52791
+ if (value.items.some((item) => item.environment !== input.environment))
52792
+ throw new TypeError(WATCH_SCOPE_MISMATCH);
52793
+ return value;
52794
+ }
52795
+ async deleteProjectDatabaseWatch(input) {
52796
+ const value = await deleteProjectDatabaseWatch({
52797
+ project_id: input.projectId,
52798
+ environment: input.environment,
52799
+ "Idempotency-Key": input.idempotencyKey,
52800
+ watch_id: input.watchId
52801
+ }, { client: this.#client });
52802
+ assertDatabaseWatchDeleted(value);
52803
+ if (value.id !== input.watchId)
52804
+ throw new TypeError(WATCH_SCOPE_MISMATCH);
52805
+ return value;
52806
+ }
52807
+ async createProjectDatabaseTable(input) {
52808
+ const value = await createProjectDatabaseTable({
52809
+ project_id: input.projectId,
52810
+ "Idempotency-Key": input.idempotencyKey,
52811
+ databaseTableCreateRequest: {
52812
+ environment: input.environment,
52813
+ table: input.request.table,
52814
+ columns: [...input.request.columns],
52815
+ ...input.request.primaryKey.length === 0 ? {} : { primary_key: [...input.request.primaryKey] }
52816
+ }
52817
+ }, { client: this.#client });
52818
+ assertDatabaseTableAnswer(value);
52819
+ if (value.table.environment !== input.environment || value.table.table !== input.request.table)
52820
+ throw new TypeError(TABLE_SCOPE_MISMATCH);
52821
+ return value;
52822
+ }
52159
52823
  /** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
52160
52824
  async createProjectDatabaseWebhook(input) {
52161
52825
  const value = await createProjectDatabaseWebhook({
@@ -53762,8 +54426,8 @@ function pnpmLockedVersion(document2, packageName) {
53762
54426
  }
53763
54427
  return null;
53764
54428
  }
53765
- function ownValue(record7, key) {
53766
- return isRecord4(record7) && Object.hasOwn(record7, key) ? record7[key] : void 0;
54429
+ function ownValue(record8, key) {
54430
+ return isRecord4(record8) && Object.hasOwn(record8, key) ? record8[key] : void 0;
53767
54431
  }
53768
54432
  function isRecord4(value) {
53769
54433
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -54123,6 +54787,8 @@ function rejectMigrationSqlReferences(tokens, allowIdentityHelpers = false) {
54123
54787
  if (token?.kind !== "word" && token?.kind !== "identifier")
54124
54788
  continue;
54125
54789
  const value = token.value.toLowerCase();
54790
+ if (value === "pgcf")
54791
+ throw new DatabaseMigrationAdmissionError("unsupported");
54126
54792
  if (value === "ohmyhost") {
54127
54793
  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
54794
  continue;
@@ -54134,6 +54800,139 @@ function rejectMigrationSqlReferences(tokens, allowIdentityHelpers = false) {
54134
54800
  }
54135
54801
  }
54136
54802
 
54803
+ // packages/contracts/dist/database-migration-capture.js
54804
+ var MAX_NAME_BYTES = 63;
54805
+ function readMigrationCaptureTrigger(tokens) {
54806
+ if (!isSqlKeyword(tokens[0], "create") || !isSqlKeyword(tokens[1], "trigger"))
54807
+ return void 0;
54808
+ if (!tokens.some((token) => isNamedPgcf(token, true)))
54809
+ return void 0;
54810
+ return new CaptureReader(tokens).read();
54811
+ }
54812
+ var CaptureReader = class {
54813
+ tokens;
54814
+ position = 2;
54815
+ constructor(tokens) {
54816
+ this.tokens = tokens;
54817
+ }
54818
+ read() {
54819
+ const trigger = this.triggerName();
54820
+ this.expectKeyword("after");
54821
+ const operations = this.operations();
54822
+ this.expectKeyword("on");
54823
+ const schema = this.name();
54824
+ this.expectSymbol(".");
54825
+ const table = this.name();
54826
+ if (!isDatabaseWatchSchema(schema))
54827
+ throw unsupported();
54828
+ for (const keyword of ["for", "each", "row", "execute", "function"]) {
54829
+ this.expectKeyword(keyword);
54830
+ }
54831
+ const header = this.tokens.slice(0, this.position);
54832
+ if (!isNamedPgcf(this.token(), false))
54833
+ throw unsupported();
54834
+ this.position += 1;
54835
+ this.expectSymbol(".");
54836
+ this.expectCapture();
54837
+ this.expectSymbol("(");
54838
+ const columns = this.columns();
54839
+ this.expectSymbol(")");
54840
+ if (this.position !== this.tokens.length)
54841
+ throw unsupported();
54842
+ return Object.freeze({
54843
+ target: Object.freeze({ trigger, schema, table, operations, columns }),
54844
+ header
54845
+ });
54846
+ }
54847
+ triggerName() {
54848
+ const token = this.token();
54849
+ if (token.kind !== "word" && token.kind !== "identifier" || !isDatabaseWatchIdentifier(token.value)) {
54850
+ throw unsupported();
54851
+ }
54852
+ this.position += 1;
54853
+ return token.value;
54854
+ }
54855
+ /** Table and schema names may be quoted identifiers; the executor checks the catalog for the exact name. */
54856
+ name() {
54857
+ const token = this.token();
54858
+ const valid = token.kind === "word" ? isDatabaseWatchIdentifier(token.value) : token.kind === "identifier" && isQuotedName(token.value);
54859
+ if (!valid)
54860
+ throw unsupported();
54861
+ this.position += 1;
54862
+ return token.value;
54863
+ }
54864
+ operations() {
54865
+ const events = [];
54866
+ do {
54867
+ const token = this.token();
54868
+ const operation = DATABASE_WATCH_OPERATIONS.find((name) => isSqlKeyword(token, name));
54869
+ if (operation === void 0 || events.includes(operation))
54870
+ throw unsupported();
54871
+ this.position += 1;
54872
+ events.push(operation);
54873
+ } while (this.takeKeyword("or"));
54874
+ return DATABASE_WATCH_OPERATIONS.filter((operation) => events.includes(operation));
54875
+ }
54876
+ expectCapture() {
54877
+ const token = this.token();
54878
+ if (token.kind !== "word" && token.kind !== "identifier" || token.value !== "capture") {
54879
+ throw unsupported();
54880
+ }
54881
+ this.position += 1;
54882
+ }
54883
+ columns() {
54884
+ const columns = [];
54885
+ do {
54886
+ const token = this.token();
54887
+ if (columns.length >= DATABASE_WATCH_MAX_COLUMNS || token.kind !== "string" || token.text !== `'${token.value}'` || !isDatabaseWatchIdentifier(token.value) || columns.includes(token.value)) {
54888
+ throw unsupported();
54889
+ }
54890
+ this.position += 1;
54891
+ columns.push(token.value);
54892
+ } while (this.takeSymbol(","));
54893
+ return columns;
54894
+ }
54895
+ token() {
54896
+ const token = this.tokens[this.position];
54897
+ if (token === void 0)
54898
+ throw unsupported();
54899
+ return token;
54900
+ }
54901
+ takeKeyword(keyword) {
54902
+ if (!isSqlKeyword(this.tokens[this.position], keyword))
54903
+ return false;
54904
+ this.position += 1;
54905
+ return true;
54906
+ }
54907
+ expectKeyword(keyword) {
54908
+ if (!this.takeKeyword(keyword))
54909
+ throw unsupported();
54910
+ }
54911
+ takeSymbol(symbol) {
54912
+ const token = this.tokens[this.position];
54913
+ if (token?.kind !== "symbol" || token.value !== symbol)
54914
+ return false;
54915
+ this.position += 1;
54916
+ return true;
54917
+ }
54918
+ expectSymbol(symbol) {
54919
+ if (!this.takeSymbol(symbol))
54920
+ throw unsupported();
54921
+ }
54922
+ };
54923
+ function isNamedPgcf(token, ignoreCase) {
54924
+ if (token.kind !== "word" && token.kind !== "identifier")
54925
+ return false;
54926
+ return ignoreCase ? token.value.toLowerCase() === "pgcf" : token.value === "pgcf";
54927
+ }
54928
+ function isQuotedName(value) {
54929
+ const bytes = new TextEncoder().encode(value).byteLength;
54930
+ return bytes >= 1 && bytes <= MAX_NAME_BYTES;
54931
+ }
54932
+ function unsupported() {
54933
+ return new DatabaseMigrationAdmissionError("unsupported");
54934
+ }
54935
+
54137
54936
  // packages/contracts/dist/database-migration-policies.js
54138
54937
  var PolicyReader = class {
54139
54938
  tokens;
@@ -54287,6 +55086,9 @@ function admitExpandOnlyMigrations(migrations) {
54287
55086
  throw new DatabaseMigrationAdmissionError("invalid");
54288
55087
  }
54289
55088
  const statements = parseDatabaseMigrationStatements(sql);
55089
+ if (statements.some((statement) => statement.captureTrigger !== void 0 && isDatabaseWatchTriggerName(statement.captureTrigger.trigger))) {
55090
+ throw new DatabaseMigrationAdmissionError("unsupported");
55091
+ }
54290
55092
  statementCount += statements.length;
54291
55093
  return Object.freeze({ path: migration.path, statementCount: statements.length });
54292
55094
  });
@@ -54300,6 +55102,11 @@ function parseDatabaseMigrationStatements(sql) {
54300
55102
  if (statements.length === 0)
54301
55103
  throw new DatabaseMigrationAdmissionError("invalid");
54302
55104
  return Object.freeze(statements.map((statement) => {
55105
+ const capture = readMigrationCaptureTrigger(statement.tokens);
55106
+ if (capture !== void 0) {
55107
+ rejectMigrationSqlReferences(capture.header);
55108
+ return Object.freeze({ sql: statement.sql, captureTrigger: capture.target });
55109
+ }
54303
55110
  rejectMigrationSqlReferences(statement.tokens, true);
54304
55111
  const rlsTarget = readMigrationRlsTarget(statement.tokens);
54305
55112
  if (rlsTarget !== void 0)
@@ -56956,6 +57763,7 @@ export {
56956
57763
  ACCOUNT_SENSITIVE_PROBLEMS,
56957
57764
  classifyCliFailure,
56958
57765
  GeneratedSdkManagedSourceApi,
57766
+ assertDatabaseTableAnswer,
56959
57767
  GeneratedSdkProductApi,
56960
57768
  RepositoryInitError,
56961
57769
  RepositoryInitBlockedError,