@amerged/ohmyhost-cli 0.1.41 → 0.1.43
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-SB6RMO3M.js → chunk-5TO5I2HF.js} +1662 -333
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/src/apps/product-cli/package.json +1 -1
- package/src/apps/product-cli/src/cli.ts +438 -38
- package/src/apps/product-cli/src/command.ts +490 -3
- package/src/apps/product-cli/src/composition.ts +6 -3
- package/src/apps/product-cli/src/product-api.ts +363 -0
- package/src/apps/product-cli/src/psql-session.ts +20 -32
- package/src/apps/product-cli/src/runtime-contract.ts +1 -13
- package/src/packages/contracts/package.json +5 -0
- package/src/packages/contracts/src/credit-pricing.ts +0 -90
- package/src/packages/contracts/src/database-access.ts +24 -45
- package/src/packages/contracts/src/database-events.ts +697 -0
- package/src/packages/contracts/src/problem-policy.ts +28 -0
- package/src/packages/postgres-bridge/src/bridge.ts +0 -4
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/sdk-ts/src/generated/index.ts +109 -1
- package/src/packages/sdk-ts/src/generated/sdk.gen.ts +697 -1
- package/src/packages/sdk-ts/src/generated/types.gen.ts +1153 -47
|
@@ -20,6 +20,36 @@ 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
|
+
assertDatabaseExecutionUsagePage,
|
|
25
|
+
assertDatabaseSchedule,
|
|
26
|
+
assertDatabaseSchedulePage,
|
|
27
|
+
assertDatabaseScheduleDeleted,
|
|
28
|
+
assertDatabaseScheduleRunAccepted,
|
|
29
|
+
assertDatabaseScheduleRunPage,
|
|
30
|
+
assertDatabaseWebhook,
|
|
31
|
+
assertDatabaseWebhookCreated,
|
|
32
|
+
assertDatabaseWebhookDeleted,
|
|
33
|
+
assertDatabaseWebhookDeliveryPage,
|
|
34
|
+
assertDatabaseWebhookPage,
|
|
35
|
+
assertDatabaseWebhookReplayAccepted,
|
|
36
|
+
assertDatabaseWebhookTest,
|
|
37
|
+
type DatabaseExecutionUsagePage,
|
|
38
|
+
type DatabaseSchedule,
|
|
39
|
+
type DatabaseScheduleDeleted,
|
|
40
|
+
type DatabaseSchedulePage,
|
|
41
|
+
type DatabaseScheduleRunAccepted,
|
|
42
|
+
type DatabaseScheduleRunPage,
|
|
43
|
+
type DatabaseScheduleSetRequest,
|
|
44
|
+
type DatabaseWebhook,
|
|
45
|
+
type DatabaseWebhookCreated,
|
|
46
|
+
type DatabaseWebhookDeleted,
|
|
47
|
+
type DatabaseWebhookDeliveryPage,
|
|
48
|
+
type DatabaseWebhookEventType,
|
|
49
|
+
type DatabaseWebhookPage,
|
|
50
|
+
type DatabaseWebhookReplayAccepted,
|
|
51
|
+
type DatabaseWebhookTestResult,
|
|
52
|
+
} from "@ohmyhost/contracts/database-events";
|
|
23
53
|
import type { DatabaseComputeSelection } from "@ohmyhost/contracts/database-sizes";
|
|
24
54
|
import {
|
|
25
55
|
assertProjectContext,
|
|
@@ -117,6 +147,20 @@ import {
|
|
|
117
147
|
createProjectDatabaseAccess,
|
|
118
148
|
listProjectDatabaseAccess,
|
|
119
149
|
revokeProjectDatabaseAccess,
|
|
150
|
+
createProjectDatabaseWebhook,
|
|
151
|
+
listProjectDatabaseWebhooks,
|
|
152
|
+
getProjectDatabaseWebhook,
|
|
153
|
+
deleteProjectDatabaseWebhook,
|
|
154
|
+
testProjectDatabaseWebhook,
|
|
155
|
+
listProjectDatabaseWebhookDeliveries,
|
|
156
|
+
replayProjectDatabaseWebhookDelivery,
|
|
157
|
+
setProjectDatabaseSchedule,
|
|
158
|
+
listProjectDatabaseSchedules,
|
|
159
|
+
getProjectDatabaseSchedule,
|
|
160
|
+
deleteProjectDatabaseSchedule,
|
|
161
|
+
listProjectDatabaseScheduleRuns,
|
|
162
|
+
runProjectDatabaseSchedule,
|
|
163
|
+
getProjectDatabaseExecutionUsage,
|
|
120
164
|
reconcileOperation,
|
|
121
165
|
streamOperationEvents,
|
|
122
166
|
type DeploymentPlan,
|
|
@@ -279,6 +323,85 @@ export interface ProductApi {
|
|
|
279
323
|
readonly projectId: string;
|
|
280
324
|
readonly accessId: string;
|
|
281
325
|
}): Promise<ProjectDatabaseAccess>;
|
|
326
|
+
createProjectDatabaseWebhook(input: {
|
|
327
|
+
readonly projectId: string;
|
|
328
|
+
readonly environment: "dev" | "prod";
|
|
329
|
+
readonly idempotencyKey: string;
|
|
330
|
+
readonly url: string;
|
|
331
|
+
readonly eventTypes: readonly DatabaseWebhookEventType[];
|
|
332
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
333
|
+
}): Promise<DatabaseWebhookCreated>;
|
|
334
|
+
listProjectDatabaseWebhooks(input: {
|
|
335
|
+
readonly projectId: string;
|
|
336
|
+
readonly environment: "dev" | "prod";
|
|
337
|
+
}): Promise<DatabaseWebhookPage>;
|
|
338
|
+
getProjectDatabaseWebhook(input: {
|
|
339
|
+
readonly projectId: string;
|
|
340
|
+
readonly environment: "dev" | "prod";
|
|
341
|
+
readonly webhookId: string;
|
|
342
|
+
}): Promise<DatabaseWebhook>;
|
|
343
|
+
deleteProjectDatabaseWebhook(input: {
|
|
344
|
+
readonly projectId: string;
|
|
345
|
+
readonly environment: "dev" | "prod";
|
|
346
|
+
readonly webhookId: string;
|
|
347
|
+
}): Promise<DatabaseWebhookDeleted>;
|
|
348
|
+
testProjectDatabaseWebhook(input: {
|
|
349
|
+
readonly projectId: string;
|
|
350
|
+
readonly environment: "dev" | "prod";
|
|
351
|
+
readonly webhookId: string;
|
|
352
|
+
}): Promise<DatabaseWebhookTestResult>;
|
|
353
|
+
listProjectDatabaseWebhookDeliveries(input: {
|
|
354
|
+
readonly projectId: string;
|
|
355
|
+
readonly environment: "dev" | "prod";
|
|
356
|
+
readonly webhookId?: string;
|
|
357
|
+
readonly limit?: number;
|
|
358
|
+
readonly cursor?: string;
|
|
359
|
+
}): Promise<DatabaseWebhookDeliveryPage>;
|
|
360
|
+
replayProjectDatabaseWebhookDelivery(input: {
|
|
361
|
+
readonly projectId: string;
|
|
362
|
+
readonly environment: "dev" | "prod";
|
|
363
|
+
readonly deliveryId: string;
|
|
364
|
+
}): Promise<DatabaseWebhookReplayAccepted>;
|
|
365
|
+
setProjectDatabaseSchedule(input: {
|
|
366
|
+
readonly projectId: string;
|
|
367
|
+
readonly environment: "dev" | "prod";
|
|
368
|
+
readonly name: string;
|
|
369
|
+
readonly request: DatabaseScheduleSetRequest;
|
|
370
|
+
}): Promise<DatabaseSchedule>;
|
|
371
|
+
listProjectDatabaseSchedules(input: {
|
|
372
|
+
readonly projectId: string;
|
|
373
|
+
readonly environment: "dev" | "prod";
|
|
374
|
+
}): Promise<DatabaseSchedulePage>;
|
|
375
|
+
getProjectDatabaseSchedule(input: {
|
|
376
|
+
readonly projectId: string;
|
|
377
|
+
readonly environment: "dev" | "prod";
|
|
378
|
+
readonly name: string;
|
|
379
|
+
}): Promise<DatabaseSchedule>;
|
|
380
|
+
deleteProjectDatabaseSchedule(input: {
|
|
381
|
+
readonly projectId: string;
|
|
382
|
+
readonly environment: "dev" | "prod";
|
|
383
|
+
readonly name: string;
|
|
384
|
+
}): Promise<DatabaseScheduleDeleted>;
|
|
385
|
+
listProjectDatabaseScheduleRuns(input: {
|
|
386
|
+
readonly projectId: string;
|
|
387
|
+
readonly environment: "dev" | "prod";
|
|
388
|
+
readonly name: string;
|
|
389
|
+
readonly limit?: number;
|
|
390
|
+
}): Promise<DatabaseScheduleRunPage>;
|
|
391
|
+
runProjectDatabaseSchedule(input: {
|
|
392
|
+
readonly projectId: string;
|
|
393
|
+
readonly environment: "dev" | "prod";
|
|
394
|
+
readonly name: string;
|
|
395
|
+
}): Promise<DatabaseScheduleRunAccepted>;
|
|
396
|
+
getProjectDatabaseExecutionUsage(input: {
|
|
397
|
+
readonly projectId: string;
|
|
398
|
+
readonly environment: "dev" | "prod";
|
|
399
|
+
readonly from: string;
|
|
400
|
+
readonly to: string;
|
|
401
|
+
readonly granularity: "hour" | "day";
|
|
402
|
+
readonly limit?: number;
|
|
403
|
+
readonly cursor?: string;
|
|
404
|
+
}): Promise<DatabaseExecutionUsagePage>;
|
|
282
405
|
planPaidDomain(input: {
|
|
283
406
|
readonly projectId: string;
|
|
284
407
|
readonly hostname: string;
|
|
@@ -441,6 +564,11 @@ export interface ProductApi {
|
|
|
441
564
|
managedMail?(input: ManagedMailCommand): Promise<unknown>;
|
|
442
565
|
}
|
|
443
566
|
|
|
567
|
+
/** A webhook answer that names another environment, webhook or delivery than the request did. */
|
|
568
|
+
const WEBHOOK_SCOPE_MISMATCH = "Webhook response scope mismatch";
|
|
569
|
+
/** A schedule answer that names another environment or schedule than the request did. */
|
|
570
|
+
const SCHEDULE_SCOPE_MISMATCH = "Schedule response scope mismatch";
|
|
571
|
+
|
|
444
572
|
export class GeneratedSdkProductApi implements ProductApi {
|
|
445
573
|
readonly #client: OhMyHostClient;
|
|
446
574
|
public readonly managedSource: ManagedSourceApi;
|
|
@@ -761,6 +889,241 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
761
889
|
return value;
|
|
762
890
|
}
|
|
763
891
|
|
|
892
|
+
/** The signing secret exists only in this answer; the CLI prints it once and stores nothing. */
|
|
893
|
+
public async createProjectDatabaseWebhook(
|
|
894
|
+
input: Parameters<ProductApi["createProjectDatabaseWebhook"]>[0],
|
|
895
|
+
): Promise<DatabaseWebhookCreated> {
|
|
896
|
+
const value = await createProjectDatabaseWebhook(
|
|
897
|
+
{
|
|
898
|
+
project_id: input.projectId,
|
|
899
|
+
environment: input.environment,
|
|
900
|
+
"Idempotency-Key": input.idempotencyKey,
|
|
901
|
+
databaseWebhookCreateRequest: {
|
|
902
|
+
url: input.url,
|
|
903
|
+
event_types: [...input.eventTypes],
|
|
904
|
+
headers: { ...input.headers },
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
{ client: this.#client },
|
|
908
|
+
);
|
|
909
|
+
assertDatabaseWebhookCreated(value);
|
|
910
|
+
if (value.environment !== input.environment) throw new TypeError(WEBHOOK_SCOPE_MISMATCH);
|
|
911
|
+
return value;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
public async listProjectDatabaseWebhooks(
|
|
915
|
+
input: Parameters<ProductApi["listProjectDatabaseWebhooks"]>[0],
|
|
916
|
+
): Promise<DatabaseWebhookPage> {
|
|
917
|
+
const value = await listProjectDatabaseWebhooks(
|
|
918
|
+
{ project_id: input.projectId, environment: input.environment },
|
|
919
|
+
{ client: this.#client },
|
|
920
|
+
);
|
|
921
|
+
assertDatabaseWebhookPage(value);
|
|
922
|
+
if (value.items.some((item) => item.environment !== input.environment))
|
|
923
|
+
throw new TypeError(WEBHOOK_SCOPE_MISMATCH);
|
|
924
|
+
return value;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
public async getProjectDatabaseWebhook(
|
|
928
|
+
input: Parameters<ProductApi["getProjectDatabaseWebhook"]>[0],
|
|
929
|
+
): Promise<DatabaseWebhook> {
|
|
930
|
+
const value = await getProjectDatabaseWebhook(
|
|
931
|
+
{
|
|
932
|
+
project_id: input.projectId,
|
|
933
|
+
environment: input.environment,
|
|
934
|
+
webhook_id: input.webhookId,
|
|
935
|
+
},
|
|
936
|
+
{ client: this.#client },
|
|
937
|
+
);
|
|
938
|
+
assertDatabaseWebhook(value);
|
|
939
|
+
if (value.id !== input.webhookId || value.environment !== input.environment)
|
|
940
|
+
throw new TypeError(WEBHOOK_SCOPE_MISMATCH);
|
|
941
|
+
return value;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
public async deleteProjectDatabaseWebhook(
|
|
945
|
+
input: Parameters<ProductApi["deleteProjectDatabaseWebhook"]>[0],
|
|
946
|
+
): Promise<DatabaseWebhookDeleted> {
|
|
947
|
+
const value = await deleteProjectDatabaseWebhook(
|
|
948
|
+
{
|
|
949
|
+
project_id: input.projectId,
|
|
950
|
+
environment: input.environment,
|
|
951
|
+
webhook_id: input.webhookId,
|
|
952
|
+
},
|
|
953
|
+
{ client: this.#client },
|
|
954
|
+
);
|
|
955
|
+
assertDatabaseWebhookDeleted(value);
|
|
956
|
+
if (value.id !== input.webhookId) throw new TypeError(WEBHOOK_SCOPE_MISMATCH);
|
|
957
|
+
return value;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
public async testProjectDatabaseWebhook(
|
|
961
|
+
input: Parameters<ProductApi["testProjectDatabaseWebhook"]>[0],
|
|
962
|
+
): Promise<DatabaseWebhookTestResult> {
|
|
963
|
+
const value = await testProjectDatabaseWebhook(
|
|
964
|
+
{
|
|
965
|
+
project_id: input.projectId,
|
|
966
|
+
environment: input.environment,
|
|
967
|
+
webhook_id: input.webhookId,
|
|
968
|
+
},
|
|
969
|
+
{ client: this.#client },
|
|
970
|
+
);
|
|
971
|
+
assertDatabaseWebhookTest(value);
|
|
972
|
+
return value;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
public async listProjectDatabaseWebhookDeliveries(
|
|
976
|
+
input: Parameters<ProductApi["listProjectDatabaseWebhookDeliveries"]>[0],
|
|
977
|
+
): Promise<DatabaseWebhookDeliveryPage> {
|
|
978
|
+
const value = await listProjectDatabaseWebhookDeliveries(
|
|
979
|
+
{
|
|
980
|
+
project_id: input.projectId,
|
|
981
|
+
environment: input.environment,
|
|
982
|
+
...(input.webhookId === undefined ? {} : { webhook_id: input.webhookId }),
|
|
983
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
984
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
985
|
+
},
|
|
986
|
+
{ client: this.#client },
|
|
987
|
+
);
|
|
988
|
+
assertDatabaseWebhookDeliveryPage(value);
|
|
989
|
+
if (
|
|
990
|
+
input.webhookId !== undefined &&
|
|
991
|
+
value.items.some((item) => item.webhook_id !== input.webhookId)
|
|
992
|
+
)
|
|
993
|
+
throw new TypeError(WEBHOOK_SCOPE_MISMATCH);
|
|
994
|
+
return value;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
public async replayProjectDatabaseWebhookDelivery(
|
|
998
|
+
input: Parameters<ProductApi["replayProjectDatabaseWebhookDelivery"]>[0],
|
|
999
|
+
): Promise<DatabaseWebhookReplayAccepted> {
|
|
1000
|
+
const value = await replayProjectDatabaseWebhookDelivery(
|
|
1001
|
+
{
|
|
1002
|
+
project_id: input.projectId,
|
|
1003
|
+
environment: input.environment,
|
|
1004
|
+
delivery_id: input.deliveryId,
|
|
1005
|
+
},
|
|
1006
|
+
{ client: this.#client },
|
|
1007
|
+
);
|
|
1008
|
+
assertDatabaseWebhookReplayAccepted(value);
|
|
1009
|
+
if (value.delivery_id !== input.deliveryId) throw new TypeError(WEBHOOK_SCOPE_MISMATCH);
|
|
1010
|
+
return value;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/** A schedule runs the statements as the application's runtime role; the request confirms it. */
|
|
1014
|
+
public async setProjectDatabaseSchedule(
|
|
1015
|
+
input: Parameters<ProductApi["setProjectDatabaseSchedule"]>[0],
|
|
1016
|
+
): Promise<DatabaseSchedule> {
|
|
1017
|
+
const request = input.request;
|
|
1018
|
+
const value = await setProjectDatabaseSchedule(
|
|
1019
|
+
{
|
|
1020
|
+
project_id: input.projectId,
|
|
1021
|
+
environment: input.environment,
|
|
1022
|
+
name: input.name,
|
|
1023
|
+
databaseScheduleSetRequest: {
|
|
1024
|
+
...(request.cron === null ? {} : { cron: request.cron }),
|
|
1025
|
+
...(request.every_seconds === null ? {} : { every_seconds: request.every_seconds }),
|
|
1026
|
+
statements: [...request.statements],
|
|
1027
|
+
timeout_ms: request.timeout_ms,
|
|
1028
|
+
enabled: request.enabled,
|
|
1029
|
+
confirm: true,
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
{ client: this.#client },
|
|
1033
|
+
);
|
|
1034
|
+
assertDatabaseSchedule(value);
|
|
1035
|
+
if (value.name !== input.name || value.environment !== input.environment)
|
|
1036
|
+
throw new TypeError(SCHEDULE_SCOPE_MISMATCH);
|
|
1037
|
+
return value;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
public async listProjectDatabaseSchedules(
|
|
1041
|
+
input: Parameters<ProductApi["listProjectDatabaseSchedules"]>[0],
|
|
1042
|
+
): Promise<DatabaseSchedulePage> {
|
|
1043
|
+
const value = await listProjectDatabaseSchedules(
|
|
1044
|
+
{ project_id: input.projectId, environment: input.environment },
|
|
1045
|
+
{ client: this.#client },
|
|
1046
|
+
);
|
|
1047
|
+
assertDatabaseSchedulePage(value);
|
|
1048
|
+
if (value.items.some((item) => item.environment !== input.environment))
|
|
1049
|
+
throw new TypeError(SCHEDULE_SCOPE_MISMATCH);
|
|
1050
|
+
return value;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
public async getProjectDatabaseSchedule(
|
|
1054
|
+
input: Parameters<ProductApi["getProjectDatabaseSchedule"]>[0],
|
|
1055
|
+
): Promise<DatabaseSchedule> {
|
|
1056
|
+
const value = await getProjectDatabaseSchedule(
|
|
1057
|
+
{ project_id: input.projectId, environment: input.environment, name: input.name },
|
|
1058
|
+
{ client: this.#client },
|
|
1059
|
+
);
|
|
1060
|
+
assertDatabaseSchedule(value);
|
|
1061
|
+
if (value.name !== input.name || value.environment !== input.environment)
|
|
1062
|
+
throw new TypeError(SCHEDULE_SCOPE_MISMATCH);
|
|
1063
|
+
return value;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
public async deleteProjectDatabaseSchedule(
|
|
1067
|
+
input: Parameters<ProductApi["deleteProjectDatabaseSchedule"]>[0],
|
|
1068
|
+
): Promise<DatabaseScheduleDeleted> {
|
|
1069
|
+
const value = await deleteProjectDatabaseSchedule(
|
|
1070
|
+
{ project_id: input.projectId, environment: input.environment, name: input.name },
|
|
1071
|
+
{ client: this.#client },
|
|
1072
|
+
);
|
|
1073
|
+
assertDatabaseScheduleDeleted(value);
|
|
1074
|
+
if (value.name !== input.name) throw new TypeError(SCHEDULE_SCOPE_MISMATCH);
|
|
1075
|
+
return value;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
public async listProjectDatabaseScheduleRuns(
|
|
1079
|
+
input: Parameters<ProductApi["listProjectDatabaseScheduleRuns"]>[0],
|
|
1080
|
+
): Promise<DatabaseScheduleRunPage> {
|
|
1081
|
+
const value = await listProjectDatabaseScheduleRuns(
|
|
1082
|
+
{
|
|
1083
|
+
project_id: input.projectId,
|
|
1084
|
+
environment: input.environment,
|
|
1085
|
+
name: input.name,
|
|
1086
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1087
|
+
},
|
|
1088
|
+
{ client: this.#client },
|
|
1089
|
+
);
|
|
1090
|
+
assertDatabaseScheduleRunPage(value);
|
|
1091
|
+
if (value.items.some((item) => item.schedule !== input.name))
|
|
1092
|
+
throw new TypeError(SCHEDULE_SCOPE_MISMATCH);
|
|
1093
|
+
return value;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
public async runProjectDatabaseSchedule(
|
|
1097
|
+
input: Parameters<ProductApi["runProjectDatabaseSchedule"]>[0],
|
|
1098
|
+
): Promise<DatabaseScheduleRunAccepted> {
|
|
1099
|
+
const value = await runProjectDatabaseSchedule(
|
|
1100
|
+
{ project_id: input.projectId, environment: input.environment, name: input.name },
|
|
1101
|
+
{ client: this.#client },
|
|
1102
|
+
);
|
|
1103
|
+
assertDatabaseScheduleRunAccepted(value);
|
|
1104
|
+
if (value.schedule !== input.name) throw new TypeError(SCHEDULE_SCOPE_MISMATCH);
|
|
1105
|
+
return value;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
public async getProjectDatabaseExecutionUsage(
|
|
1109
|
+
input: Parameters<ProductApi["getProjectDatabaseExecutionUsage"]>[0],
|
|
1110
|
+
): Promise<DatabaseExecutionUsagePage> {
|
|
1111
|
+
const value = await getProjectDatabaseExecutionUsage(
|
|
1112
|
+
{
|
|
1113
|
+
project_id: input.projectId,
|
|
1114
|
+
environment: input.environment,
|
|
1115
|
+
from: input.from,
|
|
1116
|
+
to: input.to,
|
|
1117
|
+
granularity: input.granularity,
|
|
1118
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1119
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1120
|
+
},
|
|
1121
|
+
{ client: this.#client },
|
|
1122
|
+
);
|
|
1123
|
+
assertDatabaseExecutionUsagePage(value);
|
|
1124
|
+
return value;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
764
1127
|
public async planPaidDomain(input: {
|
|
765
1128
|
readonly projectId: string;
|
|
766
1129
|
readonly hostname: string;
|
|
@@ -9,21 +9,17 @@ export const runLocalPsql = async (input: {
|
|
|
9
9
|
readonly user: string;
|
|
10
10
|
readonly password: string;
|
|
11
11
|
readonly database: string;
|
|
12
|
-
readonly websocketUrl
|
|
12
|
+
readonly websocketUrl: string;
|
|
13
13
|
readonly signal: AbortSignal;
|
|
14
14
|
}): Promise<number> => {
|
|
15
15
|
input.signal.throwIfAborted();
|
|
16
|
-
if (input.websocketUrl !==
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
input.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
database: input.database,
|
|
24
|
-
user: input.user,
|
|
25
|
-
port: 0,
|
|
26
|
-
});
|
|
16
|
+
if (input.websocketUrl !== `wss://${input.host}/`) throw new Error("Invalid PGCF transport");
|
|
17
|
+
const bridge = await startBridge({
|
|
18
|
+
endpoint: input.websocketUrl,
|
|
19
|
+
database: input.database,
|
|
20
|
+
user: input.user,
|
|
21
|
+
port: 0,
|
|
22
|
+
});
|
|
27
23
|
try {
|
|
28
24
|
input.signal.throwIfAborted();
|
|
29
25
|
// A caller's libpq service/hostaddr settings must not redirect this temporary credential.
|
|
@@ -31,26 +27,18 @@ export const runLocalPsql = async (input: {
|
|
|
31
27
|
Object.entries(process.env).filter(([key]) => !/^PG[A-Z_]+$/iu.test(key)),
|
|
32
28
|
);
|
|
33
29
|
return await new Promise<number>((resolve, reject) => {
|
|
34
|
-
const child = spawn(
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
...(bridge
|
|
45
|
-
? {
|
|
46
|
-
PGPORT: String(bridge.port),
|
|
47
|
-
PGCHANNELBINDING: "disable",
|
|
48
|
-
PGGSSENCMODE: "disable",
|
|
49
|
-
}
|
|
50
|
-
: {}),
|
|
51
|
-
},
|
|
30
|
+
const child = spawn("psql", ["-h", bridge.host, "-U", input.user, "-d", input.database], {
|
|
31
|
+
stdio: "inherit",
|
|
32
|
+
signal: input.signal,
|
|
33
|
+
env: {
|
|
34
|
+
...environment,
|
|
35
|
+
PGPASSWORD: input.password,
|
|
36
|
+
PGSSLMODE: "disable",
|
|
37
|
+
PGPORT: String(bridge.port),
|
|
38
|
+
PGCHANNELBINDING: "disable",
|
|
39
|
+
PGGSSENCMODE: "disable",
|
|
52
40
|
},
|
|
53
|
-
);
|
|
41
|
+
});
|
|
54
42
|
child.once("error", (error: NodeJS.ErrnoException) => {
|
|
55
43
|
reject(error.code === "ENOENT" ? new PsqlUnavailableError() : error);
|
|
56
44
|
});
|
|
@@ -59,6 +47,6 @@ export const runLocalPsql = async (input: {
|
|
|
59
47
|
});
|
|
60
48
|
});
|
|
61
49
|
} finally {
|
|
62
|
-
await bridge
|
|
50
|
+
await bridge.close();
|
|
63
51
|
}
|
|
64
52
|
};
|
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
ProjectCreditBudget,
|
|
24
24
|
} from "@ohmyhost/sdk-ts";
|
|
25
25
|
import { isSaasCnameTarget, type SaasCnameTarget } from "@ohmyhost/contracts/platform-origins";
|
|
26
|
-
import { assertPublishedCreditRateCards } from "@ohmyhost/contracts/credit-pricing";
|
|
27
26
|
import {
|
|
28
27
|
publicOperationFailure,
|
|
29
28
|
publicOperationReconciliation,
|
|
@@ -554,14 +553,7 @@ export function parseOrganizationCredits(value: unknown): OrganizationCredits {
|
|
|
554
553
|
"reserved_micros",
|
|
555
554
|
"grace_started_at",
|
|
556
555
|
"grace_expires_at",
|
|
557
|
-
"active_meters",
|
|
558
|
-
"rate_cards",
|
|
559
556
|
]);
|
|
560
|
-
try {
|
|
561
|
-
assertPublishedCreditRateCards(input["rate_cards"]);
|
|
562
|
-
} catch {
|
|
563
|
-
throw new ResponseContractError();
|
|
564
|
-
}
|
|
565
557
|
const graceStart = input["grace_started_at"],
|
|
566
558
|
graceEnd = input["grace_expires_at"];
|
|
567
559
|
if (
|
|
@@ -576,11 +568,7 @@ export function parseOrganizationCredits(value: unknown): OrganizationCredits {
|
|
|
576
568
|
if (
|
|
577
569
|
!isUlid(input["organization_id"]) ||
|
|
578
570
|
input["unit"] !== "microcredits" ||
|
|
579
|
-
!isIsoInstant(input["as_of"])
|
|
580
|
-
!Array.isArray(input["active_meters"]) ||
|
|
581
|
-
input["active_meters"].some(
|
|
582
|
-
(m: unknown) => typeof m !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(m),
|
|
583
|
-
)
|
|
571
|
+
!isIsoInstant(input["as_of"])
|
|
584
572
|
)
|
|
585
573
|
throw new ResponseContractError();
|
|
586
574
|
for (const field of ["available_micros", "reserved_micros"]) {
|
|
@@ -127,6 +127,11 @@
|
|
|
127
127
|
"types": "./dist/database-access.d.ts",
|
|
128
128
|
"import": "./dist/database-access.js"
|
|
129
129
|
},
|
|
130
|
+
"./database-events": {
|
|
131
|
+
"source": "./src/database-events.ts",
|
|
132
|
+
"types": "./dist/database-events.d.ts",
|
|
133
|
+
"import": "./dist/database-events.js"
|
|
134
|
+
},
|
|
130
135
|
"./functions": {
|
|
131
136
|
"source": "./src/functions.ts",
|
|
132
137
|
"types": "./dist/functions.d.ts",
|
|
@@ -116,96 +116,6 @@ export function assertOrganizationCreditUsage(
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
export interface PublishedCreditRateCard {
|
|
120
|
-
readonly id: string;
|
|
121
|
-
readonly currency: "USD";
|
|
122
|
-
readonly published_at: string;
|
|
123
|
-
readonly effective_from: string;
|
|
124
|
-
readonly rates: readonly {
|
|
125
|
-
readonly meter: string;
|
|
126
|
-
readonly unit: string;
|
|
127
|
-
readonly units_per_charge: string;
|
|
128
|
-
readonly provider_cost_micros: string | null;
|
|
129
|
-
readonly credit_micros: string;
|
|
130
|
-
}[];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Shared wire validation only; clients never recompute accounting decisions. */
|
|
134
|
-
export function assertPublishedCreditRateCards(
|
|
135
|
-
value: unknown,
|
|
136
|
-
): asserts value is readonly PublishedCreditRateCard[] {
|
|
137
|
-
if (!Array.isArray(value) || value.length > 4) throw new TypeError("Invalid published prices");
|
|
138
|
-
const ids = new Set<string>();
|
|
139
|
-
for (const input of value) {
|
|
140
|
-
if (
|
|
141
|
-
!input ||
|
|
142
|
-
typeof input !== "object" ||
|
|
143
|
-
Array.isArray(input) ||
|
|
144
|
-
Object.keys(input).sort().join() !== "currency,effective_from,id,published_at,rates"
|
|
145
|
-
)
|
|
146
|
-
throw new TypeError("Invalid published price fields");
|
|
147
|
-
const card = input as Record<string, unknown>;
|
|
148
|
-
if (
|
|
149
|
-
typeof card["id"] !== "string" ||
|
|
150
|
-
!/^[a-z0-9_.-]{1,128}$/u.test(card["id"]) ||
|
|
151
|
-
ids.has(card["id"]) ||
|
|
152
|
-
card["currency"] !== "USD"
|
|
153
|
-
)
|
|
154
|
-
throw new TypeError("Invalid price identity");
|
|
155
|
-
ids.add(card["id"]);
|
|
156
|
-
for (const key of ["published_at", "effective_from"]) {
|
|
157
|
-
const date = card[key];
|
|
158
|
-
if (
|
|
159
|
-
typeof date !== "string" ||
|
|
160
|
-
!Number.isFinite(Date.parse(date)) ||
|
|
161
|
-
new Date(date).toISOString() !== date
|
|
162
|
-
)
|
|
163
|
-
throw new TypeError("Invalid price dates");
|
|
164
|
-
}
|
|
165
|
-
if (
|
|
166
|
-
String(card["published_at"]) > String(card["effective_from"]) ||
|
|
167
|
-
!Array.isArray(card["rates"]) ||
|
|
168
|
-
card["rates"].length < 1 ||
|
|
169
|
-
card["rates"].length > 32
|
|
170
|
-
)
|
|
171
|
-
throw new TypeError("Invalid price publication");
|
|
172
|
-
const meters = new Set<string>();
|
|
173
|
-
for (const item of card["rates"]) {
|
|
174
|
-
if (
|
|
175
|
-
!item ||
|
|
176
|
-
typeof item !== "object" ||
|
|
177
|
-
Array.isArray(item) ||
|
|
178
|
-
Object.keys(item).sort().join() !==
|
|
179
|
-
"credit_micros,meter,provider_cost_micros,unit,units_per_charge"
|
|
180
|
-
)
|
|
181
|
-
throw new TypeError("Invalid rate fields");
|
|
182
|
-
const rate = item as Record<string, unknown>;
|
|
183
|
-
for (const key of ["meter", "unit"])
|
|
184
|
-
if (typeof rate[key] !== "string" || !/^[a-z0-9_.-]{1,128}$/u.test(String(rate[key])))
|
|
185
|
-
throw new TypeError("Invalid rate unit");
|
|
186
|
-
const identity = String(rate["meter"]) + "/" + String(rate["unit"]);
|
|
187
|
-
if (meters.has(identity)) throw new TypeError("Duplicate rate");
|
|
188
|
-
meters.add(identity);
|
|
189
|
-
for (const key of ["units_per_charge", "provider_cost_micros", "credit_micros"]) {
|
|
190
|
-
const amount = rate[key];
|
|
191
|
-
if (
|
|
192
|
-
key === "provider_cost_micros" &&
|
|
193
|
-
amount === null &&
|
|
194
|
-
rate["meter"] === "pgcf.memory.active"
|
|
195
|
-
)
|
|
196
|
-
continue;
|
|
197
|
-
if (
|
|
198
|
-
typeof amount !== "string" ||
|
|
199
|
-
!/^(0|[1-9][0-9]{0,18})$/u.test(amount) ||
|
|
200
|
-
BigInt(amount) > 9223372036854775807n ||
|
|
201
|
-
(key === "units_per_charge" && amount === "0")
|
|
202
|
-
)
|
|
203
|
-
throw new TypeError("Invalid rate amount");
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
119
|
/** Effective feature access and the credit account: the monthly and one-time credit and what the open reservations hold. */
|
|
210
120
|
export function assertOrganizationAccount(value: unknown): void {
|
|
211
121
|
if (
|