@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.
@@ -0,0 +1,177 @@
1
+ import {
2
+ DATABASE_WATCH_MAX_COLUMNS,
3
+ DATABASE_WATCH_OPERATIONS,
4
+ isDatabaseWatchIdentifier,
5
+ isDatabaseWatchSchema,
6
+ type DatabaseWatchOperation,
7
+ } from "./database-events.js";
8
+ import { DatabaseMigrationAdmissionError } from "./database-migration-errors.js";
9
+ import { isSqlKeyword, type MigrationSqlToken } from "./database-migration-lexical.js";
10
+
11
+ export interface DatabaseMigrationCaptureTrigger {
12
+ readonly trigger: string;
13
+ readonly schema: string;
14
+ readonly table: string;
15
+ readonly operations: readonly DatabaseWatchOperation[];
16
+ readonly columns: readonly string[];
17
+ }
18
+
19
+ export interface MigrationCaptureStatement {
20
+ readonly target: DatabaseMigrationCaptureTrigger;
21
+ /** The tokens before the pgcf.capture function name, which still pass the ordinary references check. */
22
+ readonly header: readonly MigrationSqlToken[];
23
+ }
24
+
25
+ const MAX_NAME_BYTES = 63;
26
+
27
+ /**
28
+ * Reads the one trigger form PGCF admits on an existing table:
29
+ * CREATE TRIGGER name AFTER INSERT [OR UPDATE] [OR DELETE] ON schema.table FOR EACH ROW
30
+ * EXECUTE FUNCTION pgcf.capture('column', ...). Returns undefined for every statement that does not
31
+ * name pgcf, so ordinary triggers keep their existing admission. Anything else that names pgcf throws.
32
+ */
33
+ export function readMigrationCaptureTrigger(
34
+ tokens: readonly MigrationSqlToken[],
35
+ ): MigrationCaptureStatement | undefined {
36
+ if (!isSqlKeyword(tokens[0], "create") || !isSqlKeyword(tokens[1], "trigger")) return undefined;
37
+ if (!tokens.some((token) => isNamedPgcf(token, true))) return undefined;
38
+ return new CaptureReader(tokens).read();
39
+ }
40
+
41
+ class CaptureReader {
42
+ private position = 2;
43
+
44
+ public constructor(private readonly tokens: readonly MigrationSqlToken[]) {}
45
+
46
+ public read(): MigrationCaptureStatement {
47
+ const trigger = this.triggerName();
48
+ this.expectKeyword("after");
49
+ const operations = this.operations();
50
+ this.expectKeyword("on");
51
+ const schema = this.name();
52
+ this.expectSymbol(".");
53
+ const table = this.name();
54
+ if (!isDatabaseWatchSchema(schema)) throw unsupported();
55
+ for (const keyword of ["for", "each", "row", "execute", "function"]) {
56
+ this.expectKeyword(keyword);
57
+ }
58
+ const header = this.tokens.slice(0, this.position);
59
+ if (!isNamedPgcf(this.token(), false)) throw unsupported();
60
+ this.position += 1;
61
+ this.expectSymbol(".");
62
+ this.expectCapture();
63
+ this.expectSymbol("(");
64
+ const columns = this.columns();
65
+ this.expectSymbol(")");
66
+ if (this.position !== this.tokens.length) throw unsupported();
67
+ return Object.freeze({
68
+ target: Object.freeze({ trigger, schema, table, operations, columns }),
69
+ header,
70
+ });
71
+ }
72
+
73
+ private triggerName(): string {
74
+ const token = this.token();
75
+ if (
76
+ (token.kind !== "word" && token.kind !== "identifier") ||
77
+ !isDatabaseWatchIdentifier(token.value)
78
+ ) {
79
+ throw unsupported();
80
+ }
81
+ this.position += 1;
82
+ return token.value;
83
+ }
84
+
85
+ /** Table and schema names may be quoted identifiers; the executor checks the catalog for the exact name. */
86
+ private name(): string {
87
+ const token = this.token();
88
+ const valid =
89
+ token.kind === "word"
90
+ ? isDatabaseWatchIdentifier(token.value)
91
+ : token.kind === "identifier" && isQuotedName(token.value);
92
+ if (!valid) throw unsupported();
93
+ this.position += 1;
94
+ return token.value;
95
+ }
96
+
97
+ private operations(): readonly DatabaseWatchOperation[] {
98
+ const events: DatabaseWatchOperation[] = [];
99
+ do {
100
+ const token = this.token();
101
+ const operation = DATABASE_WATCH_OPERATIONS.find((name) => isSqlKeyword(token, name));
102
+ if (operation === undefined || events.includes(operation)) throw unsupported();
103
+ this.position += 1;
104
+ events.push(operation);
105
+ } while (this.takeKeyword("or"));
106
+ return DATABASE_WATCH_OPERATIONS.filter((operation) => events.includes(operation));
107
+ }
108
+
109
+ private expectCapture(): void {
110
+ const token = this.token();
111
+ if ((token.kind !== "word" && token.kind !== "identifier") || token.value !== "capture") {
112
+ throw unsupported();
113
+ }
114
+ this.position += 1;
115
+ }
116
+
117
+ private columns(): readonly string[] {
118
+ const columns: string[] = [];
119
+ do {
120
+ const token = this.token();
121
+ if (
122
+ columns.length >= DATABASE_WATCH_MAX_COLUMNS ||
123
+ token.kind !== "string" ||
124
+ token.text !== `'${token.value}'` ||
125
+ !isDatabaseWatchIdentifier(token.value) ||
126
+ columns.includes(token.value)
127
+ ) {
128
+ throw unsupported();
129
+ }
130
+ this.position += 1;
131
+ columns.push(token.value);
132
+ } while (this.takeSymbol(","));
133
+ return columns;
134
+ }
135
+
136
+ private token(): MigrationSqlToken {
137
+ const token = this.tokens[this.position];
138
+ if (token === undefined) throw unsupported();
139
+ return token;
140
+ }
141
+
142
+ private takeKeyword(keyword: string): boolean {
143
+ if (!isSqlKeyword(this.tokens[this.position], keyword)) return false;
144
+ this.position += 1;
145
+ return true;
146
+ }
147
+
148
+ private expectKeyword(keyword: string): void {
149
+ if (!this.takeKeyword(keyword)) throw unsupported();
150
+ }
151
+
152
+ private takeSymbol(symbol: string): boolean {
153
+ const token = this.tokens[this.position];
154
+ if (token?.kind !== "symbol" || token.value !== symbol) return false;
155
+ this.position += 1;
156
+ return true;
157
+ }
158
+
159
+ private expectSymbol(symbol: string): void {
160
+ if (!this.takeSymbol(symbol)) throw unsupported();
161
+ }
162
+ }
163
+
164
+ /** The gate ignores case; the function name itself must be exactly pgcf, so "PGCF" is refused later. */
165
+ function isNamedPgcf(token: MigrationSqlToken, ignoreCase: boolean): boolean {
166
+ if (token.kind !== "word" && token.kind !== "identifier") return false;
167
+ return ignoreCase ? token.value.toLowerCase() === "pgcf" : token.value === "pgcf";
168
+ }
169
+
170
+ function isQuotedName(value: string): boolean {
171
+ const bytes = new TextEncoder().encode(value).byteLength;
172
+ return bytes >= 1 && bytes <= MAX_NAME_BYTES;
173
+ }
174
+
175
+ function unsupported(): DatabaseMigrationAdmissionError {
176
+ return new DatabaseMigrationAdmissionError("unsupported");
177
+ }
@@ -144,6 +144,7 @@ export function rejectMigrationSqlReferences(
144
144
  const token = tokens[index];
145
145
  if (token?.kind !== "word" && token?.kind !== "identifier") continue;
146
146
  const value = token.value.toLowerCase();
147
+ if (value === "pgcf") throw new DatabaseMigrationAdmissionError("unsupported");
147
148
  if (value === "ohmyhost") {
148
149
  if (
149
150
  allowIdentityHelpers &&
@@ -0,0 +1,317 @@
1
+ /** Column types a created table may declare; the closed list keeps type text out of the DDL. */
2
+ export const DATABASE_TABLE_COLUMN_TYPES = [
3
+ "text",
4
+ "integer",
5
+ "bigint",
6
+ "boolean",
7
+ "numeric",
8
+ "double precision",
9
+ "timestamptz",
10
+ "date",
11
+ "uuid",
12
+ "jsonb",
13
+ ] as const;
14
+ export type DatabaseTableColumnType = (typeof DATABASE_TABLE_COLUMN_TYPES)[number];
15
+ /** `now` is DEFAULT now() on timestamptz; `uuid` is DEFAULT gen_random_uuid() on uuid. */
16
+ export const DATABASE_TABLE_DEFAULTS = ["now", "uuid"] as const;
17
+ export type DatabaseTableDefault = (typeof DATABASE_TABLE_DEFAULTS)[number];
18
+ export const DATABASE_TABLE_MAX_COLUMNS = 64;
19
+ export const DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS = 8;
20
+ /** Ordinary and partitioned tables that a database may hold in its public schema. */
21
+ export const DATABASE_TABLE_LIMIT = 100;
22
+ export const DATABASE_TABLE_SCHEMA = "public";
23
+
24
+ const DATABASE_TABLE_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_]{0,62}$/u;
25
+ /** PostgreSQL reserved words: a table or column of such a name would need quoting everywhere it is used. */
26
+ const RESERVED_WORDS: ReadonlySet<string> = new Set([
27
+ "all",
28
+ "analyse",
29
+ "analyze",
30
+ "and",
31
+ "any",
32
+ "array",
33
+ "as",
34
+ "asc",
35
+ "asymmetric",
36
+ "both",
37
+ "case",
38
+ "cast",
39
+ "check",
40
+ "collate",
41
+ "column",
42
+ "constraint",
43
+ "create",
44
+ "current_catalog",
45
+ "current_date",
46
+ "current_role",
47
+ "current_time",
48
+ "current_timestamp",
49
+ "current_user",
50
+ "default",
51
+ "deferrable",
52
+ "desc",
53
+ "distinct",
54
+ "do",
55
+ "else",
56
+ "end",
57
+ "except",
58
+ "false",
59
+ "fetch",
60
+ "for",
61
+ "foreign",
62
+ "from",
63
+ "grant",
64
+ "group",
65
+ "having",
66
+ "in",
67
+ "initially",
68
+ "intersect",
69
+ "into",
70
+ "lateral",
71
+ "leading",
72
+ "limit",
73
+ "localtime",
74
+ "localtimestamp",
75
+ "not",
76
+ "null",
77
+ "offset",
78
+ "on",
79
+ "only",
80
+ "or",
81
+ "order",
82
+ "placing",
83
+ "primary",
84
+ "references",
85
+ "returning",
86
+ "select",
87
+ "session_user",
88
+ "some",
89
+ "symmetric",
90
+ "table",
91
+ "then",
92
+ "to",
93
+ "trailing",
94
+ "true",
95
+ "union",
96
+ "unique",
97
+ "user",
98
+ "using",
99
+ "variadic",
100
+ "when",
101
+ "where",
102
+ "window",
103
+ "with",
104
+ ]);
105
+
106
+ export interface DatabaseTableColumn {
107
+ readonly name: string;
108
+ readonly type: DatabaseTableColumnType;
109
+ readonly nullable: boolean;
110
+ readonly identity: boolean;
111
+ readonly default: DatabaseTableDefault | null;
112
+ }
113
+
114
+ /** A create request after parsing: nullable is concrete and primaryKey is empty when the request names none. */
115
+ export interface DatabaseTableRequest {
116
+ readonly table: string;
117
+ readonly columns: readonly DatabaseTableColumn[];
118
+ readonly primaryKey: readonly string[];
119
+ }
120
+
121
+ export interface DatabaseTable {
122
+ readonly schema: typeof DATABASE_TABLE_SCHEMA;
123
+ readonly table: string;
124
+ readonly columns: readonly DatabaseTableColumn[];
125
+ readonly primaryKey: readonly string[];
126
+ }
127
+
128
+ export interface DatabaseTableCreated {
129
+ readonly table: DatabaseTable;
130
+ readonly created: boolean;
131
+ }
132
+
133
+ interface ColumnInput {
134
+ readonly name: string;
135
+ readonly type: DatabaseTableColumnType;
136
+ readonly nullable: boolean | undefined;
137
+ readonly identity: boolean;
138
+ readonly default: DatabaseTableDefault | null;
139
+ }
140
+
141
+ /** Identifiers are plain lower-case SQL names; the renderer quotes them and no other caller text reaches the DDL. */
142
+ export function isDatabaseTableIdentifier(value: unknown): value is string {
143
+ return (
144
+ typeof value === "string" &&
145
+ DATABASE_TABLE_IDENTIFIER_PATTERN.test(value) &&
146
+ !value.startsWith("pg_") &&
147
+ !RESERVED_WORDS.has(value)
148
+ );
149
+ }
150
+
151
+ export function parseDatabaseTableCreateRequest(value: unknown): DatabaseTableRequest {
152
+ if (
153
+ !record(value) ||
154
+ !onlyKeys(value, ["table", "columns", "primaryKey"]) ||
155
+ !isDatabaseTableIdentifier(value["table"]) ||
156
+ !Array.isArray(value["columns"]) ||
157
+ value["columns"].length < 1 ||
158
+ value["columns"].length > DATABASE_TABLE_MAX_COLUMNS
159
+ )
160
+ throw new TypeError("Invalid database table request");
161
+ const inputs = value["columns"].map(parseColumn);
162
+ const names = inputs.map((column) => column.name);
163
+ if (new Set(names).size !== names.length || inputs.filter((column) => column.identity).length > 1)
164
+ throw new TypeError("Invalid database table request");
165
+ const primaryKey = parsePrimaryKey(value["primaryKey"], names);
166
+ return {
167
+ table: value["table"],
168
+ columns: inputs.map((column) => completeColumn(column, primaryKey.includes(column.name))),
169
+ primaryKey,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * The one CREATE TABLE statement of a request, in the public schema. Identifiers are validated again here, so the
175
+ * renderer is safe for any caller; the statement has no trailing semicolon.
176
+ */
177
+ export function renderCreateTableStatement(request: DatabaseTableRequest): string {
178
+ const definitions = request.columns.map(renderColumn);
179
+ if (request.primaryKey.length > 0)
180
+ definitions.push(`PRIMARY KEY (${request.primaryKey.map(quoted).join(",")})`);
181
+ return `CREATE TABLE ${databaseTableReference(request.table)} (${definitions.join(", ")})`;
182
+ }
183
+
184
+ /** The quoted, schema-qualified name of a table of the public schema, as SQL statements use it. */
185
+ export function databaseTableReference(table: string): string {
186
+ return `${quoted(DATABASE_TABLE_SCHEMA)}.${quoted(table)}`;
187
+ }
188
+
189
+ export function assertDatabaseTableCreated(value: unknown): asserts value is DatabaseTableCreated {
190
+ if (
191
+ !record(value) ||
192
+ !exact(value, ["table", "created"]) ||
193
+ typeof value["created"] !== "boolean" ||
194
+ !isDatabaseTable(value["table"])
195
+ )
196
+ throw new TypeError("Invalid database table");
197
+ }
198
+
199
+ function parseColumn(value: unknown): ColumnInput {
200
+ if (
201
+ !record(value) ||
202
+ !onlyKeys(value, ["name", "type", "nullable", "identity", "default"]) ||
203
+ !isDatabaseTableIdentifier(value["name"]) ||
204
+ !oneOf(DATABASE_TABLE_COLUMN_TYPES, value["type"]) ||
205
+ (value["nullable"] !== undefined && typeof value["nullable"] !== "boolean") ||
206
+ (value["identity"] !== undefined && typeof value["identity"] !== "boolean") ||
207
+ (value["default"] !== undefined && !oneOf(DATABASE_TABLE_DEFAULTS, value["default"]))
208
+ )
209
+ throw new TypeError("Invalid database table request");
210
+ const type = value["type"];
211
+ const identity = value["identity"] === true;
212
+ const defaultValue = value["default"] ?? null;
213
+ if (identity && type !== "integer" && type !== "bigint")
214
+ throw new TypeError("Invalid database table request");
215
+ if (identity && defaultValue !== null) throw new TypeError("Invalid database table request");
216
+ if (defaultValue === "now" && type !== "timestamptz")
217
+ throw new TypeError("Invalid database table request");
218
+ if (defaultValue === "uuid" && type !== "uuid")
219
+ throw new TypeError("Invalid database table request");
220
+ return {
221
+ name: value["name"],
222
+ type,
223
+ nullable: value["nullable"],
224
+ identity,
225
+ default: defaultValue,
226
+ };
227
+ }
228
+
229
+ function parsePrimaryKey(value: unknown, names: readonly string[]): readonly string[] {
230
+ if (value === undefined) return [];
231
+ if (
232
+ !Array.isArray(value) ||
233
+ value.length < 1 ||
234
+ value.length > DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS ||
235
+ new Set(value).size !== value.length ||
236
+ !value.every((name) => typeof name === "string" && names.includes(name))
237
+ )
238
+ throw new TypeError("Invalid database table request");
239
+ return value as string[];
240
+ }
241
+
242
+ /** Identity and primary key columns are NOT NULL; asking for a nullable one of them is a contradiction. */
243
+ function completeColumn(column: ColumnInput, keyed: boolean): DatabaseTableColumn {
244
+ const required = column.identity || keyed;
245
+ if (required && column.nullable === true) throw new TypeError("Invalid database table request");
246
+ return {
247
+ name: column.name,
248
+ type: column.type,
249
+ nullable: required ? false : (column.nullable ?? true),
250
+ identity: column.identity,
251
+ default: column.default,
252
+ };
253
+ }
254
+
255
+ function renderColumn(column: DatabaseTableColumn): string {
256
+ if (!oneOf(DATABASE_TABLE_COLUMN_TYPES, column.type))
257
+ throw new TypeError("Invalid database table column type");
258
+ const parts = [quoted(column.name), column.type];
259
+ if (!column.nullable) parts.push("NOT NULL");
260
+ if (column.identity) parts.push("GENERATED ALWAYS AS IDENTITY");
261
+ if (column.default === "now") parts.push("DEFAULT now()");
262
+ if (column.default === "uuid") parts.push("DEFAULT gen_random_uuid()");
263
+ return parts.join(" ");
264
+ }
265
+
266
+ function quoted(identifier: string): string {
267
+ if (!isDatabaseTableIdentifier(identifier))
268
+ throw new TypeError("Invalid database table identifier");
269
+ return `"${identifier}"`;
270
+ }
271
+
272
+ function isDatabaseTable(value: unknown): boolean {
273
+ if (!record(value) || !exact(value, ["schema", "table", "columns", "primaryKey"])) return false;
274
+ const columns = value["columns"];
275
+ const primaryKey = value["primaryKey"];
276
+ if (
277
+ value["schema"] !== DATABASE_TABLE_SCHEMA ||
278
+ !isDatabaseTableIdentifier(value["table"]) ||
279
+ !Array.isArray(columns) ||
280
+ columns.length < 1 ||
281
+ columns.length > DATABASE_TABLE_MAX_COLUMNS ||
282
+ !columns.every(isDatabaseTableColumn) ||
283
+ !Array.isArray(primaryKey) ||
284
+ primaryKey.length > DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS
285
+ )
286
+ return false;
287
+ const names = columns.map((column) => column.name);
288
+ return primaryKey.every((name) => typeof name === "string" && names.includes(name));
289
+ }
290
+
291
+ function isDatabaseTableColumn(value: unknown): value is DatabaseTableColumn {
292
+ return (
293
+ record(value) &&
294
+ exact(value, ["name", "type", "nullable", "identity", "default"]) &&
295
+ isDatabaseTableIdentifier(value["name"]) &&
296
+ oneOf(DATABASE_TABLE_COLUMN_TYPES, value["type"]) &&
297
+ typeof value["nullable"] === "boolean" &&
298
+ typeof value["identity"] === "boolean" &&
299
+ (value["default"] === null || oneOf(DATABASE_TABLE_DEFAULTS, value["default"]))
300
+ );
301
+ }
302
+
303
+ function oneOf<T extends string>(allowed: readonly T[], value: unknown): value is T {
304
+ return typeof value === "string" && (allowed as readonly string[]).includes(value);
305
+ }
306
+
307
+ function record(value: unknown): value is Record<string, unknown> {
308
+ return typeof value === "object" && value !== null && !Array.isArray(value);
309
+ }
310
+
311
+ function onlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {
312
+ return Object.keys(value).every((key) => allowed.includes(key));
313
+ }
314
+
315
+ function exact(value: Record<string, unknown>, keys: readonly string[]): boolean {
316
+ return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
317
+ }
@@ -192,6 +192,55 @@ export const PROBLEM_POLICY = {
192
192
  "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.",
193
193
  title: "Webhook delivery still pending",
194
194
  },
195
+ database_watch_limit: {
196
+ status: 409,
197
+ retryable: false,
198
+ action:
199
+ "List the project's database watches for this environment and delete one you no longer need, then create the watch again.",
200
+ title: "Database watch limit reached",
201
+ },
202
+ database_watch_table_unavailable: {
203
+ status: 409,
204
+ retryable: false,
205
+ action:
206
+ "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.",
207
+ title: "Table cannot be watched",
208
+ },
209
+ database_watch_column_unavailable: {
210
+ status: 409,
211
+ retryable: false,
212
+ action:
213
+ "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.",
214
+ title: "Watched column does not exist",
215
+ },
216
+ database_watch_events_unavailable: {
217
+ status: 409,
218
+ retryable: false,
219
+ action:
220
+ "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.",
221
+ title: "Database events not installed yet",
222
+ },
223
+ database_watch_conflict: {
224
+ status: 409,
225
+ retryable: false,
226
+ action:
227
+ "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.",
228
+ title: "Table already has a capture trigger",
229
+ },
230
+ database_table_conflict: {
231
+ status: 409,
232
+ retryable: false,
233
+ action:
234
+ "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.",
235
+ title: "Table already exists with another definition",
236
+ },
237
+ database_table_limit: {
238
+ status: 409,
239
+ retryable: false,
240
+ action:
241
+ "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.",
242
+ title: "Database table limit reached",
243
+ },
195
244
  project_notes_conflict: {
196
245
  status: 409,
197
246
  retryable: false,
@@ -502,7 +551,7 @@ export const PROBLEM_POLICY = {
502
551
  status: 503,
503
552
  retryable: false,
504
553
  action:
505
- "Report the platform user-key permission configuration through feedback. Repeated login or creation will not fix it.",
554
+ "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.",
506
555
  title: "Token permissions unavailable",
507
556
  },
508
557
  reconciliation_exhausted: {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,6 +23,8 @@ export {
23
23
  createOrganization,
24
24
  createProject,
25
25
  createProjectDatabaseAccess,
26
+ createProjectDatabaseTable,
27
+ createProjectDatabaseWatch,
26
28
  createProjectDatabaseWebhook,
27
29
  createProjectDataChange,
28
30
  createProjectDevAccessTicket,
@@ -35,6 +37,7 @@ export {
35
37
  deletePaidProjectDomain,
36
38
  deleteProject,
37
39
  deleteProjectDatabaseSchedule,
40
+ deleteProjectDatabaseWatch,
38
41
  deleteProjectDatabaseWebhook,
39
42
  detachProjectMailDomainBinding,
40
43
  diffManagedSource,
@@ -91,6 +94,7 @@ export {
91
94
  listProjectDatabaseAccess,
92
95
  listProjectDatabaseScheduleRuns,
93
96
  listProjectDatabaseSchedules,
97
+ listProjectDatabaseWatches,
94
98
  listProjectDatabaseWebhookDeliveries,
95
99
  listProjectDatabaseWebhooks,
96
100
  listProjects,
@@ -271,6 +275,16 @@ export type {
271
275
  CreateProjectDatabaseAccessErrors,
272
276
  CreateProjectDatabaseAccessResponse,
273
277
  CreateProjectDatabaseAccessResponses,
278
+ CreateProjectDatabaseTableData,
279
+ CreateProjectDatabaseTableError,
280
+ CreateProjectDatabaseTableErrors,
281
+ CreateProjectDatabaseTableResponse,
282
+ CreateProjectDatabaseTableResponses,
283
+ CreateProjectDatabaseWatchData,
284
+ CreateProjectDatabaseWatchError,
285
+ CreateProjectDatabaseWatchErrors,
286
+ CreateProjectDatabaseWatchResponse,
287
+ CreateProjectDatabaseWatchResponses,
274
288
  CreateProjectDatabaseWebhookData,
275
289
  CreateProjectDatabaseWebhookError,
276
290
  CreateProjectDatabaseWebhookErrors,
@@ -325,6 +339,17 @@ export type {
325
339
  DatabaseScheduleRunAccepted,
326
340
  DatabaseScheduleRunPage,
327
341
  DatabaseScheduleSetRequest,
342
+ DatabaseTable,
343
+ DatabaseTableColumn,
344
+ DatabaseTableColumnRequest,
345
+ DatabaseTableColumnType,
346
+ DatabaseTableCreated,
347
+ DatabaseTableCreateRequest,
348
+ DatabaseWatch,
349
+ DatabaseWatchCreateRequest,
350
+ DatabaseWatchDeleted,
351
+ DatabaseWatchId,
352
+ DatabaseWatchPage,
328
353
  DatabaseWebhook,
329
354
  DatabaseWebhookCreated,
330
355
  DatabaseWebhookCreateRequest,
@@ -332,6 +357,8 @@ export type {
332
357
  DatabaseWebhookDelivery,
333
358
  DatabaseWebhookDeliveryId,
334
359
  DatabaseWebhookDeliveryPage,
360
+ DatabaseWebhookEventPattern,
361
+ DatabaseWebhookEventPatternWritable,
335
362
  DatabaseWebhookEventType,
336
363
  DatabaseWebhookId,
337
364
  DatabaseWebhookPage,
@@ -364,6 +391,11 @@ export type {
364
391
  DeleteProjectDatabaseScheduleErrors,
365
392
  DeleteProjectDatabaseScheduleResponse,
366
393
  DeleteProjectDatabaseScheduleResponses,
394
+ DeleteProjectDatabaseWatchData,
395
+ DeleteProjectDatabaseWatchError,
396
+ DeleteProjectDatabaseWatchErrors,
397
+ DeleteProjectDatabaseWatchResponse,
398
+ DeleteProjectDatabaseWatchResponses,
367
399
  DeleteProjectDatabaseWebhookData,
368
400
  DeleteProjectDatabaseWebhookError,
369
401
  DeleteProjectDatabaseWebhookErrors,
@@ -685,6 +717,11 @@ export type {
685
717
  ListProjectDatabaseSchedulesErrors,
686
718
  ListProjectDatabaseSchedulesResponse,
687
719
  ListProjectDatabaseSchedulesResponses,
720
+ ListProjectDatabaseWatchesData,
721
+ ListProjectDatabaseWatchesError,
722
+ ListProjectDatabaseWatchesErrors,
723
+ ListProjectDatabaseWatchesResponse,
724
+ ListProjectDatabaseWatchesResponses,
688
725
  ListProjectDatabaseWebhookDeliveriesData,
689
726
  ListProjectDatabaseWebhookDeliveriesError,
690
727
  ListProjectDatabaseWebhookDeliveriesErrors,