@datarails-ai/dr-cli 1.6.121 → 1.6.122

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/cli.js CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@datarails-ai/dr-cli",
11
- version: "1.6.121",
11
+ version: "1.6.122",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-12T10:41:54.439Z";
72
+ var BUILD_TIME = "2026-10-12T10:51:59.680Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -31029,6 +31029,96 @@ function formatCalcFieldError(name, preview2, full) {
31029
31029
  return `${head} looks like placeholder text, not a real expression. The server's ETL compiler rejects formulas with no [field] reference, "quoted" literal, function() call, or numeric value \u2014 replace it before retrying. Common cause: a GET \u2192 PUT round-trip on a pre-kickoff starter mapper carried the placeholder text forward.`;
31030
31030
  }
31031
31031
 
31032
+ // src/commands/_mapperConfigShape.ts
31033
+ var OBJECT_LIST_KEYS = [
31034
+ "header",
31035
+ "dimension",
31036
+ "footer",
31037
+ "attributes",
31038
+ "calculated_fields",
31039
+ "fx_conversions",
31040
+ "table_id"
31041
+ ];
31042
+ var GET_HINT = '`dr datamappers get <mapper_id> --json` shows the shape the server expects (a mapper with no anchor carries "table_id": []).';
31043
+ function describeValue(v) {
31044
+ if (v === null) return "null";
31045
+ if (Array.isArray(v)) return "an array";
31046
+ switch (typeof v) {
31047
+ case "number":
31048
+ return `a number (${String(v)})`;
31049
+ case "string":
31050
+ return `a string (${JSON.stringify(v.length > 40 ? `${v.slice(0, 40)}\u2026` : v)})`;
31051
+ case "boolean":
31052
+ return `a boolean (${String(v)})`;
31053
+ case "object":
31054
+ return "an object";
31055
+ default:
31056
+ return typeof v;
31057
+ }
31058
+ }
31059
+ function isPlainObject2(v) {
31060
+ return v !== null && typeof v === "object" && !Array.isArray(v);
31061
+ }
31062
+ function tableIdProblems(value) {
31063
+ if (!Array.isArray(value)) {
31064
+ return [
31065
+ `config.table_id must be an array, got ${describeValue(value)}. It is the table anchor, not the table id: [] for none, or one cell object such as [{"coordinate": "A1", "value": "<text in that cell>", "match_type": "any_text"}]. The table id goes on the command line, not in the body.`
31066
+ ];
31067
+ }
31068
+ const out = [];
31069
+ if (value.length > 1) {
31070
+ out.push(
31071
+ `config.table_id holds ${value.length} entries; the server allows at most one anchor ("Only one anchor is allowed").`
31072
+ );
31073
+ }
31074
+ value.forEach((entry, i) => {
31075
+ if (!isPlainObject2(entry)) {
31076
+ out.push(`config.table_id[${i}] must be an object, got ${describeValue(entry)}.`);
31077
+ } else if (typeof entry["match_type"] !== "string") {
31078
+ out.push(
31079
+ `config.table_id[${i}].match_type is required (a string such as "any_text" or "location").`
31080
+ );
31081
+ }
31082
+ });
31083
+ return out;
31084
+ }
31085
+ function validateMapperConfigShape(config) {
31086
+ if (config === void 0) return;
31087
+ if (!isPlainObject2(config)) {
31088
+ throw new Error(`config must be a JSON object, got ${describeValue(config)}. ${GET_HINT}`);
31089
+ }
31090
+ const problems = [];
31091
+ for (const key of OBJECT_LIST_KEYS) {
31092
+ if (!(key in config)) continue;
31093
+ const value = config[key];
31094
+ if (key === "table_id") {
31095
+ problems.push(...tableIdProblems(value));
31096
+ continue;
31097
+ }
31098
+ if (!Array.isArray(value)) {
31099
+ problems.push(`config.${key} must be an array of objects, got ${describeValue(value)}.`);
31100
+ continue;
31101
+ }
31102
+ value.forEach((entry, i) => {
31103
+ if (!isPlainObject2(entry)) {
31104
+ problems.push(`config.${key}[${i}] must be an object, got ${describeValue(entry)}.`);
31105
+ }
31106
+ });
31107
+ }
31108
+ if ("include_sheets" in config && !Array.isArray(config["include_sheets"])) {
31109
+ problems.push(
31110
+ `config.include_sheets must be an array, got ${describeValue(config["include_sheets"])}.`
31111
+ );
31112
+ }
31113
+ if (problems.length > 0) {
31114
+ throw new Error(
31115
+ `invalid datamapper config (the server would answer an unattributed HTTP 500 / "Unknown error"):
31116
+ - ${problems.join("\n - ")}
31117
+ ${GET_HINT}`
31118
+ );
31119
+ }
31120
+ }
31121
+
31032
31122
  // src/commands/_errorLog.ts
31033
31123
  import { promises as fs14 } from "fs";
31034
31124
  import * as os3 from "os";
@@ -31289,6 +31379,7 @@ async function runPreviewData(cfg) {
31289
31379
  throw new Error("--payload-file is required");
31290
31380
  }
31291
31381
  let body = await readJsonPayload(payloadFile);
31382
+ validateMapperConfigShape(body["config"]);
31292
31383
  const { decoded, changeCount } = decodeBodyEntitiesDeep(body);
31293
31384
  if (changeCount > 0) {
31294
31385
  body = decoded;
@@ -31485,7 +31576,12 @@ async function runDatamappersCreate(cfg) {
31485
31576
  parseKeyEqualsValue(cfg.flags.calcs, "--calc");
31486
31577
  parseKeyEqualsValue(cfg.flags.attrs, "--attr");
31487
31578
  parseKeyEqualsValue(cfg.flags.dims, "--dim");
31488
- parseKeyEqualsValue(cfg.flags.tableIdCells ?? [], "--table-id-cell");
31579
+ const tableIdCells = parseKeyEqualsValue(cfg.flags.tableIdCells ?? [], "--table-id-cell");
31580
+ if (tableIdCells.length > 1) {
31581
+ throw new Error(
31582
+ `--table-id-cell takes one cell; got ${tableIdCells.length}. The server allows one table anchor per mapper.`
31583
+ );
31584
+ }
31489
31585
  parseKeyEqualsValue(cfg.flags.footers, "--footer");
31490
31586
  parseKeyEqualsValue(cfg.flags.renames, "--rename");
31491
31587
  }
@@ -31493,6 +31589,7 @@ async function runDatamappersCreate(cfg) {
31493
31589
  let body;
31494
31590
  if (payloadFile !== "") {
31495
31591
  body = await readJsonPayload(payloadFile);
31592
+ validateMapperConfigShape(body["config"]);
31496
31593
  } else {
31497
31594
  body = await buildConveniencePayload(
31498
31595
  cfg.client,
@@ -31590,6 +31687,7 @@ async function runDatamappersUpdate(cfg) {
31590
31687
  }
31591
31688
  const rescanValue = resolveRescan(cfg.rescan, cfg.noRescan);
31592
31689
  let body = await readJsonPayload(payloadFile);
31690
+ validateMapperConfigShape(body["config"]);
31593
31691
  if (rescanValue === "") {
31594
31692
  delete body["rescan"];
31595
31693
  } else {
@@ -32229,7 +32327,7 @@ function registerDatamappersCommand(root) {
32229
32327
  addAccountFlag(dimensionCmd);
32230
32328
  const multipleCmd = addConvenienceFlags(
32231
32329
  createCmd.command("multiple").description(
32232
- "Create a multiple-tables datamapper (requires --dim \u2265 1 AND --table-id-cell \u2265 1)"
32330
+ "Create a multiple-tables datamapper (requires --dim \u2265 1 AND one --table-id-cell)"
32233
32331
  ).argument("<table_id>", "Table ID (positive integer)")
32234
32332
  ).requiredOption(
32235
32333
  "--dim <Coord=Label>",
@@ -32238,7 +32336,7 @@ function registerDatamappersCommand(root) {
32238
32336
  []
32239
32337
  ).requiredOption(
32240
32338
  "--table-id-cell <Coord=Label>",
32241
- "Add table_id entry \u2014 points at the cell carrying a per-section table identifier. Repeatable; at least one is required.",
32339
+ "The table_id (anchor) cell \u2014 the cell carrying the per-section table identifier. Required; the server allows exactly one.",
32242
32340
  collectArrayOpt,
32243
32341
  []
32244
32342
  ).action(async (tableIdStr, opts) => {
@@ -56637,7 +56735,7 @@ async function readHiddenRulesPayload(path12) {
56637
56735
  raw.forEach((entry, idx) => {
56638
56736
  const ctx = `rules[${idx}]`;
56639
56737
  const base = validateCreate(entry, ctx);
56640
- if (!isPlainObject2(entry)) {
56738
+ if (!isPlainObject3(entry)) {
56641
56739
  throw new Error(`--file: ${ctx} must be a JSON object`);
56642
56740
  }
56643
56741
  const ctxnId = entry["customer_transaction_id"];
@@ -56696,11 +56794,11 @@ function parseReorderShorthand(raw) {
56696
56794
  }
56697
56795
  return { rules: entries };
56698
56796
  }
56699
- function isPlainObject2(v) {
56797
+ function isPlainObject3(v) {
56700
56798
  return typeof v === "object" && v !== null && !Array.isArray(v);
56701
56799
  }
56702
56800
  function validateCreate(raw, ctx) {
56703
- if (!isPlainObject2(raw)) {
56801
+ if (!isPlainObject3(raw)) {
56704
56802
  throw new Error(`--file: ${ctx} must be a JSON object`);
56705
56803
  }
56706
56804
  if (!Array.isArray(raw["conditions"]) || raw["conditions"].length === 0) {
@@ -56756,7 +56854,7 @@ function validateCreate(raw, ctx) {
56756
56854
  return out;
56757
56855
  }
56758
56856
  function validateCondition(raw, ctx) {
56759
- if (!isPlainObject2(raw)) {
56857
+ if (!isPlainObject3(raw)) {
56760
56858
  throw new Error(`--file: ${ctx} must be a JSON object`);
56761
56859
  }
56762
56860
  const inputField = raw["input_field"];
@@ -56821,7 +56919,7 @@ function validateArgsForOperation(op, args, ctx) {
56821
56919
  }
56822
56920
  }
56823
56921
  function validateReorder(raw, ctx) {
56824
- if (!isPlainObject2(raw)) {
56922
+ if (!isPlainObject3(raw)) {
56825
56923
  throw new Error(`--file: ${ctx} must be a JSON object`);
56826
56924
  }
56827
56925
  if (!Array.isArray(raw["rules"]) || raw["rules"].length === 0) {
@@ -56830,7 +56928,7 @@ function validateReorder(raw, ctx) {
56830
56928
  const seenIds = /* @__PURE__ */ new Set();
56831
56929
  const entries = [];
56832
56930
  raw["rules"].forEach((entry, idx) => {
56833
- if (!isPlainObject2(entry)) {
56931
+ if (!isPlainObject3(entry)) {
56834
56932
  throw new Error(`--file: ${ctx}.rules[${idx}] must be a JSON object`);
56835
56933
  }
56836
56934
  const id = entry["id"];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "1.6.121",
3
+ "cliVersion": "1.6.122",
4
4
  "_contract": "Resolve a command path through `aliases` (canonical prefix + alias segment -> canonical path) before any lookup. A path listed in `pendingCommands`, or whose first segment is a key of `pending`, is UNDECIDED, not unrestricted: fail closed or skip it, and never read a path's absence from `commands` as 'no requirement'. An entry carrying `denied` is UNAVAILABLE to any AGENT-SCOPED credential — one whose `scopes` claim carries at least one `resource:action` entry; a claim that is absent, empty, capability-only or wholly unparseable is UNRESTRICTED, and this mark does not apply to it — whatever else that entry says: it is not a ceiling and it is not always-available, its `denied.reason` says why and `denied.until` names the ticket that lifts it, and the absence of `requires` on it must NEVER be read as 'no requirement'. Only an entry in `commands` with an empty `requires` means always-available.",
5
5
  "commands": {
6
6
  "login": {
@@ -26,7 +26,8 @@ the matching subcommand. `datamappers create <flat|dimension|multiple|from-file>
26
26
  `--version` — `--version` is reserved by the global `dr --version` and silently
27
27
  no-ops the subcommand.
28
28
  - `dimension` adds required `--dim Coord=Label` (≥1).
29
- - `multiple` adds required `--dim` and `--table-id-cell Coord=Label` (≥1).
29
+ - `multiple` adds required `--dim` and one `--table-id-cell Coord=Label` (the
30
+ server allows a single table anchor; a second one is refused).
30
31
  - `from-file --file <path>` is the power mode — it ships the JSON body without
31
32
  the convenience-flag validation, but **not** strictly verbatim. Three things
32
33
  still rewrite it: `--rescan`/`--no-rescan` set or strip `rescan`; a source
@@ -35,6 +36,27 @@ the matching subcommand. `datamappers create <flat|dimension|multiple|from-file>
35
36
  Required body fields: `name`, `document_version_id`, `selected_document_ids`
36
37
  (GET returns this renamed as `mapped_documents_ids`), `config`, `sheet_name`.
37
38
 
39
+ **`config` shape: `table_id` is not the table id (DR-54216).** `config.table_id`
40
+ is the table *anchor*: `[]` for none (what `datamappers get --json` emits), or a
41
+ list with exactly one cell object, e.g.
42
+ `[{"coordinate": "A1", "value": "<text in that cell>", "match_type": "any_text"}]`.
43
+ The table id goes on the command line. The server does not check `config`, so a
44
+ wrong type gets a bare HTTP 500 on create and "Unknown error while processing
45
+ data for preview" on preview, with no field named. `create` (payload-file /
46
+ from-file), `update` and `preview` therefore check these keys before sending and
47
+ fail naming the field:
48
+
49
+ | Key | Must be |
50
+ |-----|---------|
51
+ | `table_id` | array of at most one object, with a `match_type` string |
52
+ | `header`, `dimension`, `footer`, `attributes` | array of cell objects |
53
+ | `calculated_fields`, `fx_conversions` | array of objects |
54
+ | `include_sheets` | array |
55
+
56
+ An absent key is fine; `null`, a number, a string or a list of strings is not.
57
+ Writing a body from scratch, start from `datamappers get <mapper_id> --json` of a
58
+ similar mapper.
59
+
38
60
  **Ordinal: mappers that share one are versions of one mapper.** Each mapper has
39
61
  an `ordinal` (the ORDINAL column of `datamappers list --table <id>`). The server
40
62
  binds documents per (table, ordinal): binding a mapper to a document moves that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-cli",
3
- "version": "1.6.121",
3
+ "version": "1.6.122",
4
4
  "description": "Datarails CLI",
5
5
  "type": "module",
6
6
  "engines": {