@datarails-ai/dr-cli 1.6.115 → 1.6.117
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.
|
|
11
|
+
version: "1.6.117",
|
|
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-
|
|
72
|
+
var BUILD_TIME = "2026-10-12T08:59:33.211Z";
|
|
73
73
|
function formatBuildTime(iso) {
|
|
74
74
|
if (iso === "") return "";
|
|
75
75
|
const d = new Date(iso);
|
|
@@ -31273,6 +31273,61 @@ async function runPreviewData(cfg) {
|
|
|
31273
31273
|
const result = await previewData(cfg.client, body, { signal: cfg.signal });
|
|
31274
31274
|
writeJSONIndented(cfg.out, result);
|
|
31275
31275
|
}
|
|
31276
|
+
async function findOrdinalCollisions(client, mapperId, docIds, signal) {
|
|
31277
|
+
const details = await getDetails2(client, mapperId, { signal });
|
|
31278
|
+
const tableId = details.template.id;
|
|
31279
|
+
const mappers = await list20(client, tableId, { signal });
|
|
31280
|
+
const self = mappers.find((m) => m.id === mapperId);
|
|
31281
|
+
if (self === void 0) {
|
|
31282
|
+
return null;
|
|
31283
|
+
}
|
|
31284
|
+
const ordinal = self.ordinal ?? null;
|
|
31285
|
+
const wanted = new Set(docIds);
|
|
31286
|
+
const others = mappers.filter(
|
|
31287
|
+
(m) => m.id !== mapperId && (m.ordinal ?? null) === ordinal && (m.mapped_documents_ids ?? []).some((d) => wanted.has(d))
|
|
31288
|
+
);
|
|
31289
|
+
return { tableId, ordinal, others };
|
|
31290
|
+
}
|
|
31291
|
+
async function warnOrdinalCollisions(cfg, body) {
|
|
31292
|
+
const err = cfg.err ?? process.stderr;
|
|
31293
|
+
const raw = body["selected_document_ids"];
|
|
31294
|
+
const docIds = (Array.isArray(raw) ? raw : []).map((d) => {
|
|
31295
|
+
const n = typeof d === "string" && /^\s*\d+\s*$/.test(d) ? Number(d) : d;
|
|
31296
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
|
|
31297
|
+
throw new Error(
|
|
31298
|
+
`selected_document_ids must hold positive integer document ids, got ${JSON.stringify(d)}`
|
|
31299
|
+
);
|
|
31300
|
+
}
|
|
31301
|
+
return n;
|
|
31302
|
+
});
|
|
31303
|
+
if (docIds.length === 0) {
|
|
31304
|
+
return;
|
|
31305
|
+
}
|
|
31306
|
+
let found;
|
|
31307
|
+
try {
|
|
31308
|
+
found = await findOrdinalCollisions(cfg.client, cfg.mapperId, docIds, cfg.signal);
|
|
31309
|
+
} catch (e) {
|
|
31310
|
+
if (cfg.signal.aborted) {
|
|
31311
|
+
throw e;
|
|
31312
|
+
}
|
|
31313
|
+
err.write(
|
|
31314
|
+
`note: could not check for other mappers on the same ordinal: ${e.message}
|
|
31315
|
+
`
|
|
31316
|
+
);
|
|
31317
|
+
return;
|
|
31318
|
+
}
|
|
31319
|
+
if (found === null) {
|
|
31320
|
+
return;
|
|
31321
|
+
}
|
|
31322
|
+
const ordinal = found.ordinal === null ? "null" : String(found.ordinal);
|
|
31323
|
+
for (const other of found.others) {
|
|
31324
|
+
const shared = (other.mapped_documents_ids ?? []).filter((d) => docIds.includes(d));
|
|
31325
|
+
err.write(
|
|
31326
|
+
`warning: mapper ${cfg.mapperId} and mapper ${other.id} (${other.name}) share ordinal ${ordinal} on table ${found.tableId}, so the server treats them as versions of one mapper. Binding ${cfg.mapperId} to document(s) [${shared.join(", ")}] unbinds them from ${other.id}; a mapper left with no documents and no processed versions is deleted with its rows. To load both side by side, re-create one with \`dr datamappers create\` (a new mapper gets the next free ordinal).
|
|
31327
|
+
`
|
|
31328
|
+
);
|
|
31329
|
+
}
|
|
31330
|
+
}
|
|
31276
31331
|
async function runUpdateBinding(cfg) {
|
|
31277
31332
|
if (cfg.mapperId <= 0) {
|
|
31278
31333
|
throw new Error("mapper id must be positive");
|
|
@@ -31297,6 +31352,7 @@ async function runUpdateBinding(cfg) {
|
|
|
31297
31352
|
body["rescan"] = r;
|
|
31298
31353
|
}
|
|
31299
31354
|
}
|
|
31355
|
+
await warnOrdinalCollisions(cfg, body);
|
|
31300
31356
|
const result = await updateBinding(cfg.client, cfg.mapperId, body, {
|
|
31301
31357
|
signal: cfg.signal
|
|
31302
31358
|
});
|
|
@@ -31456,6 +31512,18 @@ async function runDatamappersCreate(cfg) {
|
|
|
31456
31512
|
"merge"
|
|
31457
31513
|
);
|
|
31458
31514
|
}
|
|
31515
|
+
const rawDatamapperId = body["datamapper_id"];
|
|
31516
|
+
const datamapperId = rawDatamapperId == null ? 0 : Number(rawDatamapperId);
|
|
31517
|
+
if (!Number.isInteger(datamapperId) || datamapperId < 0) {
|
|
31518
|
+
throw new Error(
|
|
31519
|
+
`body datamapper_id must be a mapper id (a non-negative integer), got ${JSON.stringify(rawDatamapperId)} \u2014 remove it to create a new mapper`
|
|
31520
|
+
);
|
|
31521
|
+
}
|
|
31522
|
+
const publishesVersion = datamapperId !== 0;
|
|
31523
|
+
const requestedOrdinal = body["ordinal"];
|
|
31524
|
+
if (!publishesVersion && requestedOrdinal == null) {
|
|
31525
|
+
body["ordinal"] = 0;
|
|
31526
|
+
}
|
|
31459
31527
|
const { decoded: decodedCreate, changeCount: createChangeCount } = decodeBodyEntitiesDeep(body);
|
|
31460
31528
|
if (createChangeCount > 0) {
|
|
31461
31529
|
process.stderr.write(
|
|
@@ -31473,6 +31541,12 @@ async function runDatamappersCreate(cfg) {
|
|
|
31473
31541
|
} else {
|
|
31474
31542
|
renderMappersTable(cfg.out, [created]);
|
|
31475
31543
|
}
|
|
31544
|
+
if (!publishesVersion && typeof requestedOrdinal === "number" && typeof created.ordinal === "number" && created.ordinal !== requestedOrdinal) {
|
|
31545
|
+
(cfg.err ?? process.stderr).write(
|
|
31546
|
+
`note: the server gave mapper ${created.id} ordinal ${created.ordinal}, not ${requestedOrdinal}: a new mapper always takes the next free ordinal on the table. To publish a new version of an existing mapper (same ordinal), use \`dr datamappers update\`.
|
|
31547
|
+
`
|
|
31548
|
+
);
|
|
31549
|
+
}
|
|
31476
31550
|
const declaredCalcNames = extractCalcFields(body).map((c) => c.name).filter((n) => n !== "");
|
|
31477
31551
|
if (declaredCalcNames.length > 0 && rescanValue !== "") {
|
|
31478
31552
|
const result = await verifyCalcFieldsMaterialise(
|
|
@@ -32329,7 +32403,7 @@ function registerDatamappersCommand(root) {
|
|
|
32329
32403
|
addServerFlag(previewCmd);
|
|
32330
32404
|
addAccountFlag(previewCmd);
|
|
32331
32405
|
const bindCmd = dm.command("bind").description(
|
|
32332
|
-
'Update which document IDs a mapper is bound to. Posts to `POST /api/templates/mappers/<id>/binding` with `{selected_document_ids, rescan?}`. The legacy webclient\'s "Mapping table with documents" modal calls this when the operator checks/unchecks docs in the binding picker.'
|
|
32406
|
+
'Update which document IDs a mapper is bound to. Posts to `POST /api/templates/mappers/<id>/binding` with `{selected_document_ids, rescan?}`. The legacy webclient\'s "Mapping table with documents" modal calls this when the operator checks/unchecks docs in the binding picker. Warns first when another mapper of the table on the same ordinal is bound to one of those docs: mappers that share an ordinal are versions of one mapper, so the bind unbinds the other.'
|
|
32333
32407
|
).argument("<mapper_id>", "mapper id (positive integer)").option("--doc <id>", "Document id to bind (positive integer; repeatable)", collectInts, []).option(
|
|
32334
32408
|
"--rescan <mode>",
|
|
32335
32409
|
"Trigger a re-extraction: `latest` or `all` (default: omit; no rescan)",
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.117",
|
|
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": {
|
|
@@ -61,7 +61,8 @@ operator's own org**. `dr integrations add` is the one exception: its payload na
|
|
|
61
61
|
explicitly, so the **datasource lands in the right org while the filebox lands out of tenant**. Everything
|
|
62
62
|
downstream then looks healthy — the binding resolves, the sync completes, a version lands — but it landed
|
|
63
63
|
in the wrong org and the customer never sees the filebox. `dr connectors` (control-plane) accepts the
|
|
64
|
-
impersonated JWT and takes its org from it as well, so `--org-id` can be omitted there
|
|
64
|
+
impersonated JWT and takes its org from it as well, so `--org-id` can be omitted there (an `--org-id`
|
|
65
|
+
for another org is refused 403). Beyond the
|
|
65
66
|
breakage, creating customer resources under the operator's own identity breaks resource-level scoping and
|
|
66
67
|
loses the audited operator→target trail (`tech/security.md` §2.2, §8).
|
|
67
68
|
|
|
@@ -758,9 +759,10 @@ POST gets `401`; the port-forward also serves the SPA). Use the dr-cli, which ca
|
|
|
758
759
|
to the internal control-plane (`dr connectors connector create` / `query create` — added 2026-07;
|
|
759
760
|
older skill copies show the now-broken curl):
|
|
760
761
|
```bash
|
|
761
|
-
# a) Connector (creds source). --org-id
|
|
762
|
-
# impersonated customer identity that IS the customer's org
|
|
763
|
-
|
|
762
|
+
# a) Connector (creds source). Omit --org-id: the control plane creates the connector in the caller's
|
|
763
|
+
# JWT org, and under the impersonated customer identity that IS the customer's org. --org-id cannot
|
|
764
|
+
# override it: another org is refused 403, and a token with no org claim gets 400. Note the returned id.
|
|
765
|
+
dr connectors connector create --secret-ref <datasource_id> --json $ACC
|
|
764
766
|
# b) ConnectorQuery (the binding) — connector-id from (a); filebox-uuid + filebox-id from step 3b
|
|
765
767
|
# (filebox create). --filebox-id is the numeric doc id (MANDATORY; the Sync-query editor's write
|
|
766
768
|
# key — no uuid->id resolver on the monolith). It's the same <filebox_id> used in steps 3a/5.
|
|
@@ -35,6 +35,21 @@ the matching subcommand. `datamappers create <flat|dimension|multiple|from-file>
|
|
|
35
35
|
Required body fields: `name`, `document_version_id`, `selected_document_ids`
|
|
36
36
|
(GET returns this renamed as `mapped_documents_ids`), `config`, `sheet_name`.
|
|
37
37
|
|
|
38
|
+
**Ordinal: mappers that share one are versions of one mapper.** Each mapper has
|
|
39
|
+
an `ordinal` (the ORDINAL column of `datamappers list --table <id>`). The server
|
|
40
|
+
binds documents per (table, ordinal): binding a mapper to a document moves that
|
|
41
|
+
document off any other mapper of the table on the same ordinal, and a mapper left
|
|
42
|
+
with no documents and no processed versions is then deleted with its rows. Two
|
|
43
|
+
different extractions from one filebox (for example two tabs of one workbook)
|
|
44
|
+
need two ordinals. `create` takes care of it: a new mapper always gets the
|
|
45
|
+
table's next free ordinal, which the server picks itself (any `ordinal` in a
|
|
46
|
+
`from-file` body is replaced, and dr notes the value it got). A body with no
|
|
47
|
+
ordinal, or a null one, is sent with `ordinal: 0` because the server would
|
|
48
|
+
otherwise store it as 0, a version of whichever mapper already holds 0.
|
|
49
|
+
A body with `datamapper_id` publishes a new version and keeps its ordinal as
|
|
50
|
+
sent; use `update` for that. `bind` warns, then binds anyway, when another
|
|
51
|
+
mapper of the table on the same ordinal is bound to one of the documents.
|
|
52
|
+
|
|
38
53
|
**Schema-bootstrap shortcut:** mapping `map_to: "<field>"` to a column the target
|
|
39
54
|
template doesn't have yet auto-adds that field on the table (plus 4 system
|
|
40
55
|
scenario fields on the first mapper). This is the documented way to declare
|
|
@@ -87,7 +102,8 @@ value-inference behavior. `NAME` is the target **table field** name (the
|
|
|
87
102
|
- `datamappers preview --payload-file <p>` — renders mapper output for sample
|
|
88
103
|
input ("what would this extract?") before saving.
|
|
89
104
|
- `datamappers bind <mapper_id> --doc <id> [--rescan latest|all]` — change which
|
|
90
|
-
docs a mapper is bound to.
|
|
105
|
+
docs a mapper is bound to. A doc bound to another mapper of the table on the
|
|
106
|
+
same ordinal moves to this one (see *Ordinal* above), and dr warns first.
|
|
91
107
|
|
|
92
108
|
### `update --field NAME:TYPE` — set column types (materialising calc columns)
|
|
93
109
|
|
|
@@ -15,7 +15,7 @@ key attributes, and emits a short-lived confirmation token. Re-run with
|
|
|
15
15
|
file between plan and confirm invalidates the token.
|
|
16
16
|
- The printed confirm command echoes the plan invocation's `--account`, so on
|
|
17
17
|
a keychain holding several identities for the server it can be pasted
|
|
18
|
-
verbatim — no need to re-add the flag by hand
|
|
18
|
+
verbatim — no need to re-add the flag by hand.
|
|
19
19
|
|
|
20
20
|
Gated commands include: `dashboards delete`, `datamappers delete`,
|
|
21
21
|
`dynamic-ranges delete`, `extables delete`, `functions delete`, `lut delete`,
|