neon 2.1.2 → 2.29.0
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/LICENSE.md +178 -0
- package/README.md +601 -0
- package/dist/analytics.js +156 -0
- package/dist/api.js +665 -0
- package/dist/auth.js +127 -0
- package/dist/callback.html +51 -0
- package/dist/cli.js +9 -0
- package/dist/commands/auth.js +214 -0
- package/dist/commands/bootstrap.js +481 -0
- package/dist/commands/branches.js +481 -0
- package/dist/commands/bucket.js +543 -0
- package/dist/commands/checkout.js +289 -0
- package/dist/commands/config.js +544 -0
- package/dist/commands/connection_string.js +172 -0
- package/dist/commands/data_api.js +285 -0
- package/dist/commands/databases.js +82 -0
- package/dist/commands/deploy.js +26 -0
- package/dist/commands/dev.js +698 -0
- package/dist/commands/env.js +166 -0
- package/dist/commands/functions.js +373 -0
- package/dist/commands/index.js +54 -0
- package/dist/commands/init.js +73 -0
- package/dist/commands/ip_allow.js +137 -0
- package/dist/commands/link.js +1121 -0
- package/dist/commands/neon_auth.js +1028 -0
- package/dist/commands/operations.js +28 -0
- package/dist/commands/orgs.js +24 -0
- package/dist/commands/projects.js +372 -0
- package/dist/commands/psql.js +62 -0
- package/dist/commands/roles.js +65 -0
- package/dist/commands/schema_diff.js +151 -0
- package/dist/commands/set_context.js +29 -0
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +15 -0
- package/dist/commands/vpc_endpoints.js +134 -0
- package/dist/config.js +11 -0
- package/dist/config_format.js +72 -0
- package/dist/context.js +177 -0
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +240 -0
- package/dist/dev/functions.js +70 -0
- package/dist/dev/inputs.js +63 -0
- package/dist/dev/runtime.js +146 -0
- package/dist/env.js +36 -0
- package/dist/env_file.js +159 -0
- package/dist/errors.js +80 -0
- package/dist/functions_api.js +44 -0
- package/dist/help.js +146 -0
- package/dist/index.js +234 -0
- package/dist/log.js +18 -0
- package/dist/parameters.gen.js +480 -0
- package/dist/pkg.js +25 -0
- package/dist/psql/cli.js +53 -0
- package/dist/psql/command/cmd_cond.js +437 -0
- package/dist/psql/command/cmd_connect.js +820 -0
- package/dist/psql/command/cmd_copy.js +1035 -0
- package/dist/psql/command/cmd_describe.js +1815 -0
- package/dist/psql/command/cmd_format.js +922 -0
- package/dist/psql/command/cmd_io.js +2193 -0
- package/dist/psql/command/cmd_lo.js +393 -0
- package/dist/psql/command/cmd_meta.js +970 -0
- package/dist/psql/command/cmd_misc.js +187 -0
- package/dist/psql/command/cmd_pipeline.js +1148 -0
- package/dist/psql/command/cmd_restrict.js +171 -0
- package/dist/psql/command/cmd_show.js +766 -0
- package/dist/psql/command/dispatch.js +343 -0
- package/dist/psql/command/inputQueue.js +42 -0
- package/dist/psql/command/shared.js +71 -0
- package/dist/psql/complete/filenames.js +139 -0
- package/dist/psql/complete/index.js +104 -0
- package/dist/psql/complete/matcher.js +315 -0
- package/dist/psql/complete/psqlVars.js +247 -0
- package/dist/psql/complete/queries.js +493 -0
- package/dist/psql/complete/rules.js +2424 -0
- package/dist/psql/core/common.js +1253 -0
- package/dist/psql/core/help.js +576 -0
- package/dist/psql/core/mainloop.js +1360 -0
- package/dist/psql/core/prompt.js +440 -0
- package/dist/psql/core/settings.js +684 -0
- package/dist/psql/core/sqlHelp.js +1066 -0
- package/dist/psql/core/startup.js +846 -0
- package/dist/psql/core/syncVars.js +116 -0
- package/dist/psql/core/variables.js +287 -0
- package/dist/psql/describe/formatters.js +1290 -0
- package/dist/psql/describe/processNamePattern.js +270 -0
- package/dist/psql/describe/queries.js +2378 -0
- package/dist/psql/describe/versionGate.js +43 -0
- package/dist/psql/index.js +2030 -0
- package/dist/psql/io/history.js +299 -0
- package/dist/psql/io/input.js +120 -0
- package/dist/psql/io/lineEditor/buffer.js +325 -0
- package/dist/psql/io/lineEditor/complete.js +227 -0
- package/dist/psql/io/lineEditor/filename.js +159 -0
- package/dist/psql/io/lineEditor/index.js +894 -0
- package/dist/psql/io/lineEditor/keymap.js +745 -0
- package/dist/psql/io/lineEditor/vt100.js +363 -0
- package/dist/psql/io/pgpass.js +202 -0
- package/dist/psql/io/pgservice.js +194 -0
- package/dist/psql/io/psqlrc.js +422 -0
- package/dist/psql/print/aligned.js +1765 -0
- package/dist/psql/print/asciidoc.js +248 -0
- package/dist/psql/print/crosstab.js +463 -0
- package/dist/psql/print/csv.js +95 -0
- package/dist/psql/print/html.js +258 -0
- package/dist/psql/print/json.js +96 -0
- package/dist/psql/print/latex.js +396 -0
- package/dist/psql/print/pager.js +267 -0
- package/dist/psql/print/troff.js +258 -0
- package/dist/psql/print/unaligned.js +119 -0
- package/dist/psql/print/units.js +135 -0
- package/dist/psql/scanner/slash.js +515 -0
- package/dist/psql/scanner/sql.js +914 -0
- package/dist/psql/scanner/stringutils.js +394 -0
- package/dist/psql/types/backslash.js +1 -0
- package/dist/psql/types/connection.js +1 -0
- package/dist/psql/types/index.js +7 -0
- package/dist/psql/types/printer.js +1 -0
- package/dist/psql/types/repl.js +1 -0
- package/dist/psql/types/scanner.js +24 -0
- package/dist/psql/types/settings.js +1 -0
- package/dist/psql/types/variables.js +1 -0
- package/dist/psql/wire/connection.js +2858 -0
- package/dist/psql/wire/copy.js +108 -0
- package/dist/psql/wire/notify.js +59 -0
- package/dist/psql/wire/pipeline.js +521 -0
- package/dist/psql/wire/protocol.js +466 -0
- package/dist/psql/wire/sasl.js +296 -0
- package/dist/psql/wire/tls.js +602 -0
- package/dist/storage_api.js +147 -0
- package/dist/test_utils/fixtures.js +122 -0
- package/dist/test_utils/oauth_server.js +9 -0
- package/dist/types.js +1 -0
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/auth.js +5 -0
- package/dist/utils/branch_notice.js +22 -0
- package/dist/utils/branch_picker.js +103 -0
- package/dist/utils/compute_units.js +28 -0
- package/dist/utils/enrichers.js +161 -0
- package/dist/utils/esbuild.js +158 -0
- package/dist/utils/formats.js +18 -0
- package/dist/utils/middlewares.js +20 -0
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +56 -0
- package/dist/utils/psql.js +120 -0
- package/dist/utils/string.js +5 -0
- package/dist/utils/ui.js +59 -0
- package/dist/utils/zip.js +4 -0
- package/dist/writer.js +97 -0
- package/package.json +117 -14
- package/.jshintrc +0 -3
- package/CHANGELOG.markdown +0 -22
- package/LICENSE +0 -21
- package/README.markdown +0 -76
- package/bower.json +0 -23
- package/license.txt +0 -9
- package/neon.js +0 -234
- package/stdlib/bubbling_support.js +0 -32
- package/stdlib/custom_event.js +0 -54
- package/stdlib/custom_event_support.js +0 -195
- package/stdlib/index.js +0 -7
- package/stdlib/node_support.js +0 -123
- package/stdlib/widget.js +0 -340
- package/test/neon_browser.html +0 -11
- package/test/neon_stdlib_browser.html +0 -15
- package/test/neon_stdlib_test.js +0 -84
- package/test/neon_test.js +0 -64
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { EndpointType } from "../utils/api_enums.js";
|
|
2
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
3
|
+
import { parsePITBranch } from "../utils/point_in_time.js";
|
|
4
|
+
import { psql } from "../utils/psql.js";
|
|
5
|
+
import { writer } from "../writer.js";
|
|
6
|
+
export const SSL_MODES = [
|
|
7
|
+
"require",
|
|
8
|
+
"verify-ca",
|
|
9
|
+
"verify-full",
|
|
10
|
+
"omit",
|
|
11
|
+
];
|
|
12
|
+
export const command = "connection-string [branch]";
|
|
13
|
+
export const aliases = ["cs"];
|
|
14
|
+
export const describe = "Get connection string";
|
|
15
|
+
export const builder = (argv) => {
|
|
16
|
+
return argv
|
|
17
|
+
.usage("$0 connection-string [branch] [options]")
|
|
18
|
+
.example("$0 cs main", "Get connection string for the main branch")
|
|
19
|
+
.example("$0 cs main@2024-01-01T00:00:00Z", "Get connection string for the main branch at a specific point in time")
|
|
20
|
+
.example("$0 cs main@0/234235", "Get connection string for the main branch at a specific LSN")
|
|
21
|
+
.positional("branch", {
|
|
22
|
+
describe: `Branch name or id. Defaults to the default branch if omitted. Can be written in the point-in-time format: "branch@timestamp" or "branch@lsn"`,
|
|
23
|
+
type: "string",
|
|
24
|
+
})
|
|
25
|
+
.options({
|
|
26
|
+
"project-id": {
|
|
27
|
+
type: "string",
|
|
28
|
+
describe: "Project ID",
|
|
29
|
+
},
|
|
30
|
+
"role-name": {
|
|
31
|
+
type: "string",
|
|
32
|
+
describe: "Role name",
|
|
33
|
+
},
|
|
34
|
+
"database-name": {
|
|
35
|
+
type: "string",
|
|
36
|
+
describe: "Database name",
|
|
37
|
+
},
|
|
38
|
+
pooled: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
describe: "Use pooled connection",
|
|
41
|
+
default: false,
|
|
42
|
+
},
|
|
43
|
+
prisma: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
describe: "Use connection string for Prisma setup",
|
|
46
|
+
default: false,
|
|
47
|
+
},
|
|
48
|
+
"endpoint-type": {
|
|
49
|
+
type: "string",
|
|
50
|
+
choices: Object.values(EndpointType),
|
|
51
|
+
describe: "Endpoint type",
|
|
52
|
+
},
|
|
53
|
+
extended: {
|
|
54
|
+
type: "boolean",
|
|
55
|
+
describe: "Show extended information",
|
|
56
|
+
},
|
|
57
|
+
psql: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
describe: "Connect to a database via psql using connection string",
|
|
60
|
+
default: false,
|
|
61
|
+
},
|
|
62
|
+
fallback: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
describe: "Force the embedded TypeScript psql fallback (for testing)",
|
|
65
|
+
default: false,
|
|
66
|
+
hidden: true,
|
|
67
|
+
},
|
|
68
|
+
ssl: {
|
|
69
|
+
type: "string",
|
|
70
|
+
choices: SSL_MODES,
|
|
71
|
+
default: "require",
|
|
72
|
+
describe: "SSL mode",
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
.middleware(fillSingleProject);
|
|
76
|
+
};
|
|
77
|
+
export const handler = async (props) => {
|
|
78
|
+
const projectId = props.projectId;
|
|
79
|
+
const parsedPIT = props.branch
|
|
80
|
+
? parsePITBranch(props.branch)
|
|
81
|
+
: { tag: "head", branch: "" };
|
|
82
|
+
if (props.branch) {
|
|
83
|
+
props.branch = parsedPIT.branch;
|
|
84
|
+
}
|
|
85
|
+
const branchId = await branchIdFromProps(props);
|
|
86
|
+
const { data: { endpoints }, } = await props.apiClient.listProjectBranchEndpoints(projectId, branchId);
|
|
87
|
+
const matchEndpointType = props.endpointType ?? EndpointType.ReadWrite;
|
|
88
|
+
let endpoint = endpoints.find((e) => e.type === matchEndpointType);
|
|
89
|
+
if (!endpoint && props.endpointType == null) {
|
|
90
|
+
endpoint = endpoints[0];
|
|
91
|
+
}
|
|
92
|
+
if (!endpoint) {
|
|
93
|
+
throw new Error(`No ${props.endpointType ?? ""} endpoint found for the branch: ${branchId}`);
|
|
94
|
+
}
|
|
95
|
+
const role = props.roleName ||
|
|
96
|
+
(await props.apiClient
|
|
97
|
+
.listProjectBranchRoles(projectId, branchId)
|
|
98
|
+
.then(({ data }) => {
|
|
99
|
+
if (data.roles.length === 0) {
|
|
100
|
+
throw new Error(`No roles found for the branch: ${branchId}`);
|
|
101
|
+
}
|
|
102
|
+
if (data.roles.length === 1) {
|
|
103
|
+
return data.roles[0].name;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Multiple roles found for the branch, please provide one with the --role-name option: ${data.roles
|
|
106
|
+
.map((r) => r.name)
|
|
107
|
+
.join(", ")}`);
|
|
108
|
+
}));
|
|
109
|
+
const { data: { databases: branchDatabases }, } = await props.apiClient.listProjectBranchDatabases(projectId, branchId);
|
|
110
|
+
const database = props.databaseName ||
|
|
111
|
+
(() => {
|
|
112
|
+
if (branchDatabases.length === 0) {
|
|
113
|
+
throw new Error(`No databases found for the branch: ${branchId}`);
|
|
114
|
+
}
|
|
115
|
+
if (branchDatabases.length === 1) {
|
|
116
|
+
return branchDatabases[0].name;
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Multiple databases found for the branch, please provide one with the --database-name option: ${branchDatabases
|
|
119
|
+
.map((d) => d.name)
|
|
120
|
+
.join(", ")}`);
|
|
121
|
+
})();
|
|
122
|
+
if (!branchDatabases.find((d) => d.name === database)) {
|
|
123
|
+
throw new Error(`Database not found: ${database}`);
|
|
124
|
+
}
|
|
125
|
+
const { data: { password }, } = await props.apiClient.getProjectBranchRolePassword(props.projectId, endpoint.branch_id, role);
|
|
126
|
+
let host = props.pooled
|
|
127
|
+
? endpoint.host.replace(endpoint.id, `${endpoint.id}-pooler`)
|
|
128
|
+
: endpoint.host;
|
|
129
|
+
if (parsedPIT.tag !== "head") {
|
|
130
|
+
host = endpoint.host.replace(endpoint.id, endpoint.branch_id);
|
|
131
|
+
}
|
|
132
|
+
const connectionString = new URL(`postgresql://${host}`);
|
|
133
|
+
connectionString.pathname = database;
|
|
134
|
+
connectionString.username = role;
|
|
135
|
+
connectionString.password = password;
|
|
136
|
+
if (props.prisma) {
|
|
137
|
+
connectionString.searchParams.set("connect_timeout", "30");
|
|
138
|
+
if (props.pooled) {
|
|
139
|
+
connectionString.searchParams.set("pool_timeout", "30");
|
|
140
|
+
connectionString.searchParams.set("pgbouncer", "true");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (props.ssl !== "omit") {
|
|
144
|
+
connectionString.searchParams.set("sslmode", props.ssl);
|
|
145
|
+
connectionString.searchParams.set("channel_binding", "require");
|
|
146
|
+
}
|
|
147
|
+
if (parsedPIT.tag === "lsn") {
|
|
148
|
+
connectionString.searchParams.set("options", `neon_lsn:${parsedPIT.lsn}`);
|
|
149
|
+
}
|
|
150
|
+
else if (parsedPIT.tag === "timestamp") {
|
|
151
|
+
connectionString.searchParams.set("options", `neon_timestamp:${parsedPIT.timestamp}`);
|
|
152
|
+
}
|
|
153
|
+
if (props.psql) {
|
|
154
|
+
const psqlArgs = props["--"];
|
|
155
|
+
await psql(connectionString.toString(), psqlArgs, {
|
|
156
|
+
mode: props.fallback ? "ts" : "auto",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else if (props.extended) {
|
|
160
|
+
writer(props).end({
|
|
161
|
+
connection_string: connectionString.toString(),
|
|
162
|
+
host,
|
|
163
|
+
role,
|
|
164
|
+
password,
|
|
165
|
+
database,
|
|
166
|
+
options: connectionString.searchParams.toString(),
|
|
167
|
+
}, { fields: ["host", "role", "password", "database"] });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
process.stdout.write(connectionString.toString() + "\n");
|
|
171
|
+
}
|
|
172
|
+
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { isNeonApiError, retryOnLock } from "../api.js";
|
|
2
|
+
import { log } from "../log.js";
|
|
3
|
+
import { branchIdFromProps, fillSingleProject, resolveSingleDatabase, } from "../utils/enrichers.js";
|
|
4
|
+
import { writer } from "../writer.js";
|
|
5
|
+
const SETTINGS_FIELDS = [
|
|
6
|
+
"db_aggregates_enabled",
|
|
7
|
+
"db_anon_role",
|
|
8
|
+
"db_extra_search_path",
|
|
9
|
+
"db_max_rows",
|
|
10
|
+
"db_schemas",
|
|
11
|
+
"jwt_role_claim_key",
|
|
12
|
+
"jwt_cache_max_lifetime",
|
|
13
|
+
"openapi_mode",
|
|
14
|
+
"server_cors_allowed_origins",
|
|
15
|
+
"server_timing_enabled",
|
|
16
|
+
];
|
|
17
|
+
const settingsFlags = {
|
|
18
|
+
"db-aggregates-enabled": {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
describe: "Enable aggregate functions in queries",
|
|
21
|
+
},
|
|
22
|
+
"db-anon-role": {
|
|
23
|
+
type: "string",
|
|
24
|
+
describe: "Database role used for anonymous (unauthenticated) requests",
|
|
25
|
+
},
|
|
26
|
+
"db-extra-search-path": {
|
|
27
|
+
type: "string",
|
|
28
|
+
describe: "Extra schemas appended to the search path",
|
|
29
|
+
},
|
|
30
|
+
"db-max-rows": {
|
|
31
|
+
type: "number",
|
|
32
|
+
describe: "Maximum number of rows returned by a single request",
|
|
33
|
+
},
|
|
34
|
+
"db-schemas": {
|
|
35
|
+
type: "string",
|
|
36
|
+
describe: "Comma-separated list of schemas exposed via the Data API",
|
|
37
|
+
},
|
|
38
|
+
"jwt-role-claim-key": {
|
|
39
|
+
type: "string",
|
|
40
|
+
describe: "JWT claim path used to extract the role",
|
|
41
|
+
},
|
|
42
|
+
"jwt-cache-max-lifetime": {
|
|
43
|
+
type: "number",
|
|
44
|
+
describe: "Maximum JWT cache lifetime in seconds",
|
|
45
|
+
},
|
|
46
|
+
"openapi-mode": {
|
|
47
|
+
type: "string",
|
|
48
|
+
choices: ["ignore-privileges", "disabled"],
|
|
49
|
+
describe: "OpenAPI mode",
|
|
50
|
+
},
|
|
51
|
+
"server-cors-allowed-origins": {
|
|
52
|
+
type: "string",
|
|
53
|
+
describe: "CORS allowed origins",
|
|
54
|
+
},
|
|
55
|
+
"server-timing-enabled": {
|
|
56
|
+
type: "boolean",
|
|
57
|
+
describe: "Enable Server-Timing response headers",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
export const command = "data-api";
|
|
61
|
+
export const describe = "Manage the Neon Data API for a database";
|
|
62
|
+
export const builder = (argv) => argv
|
|
63
|
+
.usage("$0 data-api <sub-command> [options]")
|
|
64
|
+
.options({
|
|
65
|
+
"project-id": {
|
|
66
|
+
describe: "Project ID",
|
|
67
|
+
type: "string",
|
|
68
|
+
},
|
|
69
|
+
branch: {
|
|
70
|
+
describe: "Branch ID or name",
|
|
71
|
+
type: "string",
|
|
72
|
+
},
|
|
73
|
+
database: {
|
|
74
|
+
describe: "Database name",
|
|
75
|
+
type: "string",
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
.middleware(fillSingleProject)
|
|
79
|
+
.command("create", "Provision the Neon Data API for a database", (yargs) => yargs.options({
|
|
80
|
+
"auth-provider": {
|
|
81
|
+
type: "string",
|
|
82
|
+
choices: ["neon_auth", "external"],
|
|
83
|
+
describe: "Authentication provider",
|
|
84
|
+
},
|
|
85
|
+
"jwks-url": {
|
|
86
|
+
type: "string",
|
|
87
|
+
describe: "URL that lists the JWKS (used with external auth)",
|
|
88
|
+
},
|
|
89
|
+
"provider-name": {
|
|
90
|
+
type: "string",
|
|
91
|
+
describe: "Name of the auth provider (e.g. Clerk, Stytch, Auth0)",
|
|
92
|
+
},
|
|
93
|
+
"jwt-audience": {
|
|
94
|
+
type: "string",
|
|
95
|
+
describe: "Expected JWT audience claim",
|
|
96
|
+
},
|
|
97
|
+
"add-default-grants": {
|
|
98
|
+
type: "boolean",
|
|
99
|
+
describe: "Grant all permissions on tables in the public schema to authenticated users",
|
|
100
|
+
},
|
|
101
|
+
"skip-auth-schema": {
|
|
102
|
+
type: "boolean",
|
|
103
|
+
describe: "Skip creating the auth schema and RLS functions",
|
|
104
|
+
},
|
|
105
|
+
...settingsFlags,
|
|
106
|
+
}), (args) => create(args))
|
|
107
|
+
.command("get", "Show the Neon Data API status and settings", (yargs) => yargs, (args) => get(args))
|
|
108
|
+
.command("update", "Update Neon Data API settings (merges with current settings by default)", (yargs) => yargs.options({
|
|
109
|
+
replace: {
|
|
110
|
+
type: "boolean",
|
|
111
|
+
default: false,
|
|
112
|
+
describe: "Replace settings with only the flags provided. Omitted settings revert to server defaults.",
|
|
113
|
+
},
|
|
114
|
+
...settingsFlags,
|
|
115
|
+
}), (args) => update(args))
|
|
116
|
+
.command("refresh-schema", "Refresh the Data API schema cache without changing settings", (yargs) => yargs, (args) => refreshSchema(args))
|
|
117
|
+
.command("delete", "Tear down the Neon Data API for a database", (yargs) => yargs, (args) => deleteDataApi(args));
|
|
118
|
+
export const handler = (args) => {
|
|
119
|
+
return args;
|
|
120
|
+
};
|
|
121
|
+
const TOP_LEVEL_CREATE_FIELDS = [
|
|
122
|
+
"auth_provider",
|
|
123
|
+
"jwks_url",
|
|
124
|
+
"provider_name",
|
|
125
|
+
"jwt_audience",
|
|
126
|
+
"add_default_grants",
|
|
127
|
+
"skip_auth_schema",
|
|
128
|
+
];
|
|
129
|
+
const argKey = (snake) => snake.replace(/_/g, "-");
|
|
130
|
+
const buildSettings = (args) => {
|
|
131
|
+
const settings = {};
|
|
132
|
+
for (const field of SETTINGS_FIELDS) {
|
|
133
|
+
const value = args[argKey(field)];
|
|
134
|
+
if (value === undefined)
|
|
135
|
+
continue;
|
|
136
|
+
if (field === "db_schemas" && typeof value === "string") {
|
|
137
|
+
settings[field] = value
|
|
138
|
+
.split(",")
|
|
139
|
+
.map((s) => s.trim())
|
|
140
|
+
.filter(Boolean);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
settings[field] = value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return Object.keys(settings).length > 0
|
|
147
|
+
? settings
|
|
148
|
+
: undefined;
|
|
149
|
+
};
|
|
150
|
+
const buildCreateBody = (args) => {
|
|
151
|
+
const body = {};
|
|
152
|
+
for (const field of TOP_LEVEL_CREATE_FIELDS) {
|
|
153
|
+
const value = args[argKey(field)];
|
|
154
|
+
if (value !== undefined)
|
|
155
|
+
body[field] = value;
|
|
156
|
+
}
|
|
157
|
+
const settings = buildSettings(args);
|
|
158
|
+
if (settings)
|
|
159
|
+
body.settings = settings;
|
|
160
|
+
return body;
|
|
161
|
+
};
|
|
162
|
+
const create = async (props) => {
|
|
163
|
+
const branchId = await branchIdFromProps(props);
|
|
164
|
+
const database = await resolveSingleDatabase({
|
|
165
|
+
apiClient: props.apiClient,
|
|
166
|
+
projectId: props.projectId,
|
|
167
|
+
branchId,
|
|
168
|
+
database: props.database,
|
|
169
|
+
});
|
|
170
|
+
const body = buildCreateBody(props);
|
|
171
|
+
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchDataApi(props.projectId, branchId, database, body));
|
|
172
|
+
writer(props).end(data, { fields: ["url"] });
|
|
173
|
+
};
|
|
174
|
+
const GET_FIELDS = ["url", "status", "db_schemas"];
|
|
175
|
+
const get = async (props) => {
|
|
176
|
+
const branchId = await branchIdFromProps(props);
|
|
177
|
+
const database = await resolveSingleDatabase({
|
|
178
|
+
apiClient: props.apiClient,
|
|
179
|
+
projectId: props.projectId,
|
|
180
|
+
branchId,
|
|
181
|
+
database: props.database,
|
|
182
|
+
});
|
|
183
|
+
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
184
|
+
// Drop available_schemas from json/yaml output (not part of the public surface).
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
186
|
+
const { available_schemas: _ignored, ...publicData } = data;
|
|
187
|
+
// For table output, flatten db_schemas onto the top-level for column rendering.
|
|
188
|
+
const tableRow = {
|
|
189
|
+
url: publicData.url,
|
|
190
|
+
status: publicData.status,
|
|
191
|
+
db_schemas: (publicData.settings?.db_schemas ?? []).join(", "),
|
|
192
|
+
};
|
|
193
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
194
|
+
writer(props).end(publicData, {
|
|
195
|
+
fields: ["url", "status", "settings"],
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
writer(props).end(tableRow, { fields: GET_FIELDS });
|
|
200
|
+
};
|
|
201
|
+
const update = async (props) => {
|
|
202
|
+
const branchId = await branchIdFromProps(props);
|
|
203
|
+
const database = await resolveSingleDatabase({
|
|
204
|
+
apiClient: props.apiClient,
|
|
205
|
+
projectId: props.projectId,
|
|
206
|
+
branchId,
|
|
207
|
+
database: props.database,
|
|
208
|
+
});
|
|
209
|
+
const userSettings = buildSettings(props);
|
|
210
|
+
if (!userSettings) {
|
|
211
|
+
throw new Error("No settings flags provided. Pass at least one setting flag to update, or use `data-api refresh-schema` to refresh the schema cache without changing settings.");
|
|
212
|
+
}
|
|
213
|
+
let settings;
|
|
214
|
+
if (props.replace) {
|
|
215
|
+
settings = userSettings;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
let current;
|
|
219
|
+
try {
|
|
220
|
+
const { data } = await props.apiClient.getProjectBranchDataApi(props.projectId, branchId, database);
|
|
221
|
+
current = data.settings ?? undefined;
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
225
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`neonctl data-api create\` first.`);
|
|
226
|
+
}
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
if (!current) {
|
|
230
|
+
throw new Error(`Could not read current Data API settings for ${database} on branch ${branchId}. Retry, or pass --replace to overwrite.`);
|
|
231
|
+
}
|
|
232
|
+
settings = { ...current, ...(userSettings ?? {}) };
|
|
233
|
+
}
|
|
234
|
+
const body = {};
|
|
235
|
+
if (settings)
|
|
236
|
+
body.settings = settings;
|
|
237
|
+
try {
|
|
238
|
+
await retryOnLock(() => props.apiClient.updateProjectBranchDataApi(props.projectId, branchId, database, body));
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
242
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`neonctl data-api create\` first.`);
|
|
243
|
+
}
|
|
244
|
+
throw err;
|
|
245
|
+
}
|
|
246
|
+
log.info(`Data API settings updated for ${database} on branch ${branchId}`);
|
|
247
|
+
};
|
|
248
|
+
const refreshSchema = async (props) => {
|
|
249
|
+
const branchId = await branchIdFromProps(props);
|
|
250
|
+
const database = await resolveSingleDatabase({
|
|
251
|
+
apiClient: props.apiClient,
|
|
252
|
+
projectId: props.projectId,
|
|
253
|
+
branchId,
|
|
254
|
+
database: props.database,
|
|
255
|
+
});
|
|
256
|
+
try {
|
|
257
|
+
await retryOnLock(() => props.apiClient.updateProjectBranchDataApi(props.projectId, branchId, database, {}));
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
261
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}. Run \`neonctl data-api create\` first.`);
|
|
262
|
+
}
|
|
263
|
+
throw err;
|
|
264
|
+
}
|
|
265
|
+
log.info(`Data API schema cache refreshed for ${database} on branch ${branchId}`);
|
|
266
|
+
};
|
|
267
|
+
const deleteDataApi = async (props) => {
|
|
268
|
+
const branchId = await branchIdFromProps(props);
|
|
269
|
+
const database = await resolveSingleDatabase({
|
|
270
|
+
apiClient: props.apiClient,
|
|
271
|
+
projectId: props.projectId,
|
|
272
|
+
branchId,
|
|
273
|
+
database: props.database,
|
|
274
|
+
});
|
|
275
|
+
try {
|
|
276
|
+
await retryOnLock(() => props.apiClient.deleteProjectBranchDataApi(props.projectId, branchId, database));
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
if (isNeonApiError(err) && err.status === 404) {
|
|
280
|
+
throw new Error(`Data API is not provisioned for ${database} on branch ${branchId}.`);
|
|
281
|
+
}
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
log.info(`Data API deleted for ${database} on branch ${branchId}`);
|
|
285
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { retryOnLock } from "../api.js";
|
|
2
|
+
import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
|
|
3
|
+
import { writer } from "../writer.js";
|
|
4
|
+
export const DATABASE_FIELDS = ["name", "owner_name", "created_at"];
|
|
5
|
+
export const command = "databases";
|
|
6
|
+
export const describe = "Manage databases";
|
|
7
|
+
export const aliases = ["database", "db"];
|
|
8
|
+
export const builder = (argv) => argv
|
|
9
|
+
.usage("$0 databases <sub-command> [options]")
|
|
10
|
+
.options({
|
|
11
|
+
"project-id": {
|
|
12
|
+
describe: "Project ID",
|
|
13
|
+
type: "string",
|
|
14
|
+
},
|
|
15
|
+
branch: {
|
|
16
|
+
describe: "Branch ID or name",
|
|
17
|
+
type: "string",
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
.middleware(fillSingleProject)
|
|
21
|
+
.command("list", "List databases", (yargs) => yargs, (args) => list(args))
|
|
22
|
+
.command("create", "Create a database", (yargs) => yargs.options({
|
|
23
|
+
name: {
|
|
24
|
+
describe: "Database name",
|
|
25
|
+
type: "string",
|
|
26
|
+
demandOption: true,
|
|
27
|
+
},
|
|
28
|
+
"owner-name": {
|
|
29
|
+
describe: "Owner name",
|
|
30
|
+
type: "string",
|
|
31
|
+
},
|
|
32
|
+
}), (args) => create(args))
|
|
33
|
+
.command("delete <database>", "Delete a database", (yargs) => yargs, (args) => deleteDb(args));
|
|
34
|
+
export const handler = (args) => {
|
|
35
|
+
return args;
|
|
36
|
+
};
|
|
37
|
+
export const list = async (props) => {
|
|
38
|
+
const branchId = await branchIdFromProps(props);
|
|
39
|
+
const { data } = await props.apiClient.listProjectBranchDatabases(props.projectId, branchId);
|
|
40
|
+
writer(props).end(data.databases, {
|
|
41
|
+
fields: DATABASE_FIELDS,
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
export const create = async (props) => {
|
|
45
|
+
const branchId = await branchIdFromProps(props);
|
|
46
|
+
const owner = props.ownerName ??
|
|
47
|
+
(await props.apiClient
|
|
48
|
+
.listProjectBranchRoles(props.projectId, branchId)
|
|
49
|
+
.then(({ data }) => {
|
|
50
|
+
if (data.roles.length === 0) {
|
|
51
|
+
throw new Error(`No roles found in branch ${branchId}`);
|
|
52
|
+
}
|
|
53
|
+
if (data.roles.length > 1) {
|
|
54
|
+
throw new Error(`More than one role found in branch ${branchId}. Please specify the owner name. Roles: ${data.roles
|
|
55
|
+
.map((r) => r.name)
|
|
56
|
+
.join(", ")}`);
|
|
57
|
+
}
|
|
58
|
+
return data.roles[0].name;
|
|
59
|
+
}));
|
|
60
|
+
if (!owner) {
|
|
61
|
+
throw new Error("No owner found");
|
|
62
|
+
}
|
|
63
|
+
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchDatabase(props.projectId, branchId, {
|
|
64
|
+
database: {
|
|
65
|
+
name: props.name,
|
|
66
|
+
owner_name: owner,
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
writer(props).end(data.database, {
|
|
70
|
+
fields: DATABASE_FIELDS,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
export const deleteDb = async (props) => {
|
|
74
|
+
const branchId = await branchIdFromProps(props);
|
|
75
|
+
const { data } = await retryOnLock(() => props.apiClient.deleteProjectBranchDatabase(props.projectId, branchId, props.database));
|
|
76
|
+
// A 204 (database already gone) carries no body; only a 200 returns it.
|
|
77
|
+
if (data) {
|
|
78
|
+
writer(props).end(data.database, {
|
|
79
|
+
fields: DATABASE_FIELDS,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fillSingleProject } from "../utils/enrichers.js";
|
|
2
|
+
import { applyCmd, applyFlags, envFlag, envPullFlag, } from "./config.js";
|
|
3
|
+
export const command = "deploy";
|
|
4
|
+
export const describe = "Apply a neon.ts policy to a branch (alias for `config apply`)";
|
|
5
|
+
export const builder = (argv) => argv
|
|
6
|
+
.usage("$0 deploy [options]")
|
|
7
|
+
.options({
|
|
8
|
+
"project-id": {
|
|
9
|
+
describe: "Project ID",
|
|
10
|
+
type: "string",
|
|
11
|
+
},
|
|
12
|
+
branch: {
|
|
13
|
+
describe: "Branch ID or name",
|
|
14
|
+
type: "string",
|
|
15
|
+
},
|
|
16
|
+
config: {
|
|
17
|
+
describe: "Path to a neon.ts policy (defaults to walking up from cwd)",
|
|
18
|
+
type: "string",
|
|
19
|
+
},
|
|
20
|
+
...envFlag,
|
|
21
|
+
...applyFlags,
|
|
22
|
+
...envPullFlag,
|
|
23
|
+
})
|
|
24
|
+
.middleware(fillSingleProject)
|
|
25
|
+
.strict();
|
|
26
|
+
export const handler = (props) => applyCmd(props);
|