@abloatai/cli 0.54.0 → 0.55.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 +84 -7
  2. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -3970,7 +3970,7 @@ var init_observeCliError = __esm({
3970
3970
  import_errorObservation = require("@abloatai/transaction/errorObservation");
3971
3971
  import_errors5 = require("@abloatai/transaction/errors");
3972
3972
  dsn = process.env.ABLO_CLI_SENTRY_DSN ?? "https://1ac154bff10b06836e1ea9de9e0d92f0@o4510928209772544.ingest.de.sentry.io/4511660691423312" ?? "";
3973
- release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.54.0";
3973
+ release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.55.0";
3974
3974
  initialized = false;
3975
3975
  nativeProcessExit = process.exit.bind(process);
3976
3976
  exitBoundaryInstalled = false;
@@ -4297,7 +4297,7 @@ var init_src2 = __esm({
4297
4297
 
4298
4298
  // src/cliEnvironment.ts
4299
4299
  function cliVersion() {
4300
- return "0.54.0";
4300
+ return "0.55.0";
4301
4301
  }
4302
4302
  function cliOs() {
4303
4303
  const value = (0, import_node_os.platform)();
@@ -5968,6 +5968,28 @@ async function fetchPushedSchema(apiUrl3, apiKey) {
5968
5968
  clearTimeout(t);
5969
5969
  }
5970
5970
  }
5971
+ async function fetchDeliveryState(apiUrl3, apiKey, timeoutMs = 4e3) {
5972
+ if (!apiKey) return { kind: "unknown", detail: "no key" };
5973
+ const ctrl = new AbortController();
5974
+ const t = setTimeout(() => {
5975
+ ctrl.abort();
5976
+ }, timeoutMs);
5977
+ try {
5978
+ const res = await fetch(`${apiUrl3}/api/v1/logs/delivery`, {
5979
+ headers: { authorization: `Bearer ${apiKey}` },
5980
+ signal: ctrl.signal
5981
+ });
5982
+ if (!res.ok) return { kind: "unknown", detail: `HTTP ${res.status}` };
5983
+ const parsed = import_wire5.logDeliveryResponseSchema.safeParse(await res.json());
5984
+ if (!parsed.success) return { kind: "unknown", detail: "unrecognized response" };
5985
+ const { window_seconds, recorded, unroutable, sample } = parsed.data;
5986
+ return { kind: "known", window_seconds, recorded, unroutable, sample: sample ?? null };
5987
+ } catch {
5988
+ return { kind: "unknown", detail: "unreachable" };
5989
+ } finally {
5990
+ clearTimeout(t);
5991
+ }
5992
+ }
5971
5993
  async function fetchDataSourceState(apiUrl3, apiKey, timeoutMs = 4e3) {
5972
5994
  if (!apiKey) return { kind: "unknown", detail: "no key" };
5973
5995
  const ctrl = new AbortController();
@@ -6069,7 +6091,7 @@ function blockers(input) {
6069
6091
  }
6070
6092
  return found;
6071
6093
  }
6072
- var import_wire5, import_schema4;
6094
+ var import_wire5, import_schema4, WRITE_READY_VERDICT;
6073
6095
  var init_readiness = __esm({
6074
6096
  "src/readiness.ts"() {
6075
6097
  "use strict";
@@ -6079,6 +6101,7 @@ var init_readiness = __esm({
6079
6101
  init_dbProvider();
6080
6102
  init_remoteValidation();
6081
6103
  import_schema4 = require("@abloatai/transaction/schema");
6104
+ WRITE_READY_VERDICT = "write infrastructure is ready. Your database constraints and row-level policies still apply.";
6082
6105
  }
6083
6106
  });
6084
6107
 
@@ -285430,6 +285453,14 @@ function render(check2) {
285430
285453
  );
285431
285454
  if (check2.fix) console.log(` ${" ".repeat(11)}${import_picocolors18.default.dim(`\u2192 ${check2.fix}`)}`);
285432
285455
  }
285456
+ function windowWords(seconds) {
285457
+ if (seconds % 3600 === 0) {
285458
+ const hours = seconds / 3600;
285459
+ return hours === 1 ? "the last hour" : `the last ${hours} hours`;
285460
+ }
285461
+ const minutes = Math.max(1, Math.round(seconds / 60));
285462
+ return minutes === 1 ? "the last minute" : `the last ${minutes} minutes`;
285463
+ }
285433
285464
  async function ping(apiUrl3) {
285434
285465
  const ctrl = new AbortController();
285435
285466
  const t = setTimeout(() => {
@@ -285572,6 +285603,24 @@ async function inspectDoctor(options = {}) {
285572
285603
  } else {
285573
285604
  checks.push({ label: "database", state: "skip", detail: "nothing connected to check" });
285574
285605
  }
285606
+ const delivery = reachable ? await fetchDeliveryState(apiUrl3, runtimeKey.key) : { kind: "unknown", detail: "unreachable" };
285607
+ if (delivery.kind === "known") {
285608
+ const when = windowWords(delivery.window_seconds);
285609
+ checks.push(
285610
+ delivery.unroutable > 0 ? {
285611
+ label: "delivery",
285612
+ state: "fail",
285613
+ detail: `${delivery.unroutable} of ${delivery.recorded} change${delivery.recorded === 1 ? "" : "s"} in ${when} reached nobody` + (delivery.sample ? ` (e.g. ${delivery.sample.model}/${delivery.sample.id})` : ""),
285614
+ fix: "run `ablo check`. Rows written to Postgres outside Ablo carry no tenancy value, so nothing can route the change."
285615
+ } : {
285616
+ label: "delivery",
285617
+ state: "ok",
285618
+ detail: delivery.recorded === 0 ? `no changes in ${when}` : `${delivery.recorded} change${delivery.recorded === 1 ? "" : "s"} in ${when}, all deliverable`
285619
+ }
285620
+ );
285621
+ } else {
285622
+ checks.push({ label: "delivery", state: "skip", detail: `not determined (${delivery.detail})` });
285623
+ }
285575
285624
  const blocking = blockers({
285576
285625
  reachable,
285577
285626
  hasKey: Boolean(runtimeKey.key),
@@ -285592,7 +285641,8 @@ async function inspectDoctor(options = {}) {
285592
285641
  target,
285593
285642
  dataSource,
285594
285643
  pushedSchema: pushed,
285595
- schemaDrift: drift
285644
+ schemaDrift: drift,
285645
+ delivery
285596
285646
  };
285597
285647
  }
285598
285648
  function renderDoctorReport(report) {
@@ -285614,7 +285664,7 @@ function renderDoctorReport(report) {
285614
285664
  );
285615
285665
  } else {
285616
285666
  console.log(
285617
- ` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.dim("write infrastructure is ready \u2014 your database constraints and row-level policies still apply")}`
285667
+ ` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.dim(WRITE_READY_VERDICT)}`
285618
285668
  );
285619
285669
  }
285620
285670
  console.log();
@@ -288136,7 +288186,7 @@ async function status(args = []) {
288136
288186
  );
288137
288187
  } else {
288138
288188
  console.log(
288139
- ` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.dim("write infrastructure is ready \u2014 database constraints and row-level policies still apply")}`
288189
+ ` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.dim(WRITE_READY_VERDICT)}`
288140
288190
  );
288141
288191
  }
288142
288192
  console.log();
@@ -288623,6 +288673,24 @@ function parseCheckArgs(argv) {
288623
288673
  }
288624
288674
  return { schemaPath, exportName, appSchema };
288625
288675
  }
288676
+ function quoteIdent2(raw) {
288677
+ return `"${raw.replace(/"/g, '""')}"`;
288678
+ }
288679
+ var UNSTAMPED_CAP = 500;
288680
+ async function countUnstamped(sql, appSchema, table, column) {
288681
+ try {
288682
+ const rows = await sql.unsafe(
288683
+ `SELECT count(*)::int AS n FROM (
288684
+ SELECT 1 FROM ${quoteIdent2(appSchema)}.${quoteIdent2(table)}
288685
+ WHERE ${quoteIdent2(column)} IS NULL
288686
+ LIMIT ${UNSTAMPED_CAP + 1}
288687
+ ) s`
288688
+ );
288689
+ return rows[0]?.n ?? 0;
288690
+ } catch {
288691
+ return null;
288692
+ }
288693
+ }
288626
288694
  function hostOf(connectionString) {
288627
288695
  try {
288628
288696
  return new URL(connectionString).hostname || null;
@@ -288700,7 +288768,6 @@ async function check(argv) {
288700
288768
  await sql.end({ timeout: 2 });
288701
288769
  process.exit(1);
288702
288770
  }
288703
- await sql.end({ timeout: 2 });
288704
288771
  const colsByTable = /* @__PURE__ */ new Map();
288705
288772
  for (const r2 of rows) {
288706
288773
  let set = colsByTable.get(r2.table_name);
@@ -288739,6 +288806,15 @@ async function check(argv) {
288739
288806
  if (BASE_COLUMNS.has(col) || col === orgCol) continue;
288740
288807
  if (!present.has(col)) problems.push(`missing column "${col}" (field ${fieldName})`);
288741
288808
  }
288809
+ if (orgCol && present.has(orgCol)) {
288810
+ const unstamped = await countUnstamped(sql, args.appSchema, table, orgCol);
288811
+ if (unstamped !== null && unstamped > 0) {
288812
+ const count = unstamped > UNSTAMPED_CAP ? `${UNSTAMPED_CAP}+ rows` : `${unstamped} row${unstamped === 1 ? "" : "s"}`;
288813
+ problems.push(
288814
+ `${count} have no "${orgCol}", so nothing can route them. Ablo stamps it on writes it makes; an insert straight into Postgres does not. Backfill the value, then run \`ablo connect resnapshot\`.`
288815
+ );
288816
+ }
288817
+ }
288742
288818
  if (problems.length > 0) {
288743
288819
  console.log(` ${import_picocolors25.default.red("\u2717")} ${import_picocolors25.default.bold(key)} ${import_picocolors25.default.dim("\u2192")} ${table}`);
288744
288820
  for (const p2 of problems) console.log(` ${import_picocolors25.default.red("\u2022")} ${p2}`);
@@ -288752,6 +288828,7 @@ async function check(argv) {
288752
288828
  console.log(` ${import_picocolors25.default.green("\u2713")} ${import_picocolors25.default.bold(key)} ${import_picocolors25.default.dim(`\u2192 ${table} (id, ${orgCol ?? "no org"} ok)`)}`);
288753
288829
  }
288754
288830
  }
288831
+ await sql.end({ timeout: 2 });
288755
288832
  const modelCount = Object.keys(schemaJson.models).length;
288756
288833
  const ignored = [...colsByTable.keys()].filter((t) => !declaredTables.has(t)).length;
288757
288834
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/cli",
3
- "version": "0.54.0",
3
+ "version": "0.55.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",
@@ -35,10 +35,10 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@sentry/node": "^10.18.0",
38
- "@abloatai/transaction": "^0.54.0",
38
+ "@abloatai/transaction": "^0.55.0",
39
39
  "jiti": "^2.7.0",
40
40
  "zod": "^4.4.3",
41
- "@abloatai/humans": "^0.54.0"
41
+ "@abloatai/humans": "^0.55.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@ablo/product-analytics": "file:../product-analytics",