@abloatai/cli 0.45.0 → 0.46.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.
Files changed (2) hide show
  1. package/dist/cli.cjs +110 -19
  2. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -4538,6 +4538,7 @@ async function requestRemoteValidation(input) {
4538
4538
  ok: true,
4539
4539
  reachable: verdict.reachable,
4540
4540
  ready: verdict.ready,
4541
+ ...verdict.initial_snapshot !== void 0 ? { initialSnapshot: verdict.initial_snapshot } : {},
4541
4542
  ...verdict.reason !== void 0 ? { reason: verdict.reason } : {},
4542
4543
  failures: verdict.failures
4543
4544
  };
@@ -4565,7 +4566,10 @@ var init_remoteValidation = __esm({
4565
4566
  wal_level: () => `your database isn't set up to share changes as they happen yet`,
4566
4567
  publication: () => `none of your tables are shared with Ablo yet`,
4567
4568
  replication_role: () => `the login Ablo reads with can't follow your changes yet`,
4568
- replica_identity: (f) => withActual(`some shared tables don't record enough for Ablo to track edits and deletes`, f.actual),
4569
+ replica_identity: (f) => withActual(
4570
+ `some shared tables don't record enough for Ablo to track edits and deletes`,
4571
+ f.actual
4572
+ ),
4569
4573
  table_select: (f) => withActual(`the login Ablo reads with can't read some shared tables`, f.actual),
4570
4574
  write_role: () => `the login Ablo writes with isn't set up yet`,
4571
4575
  row_security: () => `the writer login isn't set to honor your row-level security`,
@@ -4911,7 +4915,8 @@ async function registerDirectDataSource(opts) {
4911
4915
  `
4912
4916
  ${import_picocolors7.default.green("\u2713")} Registered${body.host ? ` ${import_picocolors7.default.dim(body.host)}` : ""}${body.id ? ` ${import_picocolors7.default.dim(`(${body.id})`)}` : ""} as a direct DataSource (${statusNote}).
4913
4917
  Your database is connected. Reads follow its replication stream; writes go through Ablo
4914
- and land in your own tables. Check the connection anytime with ${import_picocolors7.default.cyan("ablo connect check")}.
4918
+ and land in your own tables. Rows that already exist load automatically \u2014 no manual
4919
+ backfill or row updates. Check their progress with ${import_picocolors7.default.cyan("ablo connect check")}.
4915
4920
  `
4916
4921
  );
4917
4922
  return true;
@@ -6525,10 +6530,9 @@ async function auditTenantSyncInfra(sql) {
6525
6530
  const artifacts = [];
6526
6531
  for (const artifact of import_footprint2.ABLO_FOOTPRINT) {
6527
6532
  const key = artifact.kind === "table" || artifact.kind === "type" ? `public.${artifact.name}` : artifact.name;
6528
- const rows = await sql.unsafe(
6529
- FOOTPRINT_LOOKUP[artifact.kind],
6530
- [key]
6531
- );
6533
+ const rows = await sql.unsafe(FOOTPRINT_LOOKUP[artifact.kind], [
6534
+ key
6535
+ ]);
6532
6536
  artifacts.push({
6533
6537
  kind: artifact.kind,
6534
6538
  name: artifact.name,
@@ -6637,6 +6641,22 @@ ${ambient}` : ""}`,
6637
6641
  const { label, fix } = describeRemoteFailure(failure);
6638
6642
  printCheckItem({ ok: false, label, fix });
6639
6643
  }
6644
+ if (result.initialSnapshot?.status === "complete") {
6645
+ printCheckItem({ ok: true, label: "Rows that existed before connecting are loaded" });
6646
+ } else if (result.initialSnapshot?.status === "loading") {
6647
+ printCheckItem({
6648
+ ok: false,
6649
+ label: "Rows that existed before connecting are still loading",
6650
+ fix: "No SQL or row-touch script is needed. Ablo snapshots published tables automatically; wait a moment, then rerun `ablo connect check`."
6651
+ });
6652
+ } else if (result.initialSnapshot?.status === "retrying") {
6653
+ printCheckItem({
6654
+ ok: false,
6655
+ label: "Loading existing rows hit an error and Ablo is retrying",
6656
+ fix: `${result.initialSnapshot.detail ?? "The replication worker reported an error."}
6657
+ No row updates are needed. Fix the reported connection or database issue, then rerun \`ablo connect check\`.`
6658
+ });
6659
+ }
6640
6660
  console.log();
6641
6661
  if (result.ready) {
6642
6662
  console.log(
@@ -6645,6 +6665,13 @@ ${ambient}` : ""}`,
6645
6665
  );
6646
6666
  process.exit(0);
6647
6667
  }
6668
+ if ((result.initialSnapshot?.status === "loading" || result.initialSnapshot?.status === "retrying") && result.failures.length === 0) {
6669
+ console.log(
6670
+ ` ${import_picocolors12.default.yellow("\u2014")} Not ready yet ${import_picocolors12.default.dim(`\u2014 Ablo is loading existing rows. Re-run ${import_picocolors12.default.bold("ablo connect check")} shortly.`)}
6671
+ `
6672
+ );
6673
+ process.exit(1);
6674
+ }
6648
6675
  const count = result.failures.length;
6649
6676
  console.log(
6650
6677
  ` ${import_picocolors12.default.red(`${count} item${count === 1 ? "" : "s"} to fix`)} ${import_picocolors12.default.dim(`\u2014 apply the fixes above, then re-run ${import_picocolors12.default.bold("ablo connect check")}.`)}
@@ -284738,7 +284765,9 @@ async function ping(apiUrl3) {
284738
284765
  }
284739
284766
  function formatConflict(conflict) {
284740
284767
  if (!conflict) return "";
284741
- const parts = import_schema8.participantKindSchema.options.flatMap((k3) => conflict[k3] ? [`${k3}:${conflict[k3]}`] : []);
284768
+ const parts = import_schema8.participantKindSchema.options.flatMap(
284769
+ (k3) => conflict[k3] ? [`${k3}:${conflict[k3]}`] : []
284770
+ );
284742
284771
  return parts.length ? `{${parts.join(",")}}` : "";
284743
284772
  }
284744
284773
  function printTargetLines(target, localProject, storedOrganizationId, storedOrganizationSlug) {
@@ -284783,13 +284812,21 @@ async function status(args = []) {
284783
284812
  const cfg = readConfig();
284784
284813
  const mode = getMode();
284785
284814
  const runtimeKey = resolveRuntimeApiKey();
284786
- const target = runtimeKey.key ? await resolveTarget({ url: apiUrl3, apiKey: runtimeKey.key, keySource: runtimeKey.source ?? "stored" }) : null;
284815
+ const target = runtimeKey.key ? await resolveTarget({
284816
+ url: apiUrl3,
284817
+ apiKey: runtimeKey.key,
284818
+ keySource: runtimeKey.source ?? "stored"
284819
+ }) : null;
284787
284820
  if (args.includes("--json")) {
284788
284821
  const entry = getKeyEntry(mode);
284789
284822
  const activeProject2 = getActiveProject();
284790
284823
  const pushed2 = await fetchPushedSchema(apiUrl3, runtimeKey.key);
284791
284824
  const reachableForJson = await ping(apiUrl3);
284792
- const dataSource2 = reachableForJson ? (await fetchRoutingState(apiUrl3, runtimeKey.key)).source : { kind: "unknown", detail: "unreachable" };
284825
+ const routingForJson = reachableForJson ? await fetchRoutingState(apiUrl3, runtimeKey.key) : {
284826
+ source: { kind: "unknown", detail: "unreachable" },
284827
+ validation: null
284828
+ };
284829
+ const dataSource2 = routingForJson.source;
284793
284830
  const driftForJson = schemaDrift(await readLocalSchemaHash(), pushed2?.hash);
284794
284831
  const out = {
284795
284832
  // The locally-active project (`ablo projects use`); null = org-default.
@@ -284835,6 +284872,10 @@ async function status(args = []) {
284835
284872
  // form of the human verdict: a caller can gate on it in CI rather than
284836
284873
  // discovering the same facts from a failed request later.
284837
284874
  dataSource: dataSource2,
284875
+ // Existing rows are copied into Ablo automatically after a direct source
284876
+ // is connected. Agents can gate a read cutover on this field instead of
284877
+ // probing for one row or inventing a row-touch backfill.
284878
+ initialSnapshot: routingForJson.validation?.ok === true ? routingForJson.validation.initialSnapshot ?? null : null,
284838
284879
  drift: driftForJson,
284839
284880
  blockers: blockers({
284840
284881
  reachable: reachableForJson,
@@ -284868,7 +284909,9 @@ async function status(args = []) {
284868
284909
  );
284869
284910
  const management = getManagementKeyEntry();
284870
284911
  console.log(
284871
- ` ${import_picocolors19.default.dim("\u25CB")} ${"management".padEnd(12)} ${management ? import_picocolors19.default.dim(`${management.apiKey.slice(0, 12)}\u2026${management.expiresAt ? ` \xB7 ${expiryLabel(management.expiresAt)}` : ""}`) : import_picocolors19.default.dim("\u2014 no key")}`
284912
+ ` ${import_picocolors19.default.dim("\u25CB")} ${"management".padEnd(12)} ${management ? import_picocolors19.default.dim(
284913
+ `${management.apiKey.slice(0, 12)}\u2026${management.expiresAt ? ` \xB7 ${expiryLabel(management.expiresAt)}` : ""}`
284914
+ ) : import_picocolors19.default.dim("\u2014 no key")}`
284872
284915
  );
284873
284916
  for (const { key: m2, label } of [
284874
284917
  { key: "sandbox", label: "legacy child" },
@@ -284901,7 +284944,9 @@ async function status(args = []) {
284901
284944
  const how = [...new Set(dataSource.connections)].join(" + ");
284902
284945
  const pooled = detectPoolerIn(dataSource.hosts);
284903
284946
  const unreachable = validation && !validation.ok ? validation.message : void 0;
284904
- console.log(` ${import_picocolors19.default.dim("data")} ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.dim(`database connected to this plane (${how})`)}`);
284947
+ console.log(
284948
+ ` ${import_picocolors19.default.dim("data")} ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.dim(`database connected to this plane (${how})`)}`
284949
+ );
284905
284950
  if (pooled) {
284906
284951
  console.log(
284907
284952
  ` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.dim(
@@ -284912,12 +284957,27 @@ async function status(args = []) {
284912
284957
  if (unreachable) {
284913
284958
  console.log(` ${import_picocolors19.default.red("\u2717")} ${import_picocolors19.default.dim(`Ablo could not reach it \u2014 ${unreachable}`)}`);
284914
284959
  }
284960
+ if (validation?.ok && validation.initialSnapshot?.status === "loading") {
284961
+ console.log(
284962
+ ` ${import_picocolors19.default.yellow("\u25CC")} ${import_picocolors19.default.dim(
284963
+ `loading rows that predate the connection \u2014 automatic; check with ${import_picocolors19.default.bold("ablo connect check")}`
284964
+ )}`
284965
+ );
284966
+ } else if (validation?.ok && validation.initialSnapshot?.status === "retrying") {
284967
+ console.log(
284968
+ ` ${import_picocolors19.default.red("\u2717")} ${import_picocolors19.default.dim(
284969
+ `loading existing rows is retrying${validation.initialSnapshot.detail ? ` \u2014 ${validation.initialSnapshot.detail}` : ""}`
284970
+ )}`
284971
+ );
284972
+ }
284915
284973
  } else if (dataSource.kind === "none") {
284916
284974
  console.log(
284917
284975
  ` ${import_picocolors19.default.dim("data")} ${import_picocolors19.default.red("\u2717 no database connected to this plane")} ${import_picocolors19.default.dim("\u2014 writes are held")}`
284918
284976
  );
284919
284977
  } else if (reachable) {
284920
- console.log(` ${import_picocolors19.default.dim("data")} ${import_picocolors19.default.yellow("?")} ${import_picocolors19.default.dim(`could not read the plane's databases (${dataSource.detail})`)}`);
284978
+ console.log(
284979
+ ` ${import_picocolors19.default.dim("data")} ${import_picocolors19.default.yellow("?")} ${import_picocolors19.default.dim(`could not read the plane's databases (${dataSource.detail})`)}`
284980
+ );
284921
284981
  }
284922
284982
  const pushed = reachable ? await fetchPushedSchema(apiUrl3, introspectKey) : null;
284923
284983
  if (reachable) {
@@ -284925,7 +284985,9 @@ async function status(args = []) {
284925
284985
  const when = pushed.pushedAt ? ` ${import_picocolors19.default.dim(`@ ${pushed.pushedAt.slice(0, 10)}`)}` : "";
284926
284986
  const ver = pushed.version != null ? ` ${import_picocolors19.default.dim(`(rev ${pushed.version})`)}` : "";
284927
284987
  const hashLabel = pushed.hash ? ` ${import_picocolors19.default.dim(`hash ${pushed.hash}`)}` : "";
284928
- console.log(` ${import_picocolors19.default.dim("schema")} ${import_picocolors19.default.bold(`${pushed.models.length} models pushed`)}${ver}${hashLabel}${when}`);
284988
+ console.log(
284989
+ ` ${import_picocolors19.default.dim("schema")} ${import_picocolors19.default.bold(`${pushed.models.length} models pushed`)}${ver}${hashLabel}${when}`
284990
+ );
284929
284991
  for (const m2 of pushed.models) {
284930
284992
  const tn = m2.typename === m2.key ? import_picocolors19.default.dim(`typename=${m2.typename}`) : import_picocolors19.default.yellow(`typename=${m2.typename}`);
284931
284993
  const conflict = formatConflict(m2.conflict);
@@ -284933,7 +284995,9 @@ async function status(args = []) {
284933
284995
  console.log(` ${import_picocolors19.default.dim("\u2022")} ${m2.key.padEnd(14)} ${tn}${conflictStr2}`);
284934
284996
  }
284935
284997
  } else if (pushed && !pushed.active) {
284936
- console.log(` ${import_picocolors19.default.dim("schema")} ${import_picocolors19.default.yellow("none pushed")} ${import_picocolors19.default.dim(`(run ${import_picocolors19.default.bold("ablo push")} or ${import_picocolors19.default.bold("ablo dev")})`)}`);
284998
+ console.log(
284999
+ ` ${import_picocolors19.default.dim("schema")} ${import_picocolors19.default.yellow("none pushed")} ${import_picocolors19.default.dim(`(run ${import_picocolors19.default.bold("ablo push")} or ${import_picocolors19.default.bold("ablo dev")})`)}`
285000
+ );
284937
285001
  }
284938
285002
  }
284939
285003
  const drift = schemaDrift(await readLocalSchemaHash(), pushed?.hash);
@@ -284978,7 +285042,9 @@ init_target();
284978
285042
  init_readiness();
284979
285043
  function render(check2) {
284980
285044
  const mark = check2.state === "ok" ? import_picocolors20.default.green("\u2713") : check2.state === "fail" ? import_picocolors20.default.red("\u2717") : import_picocolors20.default.dim("\u2013");
284981
- console.log(` ${mark} ${check2.label.padEnd(10)} ${check2.state === "fail" ? check2.detail : import_picocolors20.default.dim(check2.detail)}`);
285045
+ console.log(
285046
+ ` ${mark} ${check2.label.padEnd(10)} ${check2.state === "fail" ? check2.detail : import_picocolors20.default.dim(check2.detail)}`
285047
+ );
284982
285048
  if (check2.fix) console.log(` ${" ".repeat(11)}${import_picocolors20.default.dim(`\u2192 ${check2.fix}`)}`);
284983
285049
  }
284984
285050
  async function ping2(apiUrl3) {
@@ -285025,7 +285091,11 @@ async function doctor() {
285025
285091
  fix: "check your connection, then re-run `ablo doctor`"
285026
285092
  }
285027
285093
  );
285028
- const target = runtimeKey.key ? await resolveTarget({ url: apiUrl3, apiKey: runtimeKey.key, keySource: runtimeKey.source ?? "stored" }) : null;
285094
+ const target = runtimeKey.key ? await resolveTarget({
285095
+ url: apiUrl3,
285096
+ apiKey: runtimeKey.key,
285097
+ keySource: runtimeKey.source ?? "stored"
285098
+ }) : null;
285029
285099
  const confirmed = target?.confirmed ?? null;
285030
285100
  if (confirmed) {
285031
285101
  const project = confirmed.project ? confirmed.project.isDefault ? "default" : confirmed.project.name === null ? `${confirmed.project.id} (unnamed \u2014 ${confirmed.project.unnamedReason ?? "the project list did not answer"})` : `${confirmed.project.slug} (${confirmed.project.id})` : "default";
@@ -285071,7 +285141,12 @@ async function doctor() {
285071
285141
  label: "schema",
285072
285142
  state: "ok",
285073
285143
  detail: `${pushed.models.length} models active${pushed.hash ? `, hash ${pushed.hash}` : ""}`
285074
- } : reachable && runtimeKey.key ? { label: "schema", state: "fail", detail: "none active for this key", fix: "run `ablo push`" } : { label: "schema", state: "skip", detail: "not determined" }
285144
+ } : reachable && runtimeKey.key ? {
285145
+ label: "schema",
285146
+ state: "fail",
285147
+ detail: "none active for this key",
285148
+ fix: "run `ablo push`"
285149
+ } : { label: "schema", state: "skip", detail: "not determined" }
285075
285150
  );
285076
285151
  const drift = schemaDrift(await readLocalSchemaHash(), pushed?.hash);
285077
285152
  if (drift) {
@@ -285084,7 +285159,21 @@ async function doctor() {
285084
285159
  }
285085
285160
  if (dataSource.kind === "connected" && validation) {
285086
285161
  checks.push(
285087
- validation.ok ? validation.ready ? { label: "database", state: "ok", detail: "reachable from Ablo, and ready to replicate" } : {
285162
+ validation.ok ? validation.ready ? {
285163
+ label: "database",
285164
+ state: "ok",
285165
+ detail: "reachable from Ablo, and ready to replicate"
285166
+ } : validation.initialSnapshot?.status === "loading" && validation.failures.length === 0 ? {
285167
+ label: "database",
285168
+ state: "fail",
285169
+ detail: "reachable; rows that predate the connection are still loading",
285170
+ fix: "wait a moment and rerun `ablo doctor` \u2014 Ablo snapshots them automatically; do not update every row or write a manual backfill"
285171
+ } : validation.initialSnapshot?.status === "retrying" && validation.failures.length === 0 ? {
285172
+ label: "database",
285173
+ state: "fail",
285174
+ detail: `the initial row load is retrying${validation.initialSnapshot.detail ? ` \u2014 ${validation.initialSnapshot.detail}` : ""}`,
285175
+ fix: "fix the reported connection or database issue, then rerun `ablo doctor`; no row-touch backfill is needed"
285176
+ } : {
285088
285177
  label: "database",
285089
285178
  state: "fail",
285090
285179
  detail: `reachable, but not ready (${validation.failures.length} check${validation.failures.length === 1 ? "" : "s"} failing)`,
@@ -285114,7 +285203,9 @@ async function doctor() {
285114
285203
  console.log(
285115
285204
  ` ${import_picocolors20.default.red("\u2717")} ${import_picocolors20.default.bold(`${failed} problem${failed === 1 ? "" : "s"}`)}` + import_picocolors20.default.dim(skipped > 0 ? `, ${skipped} check${skipped === 1 ? "" : "s"} skipped` : "")
285116
285205
  );
285117
- console.log(import_picocolors20.default.dim(" Fix them in the order above \u2014 an earlier one often explains a later one."));
285206
+ console.log(
285207
+ import_picocolors20.default.dim(" Fix them in the order above \u2014 an earlier one often explains a later one.")
285208
+ );
285118
285209
  } else if (skipped > 0) {
285119
285210
  console.log(
285120
285211
  ` ${import_picocolors20.default.yellow("?")} ${import_picocolors20.default.dim(`nothing is blocking a write, but ${skipped} check${skipped === 1 ? "" : "s"} could not be run`)}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/cli",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "The ablo command line: set up Ablo, connect your database, and push your schema from the terminal.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -30,10 +30,10 @@
30
30
  "directory": "packages/cli"
31
31
  },
32
32
  "dependencies": {
33
- "@abloatai/transaction": "^0.45.0",
33
+ "@abloatai/transaction": "^0.46.0",
34
34
  "jiti": "^2.7.0",
35
35
  "zod": "^4.4.3",
36
- "@abloatai/humans": "^0.45.0"
36
+ "@abloatai/humans": "^0.46.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@clack/prompts": "^0.11.0",