@batadata/cli 0.2.10 → 0.2.11
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/dist/commands/import.d.ts +12 -1
- package/dist/commands/import.js +41 -8
- package/dist/commands/projects.d.ts +1 -0
- package/dist/commands/projects.js +21 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -136,7 +136,18 @@ export declare function missingExtensions(sourceExts: string[], targetHasExts: s
|
|
|
136
136
|
* compute_ctl records applied internal SQL migrations here (table
|
|
137
137
|
* `neon_migration.migration_id`) on EVERY compute startup. Real Neon hides this
|
|
138
138
|
* schema from customer `pg_tables`; our compute exposes it, so ANY object under
|
|
139
|
-
* `neon_migration.*` is platform furniture, never user data.
|
|
139
|
+
* `neon_migration.*` is platform furniture, never user data. Same story for
|
|
140
|
+
* `neon` — compute_ctl seeds it with engine helper functions on every startup,
|
|
141
|
+
* so a BataDB source's dump would carry `CREATE SCHEMA neon` straight into the
|
|
142
|
+
* identical schema every target is provisioned with.
|
|
143
|
+
*
|
|
144
|
+
* Named objects are treated differently from whole schemas. A source
|
|
145
|
+
* `public.health_check` is still dumped and verified as real data (colliding
|
|
146
|
+
* loudly on the target rather than being silently dropped). Scaffolding SCHEMAS,
|
|
147
|
+
* by contrast, are engine furniture on the source too whenever they exist, so
|
|
148
|
+
* they are excluded from the dump itself (pg_dump --exclude-schema) and from
|
|
149
|
+
* source-side verify accounting — the target's own engine already maintains its
|
|
150
|
+
* copies, and restoring them is guaranteed to fail with "already exists".
|
|
140
151
|
*/
|
|
141
152
|
export declare const PLATFORM_SCAFFOLDING_TABLES: string[];
|
|
142
153
|
export declare const PLATFORM_SCAFFOLDING_SEQUENCES: string[];
|
package/dist/commands/import.js
CHANGED
|
@@ -273,12 +273,23 @@ export function missingExtensions(sourceExts, targetHasExts) {
|
|
|
273
273
|
* compute_ctl records applied internal SQL migrations here (table
|
|
274
274
|
* `neon_migration.migration_id`) on EVERY compute startup. Real Neon hides this
|
|
275
275
|
* schema from customer `pg_tables`; our compute exposes it, so ANY object under
|
|
276
|
-
* `neon_migration.*` is platform furniture, never user data.
|
|
276
|
+
* `neon_migration.*` is platform furniture, never user data. Same story for
|
|
277
|
+
* `neon` — compute_ctl seeds it with engine helper functions on every startup,
|
|
278
|
+
* so a BataDB source's dump would carry `CREATE SCHEMA neon` straight into the
|
|
279
|
+
* identical schema every target is provisioned with.
|
|
280
|
+
*
|
|
281
|
+
* Named objects are treated differently from whole schemas. A source
|
|
282
|
+
* `public.health_check` is still dumped and verified as real data (colliding
|
|
283
|
+
* loudly on the target rather than being silently dropped). Scaffolding SCHEMAS,
|
|
284
|
+
* by contrast, are engine furniture on the source too whenever they exist, so
|
|
285
|
+
* they are excluded from the dump itself (pg_dump --exclude-schema) and from
|
|
286
|
+
* source-side verify accounting — the target's own engine already maintains its
|
|
287
|
+
* copies, and restoring them is guaranteed to fail with "already exists".
|
|
277
288
|
*/
|
|
278
289
|
export const PLATFORM_SCAFFOLDING_TABLES = ["public.health_check"];
|
|
279
290
|
export const PLATFORM_SCAFFOLDING_SEQUENCES = ["public.health_check_id_seq"];
|
|
280
291
|
/** Schemas whose ENTIRE contents are platform furniture (any `<schema>.*` object). */
|
|
281
|
-
export const PLATFORM_SCAFFOLDING_SCHEMAS = ["neon_migration"];
|
|
292
|
+
export const PLATFORM_SCAFFOLDING_SCHEMAS = ["neon_migration", "neon"];
|
|
282
293
|
let PLATFORM_SCAFFOLDING_NAMES = new Set([
|
|
283
294
|
...PLATFORM_SCAFFOLDING_TABLES,
|
|
284
295
|
...PLATFORM_SCAFFOLDING_SEQUENCES,
|
|
@@ -432,7 +443,11 @@ function runMigration(sourceUri, targetUri) {
|
|
|
432
443
|
// data corruption that row-count verify wouldn't catch. Forcing UTF8 makes the
|
|
433
444
|
// server convert, guaranteeing a valid-UTF-8 stream; the dump carries
|
|
434
445
|
// `SET client_encoding = 'UTF8';`, which restores correctly into any target.
|
|
435
|
-
|
|
446
|
+
// Scaffolding SCHEMAS are excluded at the dump: every target is provisioned
|
|
447
|
+
// with its own copies, so restoring them can only fail ("already exists").
|
|
448
|
+
// Read at call time — applyServerScaffolding() may have extended the list.
|
|
449
|
+
const excludeArgs = PLATFORM_SCAFFOLDING_SCHEMAS.flatMap((s) => ["--exclude-schema", s]);
|
|
450
|
+
const dump = spawn("pg_dump", [sourceUri, "--no-owner", "--no-privileges", "--encoding=UTF8", ...excludeArgs], {
|
|
436
451
|
stdio: ["ignore", "pipe", "pipe"],
|
|
437
452
|
env,
|
|
438
453
|
});
|
|
@@ -833,8 +848,17 @@ export async function importDb(args) {
|
|
|
833
848
|
const sourceScaffoldingHits = srcCollisionRes.code === 0
|
|
834
849
|
? scaffoldingCollisions(srcCollisionRes.stdout.split("\n").map((s) => s.trim()).filter(Boolean))
|
|
835
850
|
: [];
|
|
836
|
-
|
|
837
|
-
|
|
851
|
+
// Schema-level hits are engine furniture and are EXCLUDED from the dump
|
|
852
|
+
// outright (see runMigration); only named-object collisions still restore
|
|
853
|
+
// into a clash on the target.
|
|
854
|
+
const schemaHits = sourceScaffoldingHits.filter((n) => PLATFORM_SCAFFOLDING_SCHEMAS.some((s) => n.startsWith(`${s}.`)));
|
|
855
|
+
const namedHits = sourceScaffoldingHits.filter((n) => !schemaHits.includes(n));
|
|
856
|
+
if (schemaHits.length > 0 && !jsonMode) {
|
|
857
|
+
warn(`Source contains engine-internal object(s) ${schemaHits.join(", ")} — platform furniture the ` +
|
|
858
|
+
"target maintains itself. They are skipped by the migration (not customer data).");
|
|
859
|
+
}
|
|
860
|
+
if (namedHits.length > 0 && !jsonMode) {
|
|
861
|
+
warn(`Source contains ${namedHits.join(", ")}, which shares a name with BataDB's seeded ` +
|
|
838
862
|
'platform scaffolding on the target. The restore will likely fail with "relation already ' +
|
|
839
863
|
'exists" — rename or exclude it on the source to migrate cleanly.');
|
|
840
864
|
}
|
|
@@ -1083,7 +1107,11 @@ export async function importDb(args) {
|
|
|
1083
1107
|
"FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema')");
|
|
1084
1108
|
if (tableListRes.code !== 0)
|
|
1085
1109
|
verifyFailed = "could not list source tables";
|
|
1086
|
-
|
|
1110
|
+
// Scaffolding-SCHEMA tables were deliberately excluded from the dump (the
|
|
1111
|
+
// target engine maintains its own copies), so they must not be counted on the
|
|
1112
|
+
// source side either — they'd read as "missing on target" mismatches. Named
|
|
1113
|
+
// scaffolding objects (public.health_check) stay in: they ARE dumped.
|
|
1114
|
+
const sourceTables = (tableListRes.code === 0 ? safeJsonArray(tableListRes.stdout) : []).filter((t) => !PLATFORM_SCAFFOLDING_SCHEMAS.includes(t.schema));
|
|
1087
1115
|
const srcCounts = new Map();
|
|
1088
1116
|
const tgtCounts = new Map();
|
|
1089
1117
|
if (!verifyFailed && sourceTables.length > 0) {
|
|
@@ -1125,8 +1153,13 @@ export async function importDb(args) {
|
|
|
1125
1153
|
verifyFailed = "sequence query failed";
|
|
1126
1154
|
}
|
|
1127
1155
|
else {
|
|
1128
|
-
|
|
1129
|
-
|
|
1156
|
+
// Mirror the table filter: source sequences inside scaffolding schemas
|
|
1157
|
+
// were never dumped, so they can't be expected on the target.
|
|
1158
|
+
for (const [k, v] of parseKvRows(sSeqRes.stdout)) {
|
|
1159
|
+
const schema = k.slice(0, k.indexOf("."));
|
|
1160
|
+
if (!PLATFORM_SCAFFOLDING_SCHEMAS.includes(schema))
|
|
1161
|
+
srcSeq.set(k, v);
|
|
1162
|
+
}
|
|
1130
1163
|
for (const [k, v] of parseKvRows(tSeqRes.stdout))
|
|
1131
1164
|
tgtSeq.set(k, v);
|
|
1132
1165
|
}
|
|
@@ -108,18 +108,21 @@ export async function list() {
|
|
|
108
108
|
export function parseCreateComputeArgs(args) {
|
|
109
109
|
let tierRaw;
|
|
110
110
|
let sizeRaw;
|
|
111
|
+
let placementRaw;
|
|
111
112
|
for (let i = 0; i < args.length; i++) {
|
|
112
113
|
const arg = args[i];
|
|
113
|
-
if (arg === "--tier" || arg === "--size") {
|
|
114
|
+
if (arg === "--tier" || arg === "--size" || arg === "--placement") {
|
|
114
115
|
// The spaced form needs a value: reject a missing one (end of args, or
|
|
115
116
|
// the next token is another flag) instead of silently using the default.
|
|
116
117
|
const next = args[i + 1];
|
|
117
118
|
if (next === undefined || next.startsWith("-")) {
|
|
118
|
-
const usage = arg === "--tier" ? "serverless|always-on" : "1|2|4|8|16|32|64|128";
|
|
119
|
+
const usage = arg === "--tier" ? "serverless|always-on" : arg === "--placement" ? "fly|density" : "1|2|4|8|16|32|64|128";
|
|
119
120
|
return { error: `${arg} requires a value (${usage}).` };
|
|
120
121
|
}
|
|
121
122
|
if (arg === "--tier")
|
|
122
123
|
tierRaw = next;
|
|
124
|
+
else if (arg === "--placement")
|
|
125
|
+
placementRaw = next;
|
|
123
126
|
else
|
|
124
127
|
sizeRaw = next;
|
|
125
128
|
i++;
|
|
@@ -128,6 +131,8 @@ export function parseCreateComputeArgs(args) {
|
|
|
128
131
|
tierRaw = arg.slice("--tier=".length);
|
|
129
132
|
else if (arg.startsWith("--size="))
|
|
130
133
|
sizeRaw = arg.slice("--size=".length);
|
|
134
|
+
else if (arg.startsWith("--placement="))
|
|
135
|
+
placementRaw = arg.slice("--placement=".length);
|
|
131
136
|
}
|
|
132
137
|
let tier = "serverless";
|
|
133
138
|
if (tierRaw !== undefined) {
|
|
@@ -158,7 +163,19 @@ export function parseCreateComputeArgs(args) {
|
|
|
158
163
|
}
|
|
159
164
|
sizeCu = parsed;
|
|
160
165
|
}
|
|
161
|
-
|
|
166
|
+
let placement;
|
|
167
|
+
if (placementRaw !== undefined) {
|
|
168
|
+
const normalized = placementRaw.toLowerCase();
|
|
169
|
+
if (normalized === "fly" || normalized === "density")
|
|
170
|
+
placement = normalized;
|
|
171
|
+
else
|
|
172
|
+
return { error: `Invalid --placement "${placementRaw}". Use "fly" or "density".` };
|
|
173
|
+
// Placement pins a SERVERLESS compute's provider; always-on is Fly always.
|
|
174
|
+
if (tier === "always_on") {
|
|
175
|
+
return { error: `--placement applies to serverless projects only (always-on runs on Fly).` };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { tier, sizeCu, ...(placement ? { placement } : {}) };
|
|
162
179
|
}
|
|
163
180
|
/** `--flag <value>` / `--flag=<value>` reader. Distinguishes "absent"
|
|
164
181
|
* (undefined) from "present but missing its value" ({ error }) so a headless
|
|
@@ -275,6 +292,7 @@ export async function create(args = []) {
|
|
|
275
292
|
// One friendly step: the compute is born on the chosen tier + size (no
|
|
276
293
|
// follow-up `bata compute set` needed). Defaults to serverless / 1 CU.
|
|
277
294
|
compute: { tier: compute.tier, size_cu: compute.sizeCu },
|
|
295
|
+
...(compute.placement ? { placement: compute.placement } : {}),
|
|
278
296
|
};
|
|
279
297
|
if (teamId) {
|
|
280
298
|
body.team_id = teamId;
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ function help() {
|
|
|
51
51
|
log(` ${colors.bold("Projects")}`);
|
|
52
52
|
log(` ${colors.cyan("projects")} List all projects`);
|
|
53
53
|
log(` ${colors.cyan("projects list")} Alias for status`);
|
|
54
|
-
log(` ${colors.cyan("projects create")} Create a new project ${colors.dim("(--tier serverless|always-on, --size 1|2|4|8|16|32|64|128)")}`);
|
|
54
|
+
log(` ${colors.cyan("projects create")} Create a new project ${colors.dim("(--tier serverless|always-on, --size 1|2|4|8|16|32|64|128, --placement fly|density)")}`);
|
|
55
55
|
log(` ${colors.cyan("projects info")} Show project details`);
|
|
56
56
|
log(` ${colors.cyan("projects delete")} Delete a project`);
|
|
57
57
|
log();
|