@amerged/ohmyhost-cli 0.1.45 → 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.
- package/README.md +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-C6KEAOJP.js → chunk-A4ZYW4XW.js} +373 -56
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/apps/product-cli/package.json +1 -1
- package/src/apps/product-cli/src/cli.ts +76 -1
- package/src/apps/product-cli/src/command.ts +63 -0
- package/src/apps/product-cli/src/product-api.ts +116 -0
- package/src/packages/contracts/package.json +5 -0
- package/src/packages/contracts/src/database-tables.ts +317 -0
- package/src/packages/contracts/src/problem-policy.ts +14 -0
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/sdk-ts/src/generated/index.ts +12 -0
- package/src/packages/sdk-ts/src/generated/sdk.gen.ts +54 -0
- package/src/packages/sdk-ts/src/generated/types.gen.ts +141 -0
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
StderrDeviceAuthorizationPresenter,
|
|
35
35
|
TokenRefreshRejectedError,
|
|
36
36
|
UserTokenFileError,
|
|
37
|
+
assertDatabaseTableAnswer,
|
|
37
38
|
assertVerifiedLogin,
|
|
38
39
|
chooseLinkedProject,
|
|
39
40
|
chooseWorkspace,
|
|
@@ -100,7 +101,7 @@ import {
|
|
|
100
101
|
selectWorkspace,
|
|
101
102
|
verifiedUserId,
|
|
102
103
|
workspaceOf
|
|
103
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-A4ZYW4XW.js";
|
|
104
105
|
import {
|
|
105
106
|
CredentialStoreIntegrityError,
|
|
106
107
|
CredentialStoreUnavailableError,
|
|
@@ -287,6 +288,7 @@ export {
|
|
|
287
288
|
StderrDeviceAuthorizationPresenter,
|
|
288
289
|
TokenRefreshRejectedError,
|
|
289
290
|
UserTokenFileError,
|
|
291
|
+
assertDatabaseTableAnswer,
|
|
290
292
|
assertVerifiedLogin,
|
|
291
293
|
chooseLinkedProject,
|
|
292
294
|
chooseWorkspace,
|
package/package.json
CHANGED
|
@@ -68,7 +68,15 @@ import {
|
|
|
68
68
|
type CurrentCredential,
|
|
69
69
|
type StoredCredential,
|
|
70
70
|
} from "./credential-store.js";
|
|
71
|
-
import type {
|
|
71
|
+
import type {
|
|
72
|
+
DatabaseTableColumnInput,
|
|
73
|
+
DatabaseTableCreateInput,
|
|
74
|
+
ProductApi,
|
|
75
|
+
} from "./product-api.js";
|
|
76
|
+
import {
|
|
77
|
+
parseDatabaseTableCreateRequest,
|
|
78
|
+
type DatabaseTableColumn,
|
|
79
|
+
} from "@ohmyhost/contracts/database-tables";
|
|
72
80
|
import {
|
|
73
81
|
defaultProfileName,
|
|
74
82
|
isProfileUserId,
|
|
@@ -425,6 +433,8 @@ const HELP_USAGE = Object.freeze({
|
|
|
425
433
|
"ohmyhost database watch list --project ULID [--environment dev|prod] --json (dev when omitted; a sleeping database wakes)",
|
|
426
434
|
"database watch delete":
|
|
427
435
|
"ohmyhost database watch delete --project ULID --environment dev|prod --watch ID --idempotency-key KEY --yes --json",
|
|
436
|
+
"database table create":
|
|
437
|
+
"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)",
|
|
428
438
|
"database webhook create":
|
|
429
439
|
"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)",
|
|
430
440
|
"database webhook list":
|
|
@@ -665,6 +675,9 @@ const SCHEDULE_STATEMENTS_FILE_INVALID_ACTION =
|
|
|
665
675
|
const SCHEDULE_INVALID_ACTION =
|
|
666
676
|
"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.";
|
|
667
677
|
|
|
678
|
+
const COLUMNS_FILE_INVALID_ACTION =
|
|
679
|
+
"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.";
|
|
680
|
+
|
|
668
681
|
const STATEMENT_FILE_INVALID_ACTION =
|
|
669
682
|
"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.";
|
|
670
683
|
|
|
@@ -1203,6 +1216,8 @@ export class ProductCli {
|
|
|
1203
1216
|
return await this.listDatabaseWatches(command, signal);
|
|
1204
1217
|
if (command.kind === "database-watch-delete")
|
|
1205
1218
|
return await this.deleteDatabaseWatch(command, signal);
|
|
1219
|
+
if (command.kind === "database-table-create")
|
|
1220
|
+
return await this.createDatabaseTable(command, signal);
|
|
1206
1221
|
if (command.kind === "database-webhook-create")
|
|
1207
1222
|
return await this.createDatabaseWebhook(command, signal);
|
|
1208
1223
|
if (command.kind === "database-webhook-list")
|
|
@@ -2009,6 +2024,22 @@ export class ProductCli {
|
|
|
2009
2024
|
return success("database watch delete", { status: "succeeded", deleted });
|
|
2010
2025
|
}
|
|
2011
2026
|
|
|
2027
|
+
/** The columns file and the key are checked before any request; a repeat answers the same table with created false. */
|
|
2028
|
+
private async createDatabaseTable(
|
|
2029
|
+
command: Extract<ProductCliCommand, { kind: "database-table-create" }>,
|
|
2030
|
+
signal: AbortSignal,
|
|
2031
|
+
): Promise<CliResult> {
|
|
2032
|
+
const request = await tableCreateRequest(command);
|
|
2033
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
2034
|
+
const answer = await api.createProjectDatabaseTable({
|
|
2035
|
+
projectId: command.projectId,
|
|
2036
|
+
environment: command.environment,
|
|
2037
|
+
idempotencyKey: command.idempotencyKey,
|
|
2038
|
+
request,
|
|
2039
|
+
});
|
|
2040
|
+
return success("database table create", { status: "succeeded", table: answer.table });
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2012
2043
|
/** The signing secret is returned by create only; every other answer carries no secret. */
|
|
2013
2044
|
private async createDatabaseWebhook(
|
|
2014
2045
|
command: Extract<ProductCliCommand, { kind: "database-webhook-create" }>,
|
|
@@ -3479,6 +3510,49 @@ const scheduleSetRequest = async (
|
|
|
3479
3510
|
}
|
|
3480
3511
|
};
|
|
3481
3512
|
|
|
3513
|
+
/**
|
|
3514
|
+
* The table of `database table create`: the columns file is a UTF-8 JSON array, and the table, the
|
|
3515
|
+
* columns and the key are checked by the contract the service uses. A refused file sends nothing.
|
|
3516
|
+
*/
|
|
3517
|
+
const tableCreateRequest = async (
|
|
3518
|
+
command: Extract<ProductCliCommand, { kind: "database-table-create" }>,
|
|
3519
|
+
): Promise<DatabaseTableCreateInput> => {
|
|
3520
|
+
const columnsFailure = () =>
|
|
3521
|
+
new ProductCliFailure(2, "columns_file_invalid", false, COLUMNS_FILE_INVALID_ACTION);
|
|
3522
|
+
const columns = await readBoundedUtf8File(command.columnsFile, 65_536, columnsFailure).then(
|
|
3523
|
+
(text: string): unknown => {
|
|
3524
|
+
try {
|
|
3525
|
+
return JSON.parse(text) as unknown;
|
|
3526
|
+
} catch {
|
|
3527
|
+
throw columnsFailure();
|
|
3528
|
+
}
|
|
3529
|
+
},
|
|
3530
|
+
);
|
|
3531
|
+
try {
|
|
3532
|
+
const parsed = parseDatabaseTableCreateRequest({
|
|
3533
|
+
table: command.table,
|
|
3534
|
+
columns,
|
|
3535
|
+
...(command.primaryKey.length === 0 ? {} : { primaryKey: command.primaryKey }),
|
|
3536
|
+
});
|
|
3537
|
+
return {
|
|
3538
|
+
table: parsed.table,
|
|
3539
|
+
columns: parsed.columns.map(tableColumnInput),
|
|
3540
|
+
primaryKey: parsed.primaryKey,
|
|
3541
|
+
};
|
|
3542
|
+
} catch {
|
|
3543
|
+
throw columnsFailure();
|
|
3544
|
+
}
|
|
3545
|
+
};
|
|
3546
|
+
|
|
3547
|
+
/** The checked column as the REST body names it; a default that is not set is left out, not sent as null. */
|
|
3548
|
+
const tableColumnInput = (column: DatabaseTableColumn): DatabaseTableColumnInput => ({
|
|
3549
|
+
name: column.name,
|
|
3550
|
+
type: column.type,
|
|
3551
|
+
nullable: column.nullable,
|
|
3552
|
+
identity: column.identity,
|
|
3553
|
+
...(column.default === null ? {} : { default: column.default }),
|
|
3554
|
+
});
|
|
3555
|
+
|
|
3482
3556
|
const psqlUnavailable = (): CliResult =>
|
|
3483
3557
|
failure(
|
|
3484
3558
|
"database psql",
|
|
@@ -3586,6 +3660,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
|
|
|
3586
3660
|
if (command.kind === "database-watch-create") return "database watch create";
|
|
3587
3661
|
if (command.kind === "database-watch-list") return "database watch list";
|
|
3588
3662
|
if (command.kind === "database-watch-delete") return "database watch delete";
|
|
3663
|
+
if (command.kind === "database-table-create") return "database table create";
|
|
3589
3664
|
if (command.kind === "database-webhook-create") return "database webhook create";
|
|
3590
3665
|
if (command.kind === "database-webhook-list") return "database webhook list";
|
|
3591
3666
|
if (command.kind === "database-webhook-get") return "database webhook get";
|
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
type DatabaseWatchRequest,
|
|
14
14
|
type DatabaseWebhookEventPattern,
|
|
15
15
|
} from "@ohmyhost/contracts/database-events";
|
|
16
|
+
import {
|
|
17
|
+
DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS,
|
|
18
|
+
isDatabaseTableIdentifier,
|
|
19
|
+
} from "@ohmyhost/contracts/database-tables";
|
|
16
20
|
import {
|
|
17
21
|
databaseSizeForMemory,
|
|
18
22
|
type DatabaseComputeSelection,
|
|
@@ -416,6 +420,17 @@ export type ProductCliCommand =
|
|
|
416
420
|
readonly yes: true;
|
|
417
421
|
readonly credentialStore: "native";
|
|
418
422
|
}
|
|
423
|
+
| {
|
|
424
|
+
readonly kind: "database-table-create";
|
|
425
|
+
readonly projectId: string;
|
|
426
|
+
readonly environment: "dev" | "prod";
|
|
427
|
+
readonly table: string;
|
|
428
|
+
readonly columnsFile: string;
|
|
429
|
+
readonly primaryKey: readonly string[];
|
|
430
|
+
readonly idempotencyKey: string;
|
|
431
|
+
readonly yes: true;
|
|
432
|
+
readonly credentialStore: "native";
|
|
433
|
+
}
|
|
419
434
|
| {
|
|
420
435
|
readonly kind: "database-watch-list";
|
|
421
436
|
readonly projectId: string;
|
|
@@ -1033,6 +1048,7 @@ export type RecognizedCommandLabel =
|
|
|
1033
1048
|
| "database watch create"
|
|
1034
1049
|
| "database watch list"
|
|
1035
1050
|
| "database watch delete"
|
|
1051
|
+
| "database table create"
|
|
1036
1052
|
| "database webhook create"
|
|
1037
1053
|
| "database webhook list"
|
|
1038
1054
|
| "database webhook get"
|
|
@@ -1209,6 +1225,8 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
1209
1225
|
if (values[1] === "list") return "database watch list";
|
|
1210
1226
|
if (values[1] === "delete") return "database watch delete";
|
|
1211
1227
|
}
|
|
1228
|
+
if (value === "database" && values[0] === "table" && values[1] === "create")
|
|
1229
|
+
return "database table create";
|
|
1212
1230
|
if (value === "database" && values[0] === "webhook") {
|
|
1213
1231
|
if (values[1] === "create") return "database webhook create";
|
|
1214
1232
|
if (values[1] === "list") return "database webhook list";
|
|
@@ -2184,6 +2202,51 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
2184
2202
|
}
|
|
2185
2203
|
throw new InvalidCommandError();
|
|
2186
2204
|
}
|
|
2205
|
+
if (first === "database" && values[0] === "table") {
|
|
2206
|
+
values.shift();
|
|
2207
|
+
const subcommand = values.shift();
|
|
2208
|
+
if (subcommand === "create") {
|
|
2209
|
+
if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
|
|
2210
|
+
const options = parseOptionalOptions(values, [
|
|
2211
|
+
"--project",
|
|
2212
|
+
"--environment",
|
|
2213
|
+
"--table",
|
|
2214
|
+
"--columns-file",
|
|
2215
|
+
"--primary-key",
|
|
2216
|
+
"--idempotency-key",
|
|
2217
|
+
]);
|
|
2218
|
+
const projectId = options["--project"],
|
|
2219
|
+
environment = options["--environment"],
|
|
2220
|
+
table = options["--table"],
|
|
2221
|
+
columnsFile = options["--columns-file"],
|
|
2222
|
+
primaryKey = options["--primary-key"],
|
|
2223
|
+
idempotencyKey = options["--idempotency-key"];
|
|
2224
|
+
const keyNames = primaryKey === undefined ? [] : primaryKey.split(",");
|
|
2225
|
+
if (
|
|
2226
|
+
!isValue(projectId) ||
|
|
2227
|
+
!isValue(idempotencyKey) ||
|
|
2228
|
+
!isValue(table) ||
|
|
2229
|
+
!isValue(columnsFile) ||
|
|
2230
|
+
(primaryKey !== undefined && !keyNames.every(isDatabaseTableIdentifier)) ||
|
|
2231
|
+
keyNames.length > DATABASE_TABLE_MAX_PRIMARY_KEY_COLUMNS ||
|
|
2232
|
+
!isDatabaseTableIdentifier(table) ||
|
|
2233
|
+
(environment !== "dev" && environment !== "prod")
|
|
2234
|
+
)
|
|
2235
|
+
throw new InvalidCommandError();
|
|
2236
|
+
return {
|
|
2237
|
+
kind: "database-table-create",
|
|
2238
|
+
projectId,
|
|
2239
|
+
environment,
|
|
2240
|
+
table,
|
|
2241
|
+
columnsFile,
|
|
2242
|
+
primaryKey: keyNames,
|
|
2243
|
+
idempotencyKey,
|
|
2244
|
+
yes: true,
|
|
2245
|
+
credentialStore,
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
throw new InvalidCommandError();
|
|
2249
|
+
}
|
|
2187
2250
|
if (first === "database" && values[0] === "webhook") {
|
|
2188
2251
|
values.shift();
|
|
2189
2252
|
const subcommand = values.shift();
|
|
@@ -20,6 +20,13 @@ import {
|
|
|
20
20
|
type ProjectDatabaseWriteReceipt,
|
|
21
21
|
} from "@ohmyhost/contracts/database-access";
|
|
22
22
|
import { assertDatabaseCompute, type DatabaseCompute } from "@ohmyhost/contracts/database-compute";
|
|
23
|
+
import {
|
|
24
|
+
DATABASE_TABLE_COLUMN_TYPES,
|
|
25
|
+
DATABASE_TABLE_DEFAULTS,
|
|
26
|
+
isDatabaseTableIdentifier,
|
|
27
|
+
type DatabaseTableColumnType,
|
|
28
|
+
type DatabaseTableDefault,
|
|
29
|
+
} from "@ohmyhost/contracts/database-tables";
|
|
23
30
|
import {
|
|
24
31
|
assertDatabaseExecutionUsagePage,
|
|
25
32
|
assertDatabaseSchedule,
|
|
@@ -162,6 +169,7 @@ import {
|
|
|
162
169
|
createProjectDatabaseWatch,
|
|
163
170
|
listProjectDatabaseWatches,
|
|
164
171
|
deleteProjectDatabaseWatch,
|
|
172
|
+
createProjectDatabaseTable,
|
|
165
173
|
listProjectDatabaseWebhookDeliveries,
|
|
166
174
|
replayProjectDatabaseWebhookDelivery,
|
|
167
175
|
setProjectDatabaseSchedule,
|
|
@@ -215,6 +223,39 @@ export interface ProjectCreateInput {
|
|
|
215
223
|
readonly idempotencyKey: string;
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
/** One column of a table to create; nullable, identity and default are optional as the REST body allows. */
|
|
227
|
+
export interface DatabaseTableColumnInput {
|
|
228
|
+
readonly name: string;
|
|
229
|
+
readonly type: DatabaseTableColumnType;
|
|
230
|
+
readonly nullable?: boolean;
|
|
231
|
+
readonly identity?: boolean;
|
|
232
|
+
readonly default?: DatabaseTableDefault;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface DatabaseTableCreateInput {
|
|
236
|
+
readonly table: string;
|
|
237
|
+
readonly columns: readonly DatabaseTableColumnInput[];
|
|
238
|
+
readonly primaryKey: readonly string[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The created or identical existing table, every column with its concrete nullability and default. */
|
|
242
|
+
export interface DatabaseTableAnswer {
|
|
243
|
+
readonly table: {
|
|
244
|
+
readonly environment: "dev" | "prod";
|
|
245
|
+
readonly schema: "public";
|
|
246
|
+
readonly table: string;
|
|
247
|
+
readonly columns: readonly {
|
|
248
|
+
readonly name: string;
|
|
249
|
+
readonly type: DatabaseTableColumnType;
|
|
250
|
+
readonly nullable: boolean;
|
|
251
|
+
readonly identity: boolean;
|
|
252
|
+
readonly default: DatabaseTableDefault | null;
|
|
253
|
+
}[];
|
|
254
|
+
readonly primary_key: readonly string[];
|
|
255
|
+
readonly created: boolean;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
218
259
|
export interface ProductApi {
|
|
219
260
|
readonly managedSource?: ManagedSourceApi;
|
|
220
261
|
planProjectDataChange(input: {
|
|
@@ -376,6 +417,12 @@ export interface ProductApi {
|
|
|
376
417
|
readonly idempotencyKey: string;
|
|
377
418
|
readonly watchId: string;
|
|
378
419
|
}): Promise<DatabaseWatchDeleted>;
|
|
420
|
+
createProjectDatabaseTable(input: {
|
|
421
|
+
readonly projectId: string;
|
|
422
|
+
readonly environment: "dev" | "prod";
|
|
423
|
+
readonly idempotencyKey: string;
|
|
424
|
+
readonly request: DatabaseTableCreateInput;
|
|
425
|
+
}): Promise<DatabaseTableAnswer>;
|
|
379
426
|
listProjectDatabaseWebhookDeliveries(input: {
|
|
380
427
|
readonly projectId: string;
|
|
381
428
|
readonly environment: "dev" | "prod";
|
|
@@ -593,9 +640,54 @@ export interface ProductApi {
|
|
|
593
640
|
/** A webhook answer that names another environment, webhook or delivery than the request did. */
|
|
594
641
|
const WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
|
|
595
642
|
const WATCH_SCOPE_MISMATCH = "Watch response scope mismatch";
|
|
643
|
+
const TABLE_SCOPE_MISMATCH = "Table response scope mismatch";
|
|
596
644
|
/** A schedule answer that names another environment or schedule than the request did. */
|
|
597
645
|
const SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
|
|
598
646
|
|
|
647
|
+
/** The REST answer read exactly: no extra keys, closed column types and defaults, keys among the columns. */
|
|
648
|
+
export function assertDatabaseTableAnswer(value: unknown): asserts value is DatabaseTableAnswer {
|
|
649
|
+
const table = isRecordValue(value) ? value["table"] : undefined;
|
|
650
|
+
const columns = isRecordValue(table) ? table["columns"] : undefined;
|
|
651
|
+
const primaryKey = isRecordValue(table) ? table["primary_key"] : undefined;
|
|
652
|
+
if (
|
|
653
|
+
!isRecordValue(table) ||
|
|
654
|
+
!hasExactKeys(table, ["environment", "schema", "table", "columns", "primary_key", "created"]) ||
|
|
655
|
+
(table["environment"] !== "dev" && table["environment"] !== "prod") ||
|
|
656
|
+
table["schema"] !== "public" ||
|
|
657
|
+
!isDatabaseTableIdentifier(table["table"]) ||
|
|
658
|
+
typeof table["created"] !== "boolean" ||
|
|
659
|
+
!Array.isArray(columns) ||
|
|
660
|
+
columns.length < 1 ||
|
|
661
|
+
!columns.every(isDatabaseTableAnswerColumn) ||
|
|
662
|
+
!Array.isArray(primaryKey) ||
|
|
663
|
+
!primaryKey.every((name: unknown) =>
|
|
664
|
+
columns.some((column: Record<string, unknown>) => column["name"] === name),
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
throw new TypeError("Invalid database table answer");
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function isDatabaseTableAnswerColumn(value: unknown): boolean {
|
|
671
|
+
return (
|
|
672
|
+
isRecordValue(value) &&
|
|
673
|
+
hasExactKeys(value, ["name", "type", "nullable", "identity", "default"]) &&
|
|
674
|
+
isDatabaseTableIdentifier(value["name"]) &&
|
|
675
|
+
(DATABASE_TABLE_COLUMN_TYPES as readonly unknown[]).includes(value["type"]) &&
|
|
676
|
+
typeof value["nullable"] === "boolean" &&
|
|
677
|
+
typeof value["identity"] === "boolean" &&
|
|
678
|
+
(value["default"] === null ||
|
|
679
|
+
(DATABASE_TABLE_DEFAULTS as readonly unknown[]).includes(value["default"]))
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function isRecordValue(value: unknown): value is Record<string, unknown> {
|
|
684
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
688
|
+
return Object.keys(value).sort().join(",") === [...keys].sort().join(",");
|
|
689
|
+
}
|
|
690
|
+
|
|
599
691
|
export class GeneratedSdkProductApi implements ProductApi {
|
|
600
692
|
readonly #client: OhMyHostClient;
|
|
601
693
|
public readonly managedSource: ManagedSourceApi;
|
|
@@ -973,6 +1065,30 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
973
1065
|
return value;
|
|
974
1066
|
}
|
|
975
1067
|
|
|
1068
|
+
public async createProjectDatabaseTable(
|
|
1069
|
+
input: Parameters<ProductApi["createProjectDatabaseTable"]>[0],
|
|
1070
|
+
): Promise<DatabaseTableAnswer> {
|
|
1071
|
+
const value = await createProjectDatabaseTable(
|
|
1072
|
+
{
|
|
1073
|
+
project_id: input.projectId,
|
|
1074
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
1075
|
+
databaseTableCreateRequest: {
|
|
1076
|
+
environment: input.environment,
|
|
1077
|
+
table: input.request.table,
|
|
1078
|
+
columns: [...input.request.columns],
|
|
1079
|
+
...(input.request.primaryKey.length === 0
|
|
1080
|
+
? {}
|
|
1081
|
+
: { primary_key: [...input.request.primaryKey] }),
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
{ client: this.#client },
|
|
1085
|
+
);
|
|
1086
|
+
assertDatabaseTableAnswer(value);
|
|
1087
|
+
if (value.table.environment !== input.environment || value.table.table !== input.request.table)
|
|
1088
|
+
throw new TypeError(TABLE_SCOPE_MISMATCH);
|
|
1089
|
+
return value;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
976
1092
|
/** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
|
|
977
1093
|
public async createProjectDatabaseWebhook(
|
|
978
1094
|
input: Parameters<ProductApi["createProjectDatabaseWebhook"]>[0],
|
|
@@ -132,6 +132,11 @@
|
|
|
132
132
|
"types": "./dist/database-events.d.ts",
|
|
133
133
|
"import": "./dist/database-events.js"
|
|
134
134
|
},
|
|
135
|
+
"./database-tables": {
|
|
136
|
+
"source": "./src/database-tables.ts",
|
|
137
|
+
"types": "./dist/database-tables.d.ts",
|
|
138
|
+
"import": "./dist/database-tables.js"
|
|
139
|
+
},
|
|
135
140
|
"./functions": {
|
|
136
141
|
"source": "./src/functions.ts",
|
|
137
142
|
"types": "./dist/functions.d.ts",
|