@erdoai/cli 0.139.0 → 0.140.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/index.js +97 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,13 +15,34 @@ function advertiserVerificationAction(container) {
15
15
 
16
16
  // src/refresh.ts
17
17
  function describeRefreshTrigger(state) {
18
- if (state.disarmed) return "disarmed \u2014 the experiment has settled";
19
- if (state.strategy === "live_webhook") return "incoming webhook events \u2014 page views do not trigger this feed";
20
- if (state.strategy === "live_native_poll") return "not running \u2014 native polling is unavailable; configure script_js with a schedule or live_webhook";
21
- if (!state.job_id) return "not running \u2014 no linked job; configure the refresh again";
22
- if (state.job_enabled === false) return "not running \u2014 the linked job is disabled; configure the refresh again";
23
- if (state.schedule) return state.schedule_enabled === false ? `cron ${state.schedule} is disabled` : `cron: ${state.schedule}`;
24
- return state.refresh_on_view_enabled !== false ? "runs on view \u2014 when a bound page is opened and the data is stale" : "manual only \u2014 page views do not trigger this refresh";
18
+ const event = describeEventTriggers(state);
19
+ if (state.disarmed) return event ? `disarmed \u2014 the experiment has settled; ${event}` : "disarmed \u2014 the experiment has settled";
20
+ if (state.strategy === "live_webhook") return event ? `incoming webhook events \u2014 page views do not trigger this feed; ${event}` : "incoming webhook events \u2014 page views do not trigger this feed";
21
+ if (state.strategy === "live_native_poll") return event ? `not running \u2014 native polling is unavailable; ${event}` : "not running \u2014 native polling is unavailable; configure script_js with a schedule or live_webhook";
22
+ if (!state.job_id) return event ? `not running \u2014 no linked job; ${event}` : "not running \u2014 no linked job; configure the refresh again";
23
+ if (state.job_enabled === false) return event ? `not running \u2014 the linked job is disabled; ${event}` : "not running \u2014 the linked job is disabled; configure the refresh again";
24
+ const trigger = state.schedule ? state.schedule_enabled === false ? `cron ${state.schedule} is disabled` : `cron: ${state.schedule}` : state.refresh_on_view_enabled !== false ? "runs on view \u2014 when a bound page is opened and the data is stale" : event ? void 0 : "manual only \u2014 page views do not trigger this refresh";
25
+ return [trigger, event, state.test_run_success === false ? "configuration test failed" : void 0].filter(Boolean).join("; ");
26
+ }
27
+ function describeEventTriggers(state) {
28
+ if (!state.event_triggers?.length) {
29
+ if (!state.event_dataset_ids?.length) return void 0;
30
+ return `event-driven \u2014 configured for ${state.event_dataset_ids.join(", ")} (debounce ${state.event_debounce_seconds ?? 0}s)`;
31
+ }
32
+ const triggers = state.event_triggers;
33
+ const ownerActive = !!state.job_id && state.disarmed !== true && state.job_enabled !== false;
34
+ const enabled = ownerActive ? triggers.filter((trigger) => trigger.enabled) : [];
35
+ const disabled = ownerActive ? triggers.filter((trigger) => !trigger.enabled) : triggers;
36
+ const parts = [];
37
+ if (enabled.length) {
38
+ const debounce = new Set(enabled.map((trigger) => trigger.debounce_seconds));
39
+ parts.push(`event-driven \u2014 enabled for ${enabled.map((trigger) => trigger.dataset_id).join(", ")}${debounce.size === 1 ? ` (debounce ${enabled[0].debounce_seconds}s)` : ""}`);
40
+ }
41
+ if (disabled.length || enabled.length !== triggers.length) {
42
+ const unavailable = disabled.map((trigger) => trigger.dataset_id);
43
+ parts.push(`event-driven \u2014 disabled for ${unavailable.join(", ")}`);
44
+ }
45
+ return parts.join("; ");
25
46
  }
26
47
  function describeRefreshWrites(strategy, mode, key, liveMode) {
27
48
  if (strategy?.startsWith("live_")) return liveMode === "append" ? "append \u2014 each incoming event adds rows" : "replace \u2014 each incoming event replaces the current rows";
@@ -1223,6 +1244,9 @@ var ErdoClient = class {
1223
1244
  getDatasetDeclaredSchema(slug) {
1224
1245
  return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/fetch`, { view: "declared_schema" });
1225
1246
  }
1247
+ getDatasetResourceSchema(slug) {
1248
+ return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/fetch`, { view: "resource_schema" });
1249
+ }
1226
1250
  // Create an empty dataset with its declared columns. The server refuses a
1227
1251
  // create without a schema unless the purpose's columns are platform-defined.
1228
1252
  createDataset(input) {
@@ -7281,7 +7305,7 @@ Read one with: erdo datasets fetch ${res.dataset_slug} --revision <id> --sql "SE
7281
7305
  }
7282
7306
  });
7283
7307
  datasetsCmd.command("schema <slug>").alias("describe").description(
7284
- "Show physical columns by default, or the declared write contract with --declared. Physical columns are the names and types you can reference in `datasets fetch --sql`, read from the stored `data` table itself (DuckDB DESCRIBE) so they are what is actually there, not what was declared. For a database or warehouse dataset, DESCRIBE its real table names instead: `datasets fetch <slug> --sql 'DESCRIBE <table>'`."
7308
+ "Show physical columns by default, or the declared write contract with --declared. File datasets read the stored `data` table; integration datasets show their discovered resource tables and columns. For an integration dataset, choose a resource/table explicitly when fetching: `datasets fetch <slug> --sql 'SELECT * FROM <table>'`."
7285
7309
  ).option("--json", "print the raw JSON result instead of a table").option("--declared", "read the declared write contract (column names, type hints, required flags, semantic roles) instead of the physical columns").action(async (slug, opts) => {
7286
7310
  try {
7287
7311
  const client = new ErdoClient();
@@ -7310,7 +7334,35 @@ datasetsCmd.command("schema <slug>").alias("describe").description(
7310
7334
  );
7311
7335
  return;
7312
7336
  }
7313
- const res = await client.fetchDatasetContents(slug, { sql_query: "DESCRIBE data" });
7337
+ const res = await client.getDatasetResourceSchema(slug);
7338
+ if (!res || typeof res !== "object" || res.view !== "resource_schema")
7339
+ throw new Error("Invalid dataset schema response: the server did not answer the resource_schema view");
7340
+ if (res.schema_provenance === "resource_catalog") {
7341
+ const resources = res.resources ?? [];
7342
+ if (!Array.isArray(resources) || resources.some((resource) => !resource || !Array.isArray(resource.columns)))
7343
+ throw new Error("Invalid dataset schema response: malformed resource catalog");
7344
+ if (opts.json) {
7345
+ print(res);
7346
+ return;
7347
+ }
7348
+ if (!resources.length) {
7349
+ console.log(`${slug} has no discovered integration resources yet.`);
7350
+ return;
7351
+ }
7352
+ const rows = resources.flatMap(
7353
+ (resource) => resource.columns.length ? resource.columns.map((column) => [
7354
+ resource.table_name || resource.key || resource.name,
7355
+ column.name,
7356
+ column.type || ""
7357
+ ]) : [[resource.table_name || resource.key || resource.name, "", "(columns unavailable)"]]
7358
+ );
7359
+ printAlignedTable(["table", "column", "type"], rows);
7360
+ console.log(`
7361
+ Fetch a selected table with: erdo datasets fetch ${slug} --sql "SELECT * FROM <table>"`);
7362
+ return;
7363
+ }
7364
+ if (res.schema_provenance !== "dataset_storage" || !Array.isArray(res.columns) || !Array.isArray(res.rows))
7365
+ throw new Error("Invalid dataset schema response: malformed physical schema");
7314
7366
  if (opts.json) {
7315
7367
  print(res);
7316
7368
  return;
@@ -7439,6 +7491,8 @@ refreshCmd.command("show <slug>").description(
7439
7491
  if (res.experiment_run_id) rows.push(["experiment", res.experiment_run_id]);
7440
7492
  rows.push(["writes", describeRefreshWrites(res.strategy, res.refresh_mode, res.key_column, res.live_write_mode)]);
7441
7493
  rows.push(["trigger", describeRefreshTrigger(res)]);
7494
+ const eventTriggers = describeEventTriggers(res);
7495
+ if (eventTriggers) rows.push(["event triggers", eventTriggers]);
7442
7496
  if (res.schedule) {
7443
7497
  const zone = res.schedule_timezone || "UTC";
7444
7498
  rows.push([
@@ -7489,7 +7543,7 @@ refreshCmd.command("show <slug>").description(
7489
7543
  }
7490
7544
  if (res.job_id) rows.push(["job", res.job_id]);
7491
7545
  printAlignedTable(["setting", "value"], rows);
7492
- if (!res.strategy?.startsWith("live_") && res.job_id && res.job_enabled !== false && !res.disarmed && !res.schedule && !res.refresh_on_view_enabled) {
7546
+ if (!res.strategy?.startsWith("live_") && res.job_id && res.job_enabled !== false && !res.disarmed && !res.schedule && !res.refresh_on_view_enabled && !eventTriggers) {
7493
7547
  console.log(
7494
7548
  `
7495
7549
  Nothing triggers this refresh on its own \u2014 it runs only when asked: erdo datasets refresh run ${res.dataset_slug}`
@@ -7523,7 +7577,7 @@ refreshCmd.command("set <slug>").description(
7523
7577
  ).option("--agent-prompt <textOr@file>", "instructions for the agent_run strategy, inline or @path").option(
7524
7578
  "--context-datasets <csv>",
7525
7579
  "slugs of other datasets the recipe reads besides the one it writes"
7526
- ).option(
7580
+ ).option("--event-datasets <csv>", "dataset slugs whose writes trigger this refresh (up to 16, comma-separated)").option("--event-debounce <seconds>", "wait after the first source write to coalesce a burst (integer 0\u20133600 seconds)").option(
7527
7581
  "--on-view",
7528
7582
  "let a page view trigger this refresh (the platform default); pass --no-on-view to turn it off. Sent only when you pass one of the two"
7529
7583
  ).option("--no-on-view", "stop page views from triggering this refresh").option(
@@ -7586,6 +7640,31 @@ refreshCmd.command("set <slug>").description(
7586
7640
  const slugs = opts.contextDatasets.split(",").map((s) => s.trim()).filter(Boolean);
7587
7641
  if (slugs.length) body.context_dataset_slugs = slugs;
7588
7642
  }
7643
+ let eventDatasetSlugs;
7644
+ if (opts.eventDatasets !== void 0) {
7645
+ eventDatasetSlugs = opts.eventDatasets.split(",").map((s) => s.trim());
7646
+ if (!eventDatasetSlugs.length || eventDatasetSlugs.some((slug2) => !slug2)) {
7647
+ fail(new Error("--event-datasets must include at least one non-blank dataset slug"));
7648
+ return;
7649
+ }
7650
+ if (eventDatasetSlugs.length > 16) {
7651
+ fail(new Error("--event-datasets supports at most 16 dataset slugs"));
7652
+ return;
7653
+ }
7654
+ body.event_dataset_slugs = eventDatasetSlugs;
7655
+ }
7656
+ if (opts.eventDebounce !== void 0) {
7657
+ const value = Number(opts.eventDebounce);
7658
+ if (!opts.eventDebounce.trim() || !Number.isFinite(value) || !Number.isInteger(value) || value < 0 || value > 3600) {
7659
+ fail(new Error("--event-debounce must be a finite integer between 0 and 3600"));
7660
+ return;
7661
+ }
7662
+ if (!eventDatasetSlugs?.length) {
7663
+ fail(new Error("--event-debounce requires non-empty --event-datasets"));
7664
+ return;
7665
+ }
7666
+ body.event_debounce_seconds = value;
7667
+ }
7589
7668
  if (opts.onView !== void 0) body.refresh_on_view_enabled = opts.onView;
7590
7669
  if (opts.staleAfter !== void 0) {
7591
7670
  body.refresh_on_view_stale_after_seconds = opts.staleAfter;
@@ -7606,7 +7685,13 @@ refreshCmd.command("set <slug>").description(
7606
7685
  `Installed a ${res.strategy} refresh on ${res.dataset_slug}${res.previous_strategy ? ` (replacing ${res.previous_strategy})` : ""}`
7607
7686
  );
7608
7687
  console.log(`writes: ${describeRefreshWrites(res.strategy, res.refresh_mode, body.key_column, body.live_write_mode)}`);
7609
- console.log(describeRefreshTrigger({ ...res, refresh_on_view_enabled: body.refresh_on_view_enabled }));
7688
+ console.log(describeRefreshTrigger({
7689
+ ...res,
7690
+ event_dataset_ids: res.event_dataset_ids,
7691
+ event_debounce_seconds: res.event_debounce_seconds ?? body.event_debounce_seconds,
7692
+ refresh_on_view_enabled: body.refresh_on_view_enabled,
7693
+ test_run_success: res.test_run?.success
7694
+ }));
7610
7695
  if (res.job_id) console.log(`job: ${res.job_id}`);
7611
7696
  if (res.webhook_url) {
7612
7697
  console.log(`webhook url: ${res.webhook_url}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.139.0",
3
+ "version": "0.140.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {