@graphit/cli 0.2.113 → 0.2.136
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +2 -0
- package/bin/graphit +1 -1
- package/bin/graphit.ps1 +1 -1
- package/dist/commands/connector.js +66 -2
- package/dist/commands/connector.js.map +1 -1
- package/dist/commands/ds-config.d.ts +45 -0
- package/dist/commands/ds-config.js +122 -0
- package/dist/commands/ds-config.js.map +1 -0
- package/dist/commands/ds.d.ts +0 -45
- package/dist/commands/ds.js +4 -128
- package/dist/commands/ds.js.map +1 -1
- package/dist/commands/governance.js +1 -17
- package/dist/commands/governance.js.map +1 -1
- package/dist/commands/kb-create.js +2 -2
- package/dist/commands/kb-create.js.map +1 -1
- package/dist/commands/kb-update.js +2 -2
- package/dist/commands/kb-update.js.map +1 -1
- package/dist/commands/query.d.ts +38 -0
- package/dist/commands/query.js +145 -18
- package/dist/commands/query.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/skill-guard.d.ts +4 -0
- package/dist/skill-guard.js +75 -0
- package/dist/skill-guard.js.map +1 -0
- package/package.json +1 -1
- package/scripts/plugin-status.mjs +48 -0
- package/skills/graphit/SKILL.md +15 -14
- package/skills/graphit/VERSION.json +1 -1
- package/skills/graphit/cursor/graphit-sql-reference.mdc +2 -2
- package/skills/graphit/graphit.mdc +6 -9
- package/skills/graphit/references/data-sources.md +7 -3
- package/skills/graphit/references/governance-explained.md +2 -2
- package/skills/graphit/references/governance.md +22 -17
- package/skills/graphit/references/kb-actions.md +2 -3
- package/skills/graphit/references/kb-traversal.md +1 -1
- package/skills/graphit/references/runtime.md +5 -1
- package/skills/graphit/references/sql-reference.md +15 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Graphit CLI plugin for AI coding assistants",
|
|
10
|
-
"version": "0.2.
|
|
10
|
+
"version": "0.2.136"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"source": {
|
|
17
17
|
"source": "npm",
|
|
18
18
|
"package": "@graphit/cli",
|
|
19
|
-
"version": "0.2.
|
|
19
|
+
"version": "0.2.136"
|
|
20
20
|
},
|
|
21
|
-
"version": "0.2.
|
|
21
|
+
"version": "0.2.136",
|
|
22
22
|
"category": "data-visualization",
|
|
23
23
|
"tags": [
|
|
24
24
|
"bi",
|
package/README.md
CHANGED
|
@@ -51,6 +51,8 @@ graphit setup
|
|
|
51
51
|
|
|
52
52
|
Either path installs the Graphit skill so the assistant can drive the CLI on your behalf.
|
|
53
53
|
|
|
54
|
+
> **Start a new session after installing the plugin.** Claude Code (and Codex) load plugin skills and commands at session start, so the Graphit skill, `/graphit`, and the `graphit` commands are not available in the session you installed from. Open a new session first - otherwise the assistant will not have the skill loaded and will improvise instead of following the guided workflow.
|
|
55
|
+
|
|
54
56
|
## Authenticate
|
|
55
57
|
|
|
56
58
|
```bash
|
package/bin/graphit
CHANGED
|
@@ -14,7 +14,7 @@ if [ -z "${GRAPHIT_PLUGIN_ROOT:-}" ]; then
|
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
16
|
# graphit:floor (stamped by scripts/sync-plugin-version.mjs from cli/package.json)
|
|
17
|
-
FLOOR_VERSION="0.2.
|
|
17
|
+
FLOOR_VERSION="0.2.136"
|
|
18
18
|
|
|
19
19
|
PACKAGE_NAME="@graphit/cli"
|
|
20
20
|
# Strict semver: anything else is rejected so a tampered cache cannot inject.
|
package/bin/graphit.ps1
CHANGED
|
@@ -7,7 +7,7 @@ if (-not $env:GRAPHIT_PLUGIN_ROOT) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
# graphit:floor (stamped by scripts/sync-plugin-version.mjs from cli/package.json)
|
|
10
|
-
$FloorVersion = "0.2.
|
|
10
|
+
$FloorVersion = "0.2.136"
|
|
11
11
|
|
|
12
12
|
$PackageName = "@graphit/cli"
|
|
13
13
|
# Strict semver: anything else is rejected so a tampered cache cannot inject.
|
|
@@ -1,18 +1,48 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { apiClient } from "../api/client.js";
|
|
3
3
|
import { output, errorOutput } from "../output/format.js";
|
|
4
|
+
// Mirrors the backend SA-key required fields; the client-side check gives fast
|
|
5
|
+
// feedback (bad path / not JSON / wrong key file) before the network call. The
|
|
6
|
+
// backend re-validates authoritatively.
|
|
7
|
+
const SA_KEY_REQUIRED_FIELDS = [
|
|
8
|
+
"type", "project_id", "private_key_id", "private_key",
|
|
9
|
+
"client_email", "client_id", "auth_uri", "token_uri",
|
|
10
|
+
];
|
|
11
|
+
function parseServiceAccountKey(raw) {
|
|
12
|
+
let key;
|
|
13
|
+
try {
|
|
14
|
+
key = JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error("Invalid JSON in key file. Pass the full service-account key file with --key-file.");
|
|
18
|
+
}
|
|
19
|
+
if (typeof key !== "object" || key === null || Array.isArray(key)) {
|
|
20
|
+
throw new Error("Service-account key must be a JSON object.");
|
|
21
|
+
}
|
|
22
|
+
if (key.type !== "service_account") {
|
|
23
|
+
throw new Error(`Expected key type 'service_account', got '${key.type ?? "(missing)"}'.`);
|
|
24
|
+
}
|
|
25
|
+
const missing = SA_KEY_REQUIRED_FIELDS.filter((f) => !(f in key));
|
|
26
|
+
if (missing.length > 0) {
|
|
27
|
+
throw new Error(`Service-account key is missing required fields: ${missing.sort().join(", ")}`);
|
|
28
|
+
}
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
4
31
|
export function registerConnectorCommands(program) {
|
|
5
32
|
const connector = program
|
|
6
33
|
.command("connector")
|
|
7
34
|
.description("Connection management. OAuth and GitHub connections are set up in the Graphit web app.");
|
|
8
35
|
connector
|
|
9
36
|
.command("list")
|
|
10
|
-
.description("List active connections")
|
|
37
|
+
.description("List active connections (Snowflake + BigQuery)")
|
|
11
38
|
.action(async function () {
|
|
12
39
|
try {
|
|
13
40
|
const resp = await apiClient.get("/api/v1/cli/connectors");
|
|
41
|
+
// Warehouse-agnostic columns: `type` distinguishes the warehouse;
|
|
42
|
+
// per-type fields (account/warehouse vs project/dataset) fill in where
|
|
43
|
+
// present and render blank where not applicable.
|
|
14
44
|
output(this, resp.connectors, {
|
|
15
|
-
columns: ["id", "name", "account", "
|
|
45
|
+
columns: ["id", "name", "type", "account", "warehouse", "project", "dataset"],
|
|
16
46
|
});
|
|
17
47
|
}
|
|
18
48
|
catch (err) {
|
|
@@ -56,6 +86,40 @@ export function registerConnectorCommands(program) {
|
|
|
56
86
|
errorOutput(err);
|
|
57
87
|
}
|
|
58
88
|
});
|
|
89
|
+
add
|
|
90
|
+
.command("bigquery-serviceaccount")
|
|
91
|
+
.description("Add BigQuery via a service-account key (org admin only)")
|
|
92
|
+
.requiredOption("--key-file <path>", "Path to the service-account key JSON file")
|
|
93
|
+
.option("--project <id>", "GCP project for billing/queries (defaults to the key's project_id)")
|
|
94
|
+
.option("--dataset <name>", "Default BigQuery dataset")
|
|
95
|
+
.option("--location <loc>", "BigQuery location, e.g. US or EU (must match the dataset region)")
|
|
96
|
+
.option("--name <name>", "Connection name (defaults to the project id)")
|
|
97
|
+
.option("--max-bytes-billed <n>", "Per-query scan cap in bytes (default 10 GiB)")
|
|
98
|
+
.action(async function () {
|
|
99
|
+
try {
|
|
100
|
+
const opts = this.opts();
|
|
101
|
+
const keyStats = await stat(opts.keyFile);
|
|
102
|
+
const mode = keyStats.mode & 0o777;
|
|
103
|
+
if (mode > 0o600) {
|
|
104
|
+
console.error(`Warning: Key file has loose permissions (${mode.toString(8)}). Consider chmod 600.`);
|
|
105
|
+
}
|
|
106
|
+
const raw = await readFile(opts.keyFile, "utf-8");
|
|
107
|
+
const key = parseServiceAccountKey(raw);
|
|
108
|
+
const projectId = opts.project || key.project_id;
|
|
109
|
+
const resp = await apiClient.post("/api/v1/cli/connectors/bigquery-serviceaccount", {
|
|
110
|
+
name: opts.name || projectId,
|
|
111
|
+
sa_key_json: raw,
|
|
112
|
+
project_id: projectId || null,
|
|
113
|
+
dataset: opts.dataset || null,
|
|
114
|
+
location: opts.location || null,
|
|
115
|
+
max_bytes_billed: opts.maxBytesBilled ? Number(opts.maxBytesBilled) : null,
|
|
116
|
+
});
|
|
117
|
+
output(this, resp, { keyValue: true });
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
errorOutput(err);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
59
123
|
connector
|
|
60
124
|
.command("test <id>")
|
|
61
125
|
.description("Test a connection")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connector.js","sourceRoot":"","sources":["../../src/commands/connector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"connector.js","sourceRoot":"","sources":["../../src/commands/connector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type CreateMode = {
|
|
2
|
+
kind: "file";
|
|
3
|
+
} | {
|
|
4
|
+
kind: "sql";
|
|
5
|
+
} | {
|
|
6
|
+
kind: "error";
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Decide whether `ds create` runs in file-upload or SQL mode (Feature #645).
|
|
11
|
+
* Pure + exported so it is unit-testable without a backend.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveCreateMode(opts: {
|
|
14
|
+
file?: unknown;
|
|
15
|
+
sql?: unknown;
|
|
16
|
+
name?: unknown;
|
|
17
|
+
}): CreateMode;
|
|
18
|
+
/**
|
|
19
|
+
* Parse one `--table-lookback` spec of the form `TABLE:COLUMN:DAYS`. Split from
|
|
20
|
+
* the RIGHT so schema-qualified table names (`DB.SCHEMA.TABLE`) pass through
|
|
21
|
+
* verbatim - column names can never contain `:` (server-validated identifiers).
|
|
22
|
+
* Pure + exported so it is unit-testable without a backend.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseTableLookback(spec: string): {
|
|
25
|
+
table: string;
|
|
26
|
+
column: string;
|
|
27
|
+
lookback_days: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Validate `ds refresh-config` flags and build the PATCH body. Pure + exported
|
|
31
|
+
* so it is unit-testable without a backend. Throws on invalid input; the action
|
|
32
|
+
* surfaces the message via errorOutput. Sets the COMPLETE incremental config -
|
|
33
|
+
* omitted fields fall back to server defaults (merge window 3, reconciliation
|
|
34
|
+
* off, table lookback windows cleared).
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildRefreshConfigBody(opts: {
|
|
37
|
+
mode?: unknown;
|
|
38
|
+
watermarkColumn?: unknown;
|
|
39
|
+
watermarkType?: unknown;
|
|
40
|
+
mergeKey?: unknown;
|
|
41
|
+
lookback?: unknown;
|
|
42
|
+
mergeWindow?: unknown;
|
|
43
|
+
reconciliation?: unknown;
|
|
44
|
+
tableLookback?: unknown;
|
|
45
|
+
}): Record<string, unknown>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether `ds create` runs in file-upload or SQL mode (Feature #645).
|
|
3
|
+
* Pure + exported so it is unit-testable without a backend.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveCreateMode(opts) {
|
|
6
|
+
if (opts.file) {
|
|
7
|
+
if (opts.sql) {
|
|
8
|
+
return { kind: "error", message: "Use either --file or --sql, not both." };
|
|
9
|
+
}
|
|
10
|
+
return { kind: "file" };
|
|
11
|
+
}
|
|
12
|
+
if (!opts.sql || !opts.name) {
|
|
13
|
+
return {
|
|
14
|
+
kind: "error",
|
|
15
|
+
message: "Provide --file <path> to upload a local Excel/CSV, or both --sql <query> and --name <name> for a SQL data source.",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return { kind: "sql" };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse one `--table-lookback` spec of the form `TABLE:COLUMN:DAYS`. Split from
|
|
22
|
+
* the RIGHT so schema-qualified table names (`DB.SCHEMA.TABLE`) pass through
|
|
23
|
+
* verbatim - column names can never contain `:` (server-validated identifiers).
|
|
24
|
+
* Pure + exported so it is unit-testable without a backend.
|
|
25
|
+
*/
|
|
26
|
+
export function parseTableLookback(spec) {
|
|
27
|
+
const last = spec.lastIndexOf(":");
|
|
28
|
+
const mid = last === -1 ? -1 : spec.lastIndexOf(":", last - 1);
|
|
29
|
+
const column = mid === -1 ? "" : spec.slice(mid + 1, last);
|
|
30
|
+
if (mid <= 0 || !column) {
|
|
31
|
+
throw new Error(`Invalid --table-lookback "${spec}". Use TABLE:COLUMN:DAYS (e.g. "ANALYTICS.PUBLIC.EVENTS:EVENT_DATE:30").`);
|
|
32
|
+
}
|
|
33
|
+
const days = Number(spec.slice(last + 1));
|
|
34
|
+
if (!Number.isInteger(days) || days < 1 || days > 3650) {
|
|
35
|
+
throw new Error(`Invalid days in --table-lookback "${spec}". Must be an integer between 1 and 3650.`);
|
|
36
|
+
}
|
|
37
|
+
return { table: spec.slice(0, mid), column, lookback_days: days };
|
|
38
|
+
}
|
|
39
|
+
// Mirrors the server's canonical table matching: last dotted segment, uppercased.
|
|
40
|
+
function canonicalTableKey(table) {
|
|
41
|
+
return (table.split(".").pop() ?? table).toUpperCase();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate `ds refresh-config` flags and build the PATCH body. Pure + exported
|
|
45
|
+
* so it is unit-testable without a backend. Throws on invalid input; the action
|
|
46
|
+
* surfaces the message via errorOutput. Sets the COMPLETE incremental config -
|
|
47
|
+
* omitted fields fall back to server defaults (merge window 3, reconciliation
|
|
48
|
+
* off, table lookback windows cleared).
|
|
49
|
+
*/
|
|
50
|
+
export function buildRefreshConfigBody(opts) {
|
|
51
|
+
const mode = opts.mode;
|
|
52
|
+
if (!mode) {
|
|
53
|
+
throw new Error('--mode is required. Use "full" or "incremental".');
|
|
54
|
+
}
|
|
55
|
+
if (mode !== "full" && mode !== "incremental") {
|
|
56
|
+
throw new Error(`Invalid --mode "${mode}". Use "full" or "incremental".`);
|
|
57
|
+
}
|
|
58
|
+
// Mirror the backend 400 client-side so the user fixes it in one turn.
|
|
59
|
+
if (mode === "incremental" && (!opts.watermarkColumn || !opts.watermarkType)) {
|
|
60
|
+
throw new Error("Incremental mode requires --watermark-column and --watermark-type.");
|
|
61
|
+
}
|
|
62
|
+
const WM_TYPES = new Set(["timestamp", "date", "integer"]);
|
|
63
|
+
if (opts.watermarkType && !WM_TYPES.has(opts.watermarkType)) {
|
|
64
|
+
throw new Error(`Invalid --watermark-type "${opts.watermarkType}". Use timestamp, date, or integer.`);
|
|
65
|
+
}
|
|
66
|
+
const RECON = new Set(["daily", "weekly", "monthly", "off"]);
|
|
67
|
+
if (opts.reconciliation !== undefined && !RECON.has(opts.reconciliation)) {
|
|
68
|
+
throw new Error(`Invalid --reconciliation "${opts.reconciliation}". Use daily, weekly, monthly, or off.`);
|
|
69
|
+
}
|
|
70
|
+
// Clean break: the pre-rename --lookback flag set the merge window, while
|
|
71
|
+
// "lookback" now means the per-table read windows - silently honoring
|
|
72
|
+
// either meaning would be a trap. Hidden from help; always errors with the fix.
|
|
73
|
+
if (opts.lookback !== undefined) {
|
|
74
|
+
throw new Error("--lookback was renamed to --merge-window (it sets the merge window - how far back " +
|
|
75
|
+
"each run re-fetches and upserts). For per-table lookback windows use --table-lookback.");
|
|
76
|
+
}
|
|
77
|
+
const body = { refresh_mode: mode };
|
|
78
|
+
if (opts.watermarkColumn)
|
|
79
|
+
body.watermark_column = opts.watermarkColumn;
|
|
80
|
+
if (opts.watermarkType)
|
|
81
|
+
body.watermark_type = opts.watermarkType;
|
|
82
|
+
if (opts.mergeKey)
|
|
83
|
+
body.merge_key = opts.mergeKey;
|
|
84
|
+
if (opts.mergeWindow !== undefined) {
|
|
85
|
+
const n = parseInt(opts.mergeWindow, 10);
|
|
86
|
+
if (isNaN(n)) {
|
|
87
|
+
throw new Error(`Invalid --merge-window "${opts.mergeWindow}". Must be a number.`);
|
|
88
|
+
}
|
|
89
|
+
body.lookback_periods = n;
|
|
90
|
+
}
|
|
91
|
+
if (opts.reconciliation !== undefined) {
|
|
92
|
+
// "off" disables the reconciliation full-refresh backstop.
|
|
93
|
+
body.reconciliation_schedule = opts.reconciliation === "off" ? null : opts.reconciliation;
|
|
94
|
+
}
|
|
95
|
+
// Per-table lookback windows (repeatable TABLE:COLUMN:DAYS). The server
|
|
96
|
+
// clears windows SILENTLY in full mode and rejects integer watermarks -
|
|
97
|
+
// both are mirrored loudly here so the user fixes the call in one turn.
|
|
98
|
+
const specs = (opts.tableLookback ?? []);
|
|
99
|
+
if (specs.length > 0) {
|
|
100
|
+
if (mode !== "incremental") {
|
|
101
|
+
throw new Error("--table-lookback requires --mode incremental.");
|
|
102
|
+
}
|
|
103
|
+
if (opts.watermarkType === "integer") {
|
|
104
|
+
throw new Error("--table-lookback needs --watermark-type date or timestamp (lookback windows are day-based).");
|
|
105
|
+
}
|
|
106
|
+
if (specs.length > 20) {
|
|
107
|
+
throw new Error(`Too many --table-lookback windows (${specs.length}); max 20.`);
|
|
108
|
+
}
|
|
109
|
+
const windows = specs.map(parseTableLookback);
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
for (const w of windows) {
|
|
112
|
+
const key = canonicalTableKey(w.table);
|
|
113
|
+
if (seen.has(key)) {
|
|
114
|
+
throw new Error(`Duplicate --table-lookback for table "${w.table}" (tables match on the last dotted segment, case-insensitive).`);
|
|
115
|
+
}
|
|
116
|
+
seen.add(key);
|
|
117
|
+
}
|
|
118
|
+
body.table_lookback_windows = windows;
|
|
119
|
+
}
|
|
120
|
+
return body;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=ds-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ds-config.js","sourceRoot":"","sources":["../../src/commands/ds-config.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/ds.d.ts
CHANGED
|
@@ -1,47 +1,2 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
export type CreateMode = {
|
|
3
|
-
kind: "file";
|
|
4
|
-
} | {
|
|
5
|
-
kind: "sql";
|
|
6
|
-
} | {
|
|
7
|
-
kind: "error";
|
|
8
|
-
message: string;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Decide whether `ds create` runs in file-upload or SQL mode (Feature #645).
|
|
12
|
-
* Pure + exported so it is unit-testable without a backend.
|
|
13
|
-
*/
|
|
14
|
-
export declare function resolveCreateMode(opts: {
|
|
15
|
-
file?: unknown;
|
|
16
|
-
sql?: unknown;
|
|
17
|
-
name?: unknown;
|
|
18
|
-
}): CreateMode;
|
|
19
|
-
/**
|
|
20
|
-
* Parse one `--table-lookback` spec of the form `TABLE:COLUMN:DAYS`. Split from
|
|
21
|
-
* the RIGHT so schema-qualified table names (`DB.SCHEMA.TABLE`) pass through
|
|
22
|
-
* verbatim - column names can never contain `:` (server-validated identifiers).
|
|
23
|
-
* Pure + exported so it is unit-testable without a backend.
|
|
24
|
-
*/
|
|
25
|
-
export declare function parseTableLookback(spec: string): {
|
|
26
|
-
table: string;
|
|
27
|
-
column: string;
|
|
28
|
-
lookback_days: number;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* Validate `ds refresh-config` flags and build the PATCH body. Pure + exported
|
|
32
|
-
* so it is unit-testable without a backend. Throws on invalid input; the action
|
|
33
|
-
* surfaces the message via errorOutput. Sets the COMPLETE incremental config -
|
|
34
|
-
* omitted fields fall back to server defaults (merge window 3, reconciliation
|
|
35
|
-
* off, table lookback windows cleared).
|
|
36
|
-
*/
|
|
37
|
-
export declare function buildRefreshConfigBody(opts: {
|
|
38
|
-
mode?: unknown;
|
|
39
|
-
watermarkColumn?: unknown;
|
|
40
|
-
watermarkType?: unknown;
|
|
41
|
-
mergeKey?: unknown;
|
|
42
|
-
lookback?: unknown;
|
|
43
|
-
mergeWindow?: unknown;
|
|
44
|
-
reconciliation?: unknown;
|
|
45
|
-
tableLookback?: unknown;
|
|
46
|
-
}): Record<string, unknown>;
|
|
47
2
|
export declare function registerDSCommands(program: Command): void;
|
package/dist/commands/ds.js
CHANGED
|
@@ -6,6 +6,7 @@ import { apiClient } from "../api/client.js";
|
|
|
6
6
|
import { output, errorOutput, note } from "../output/format.js";
|
|
7
7
|
import { getFrontendUrl } from "../config.js";
|
|
8
8
|
import { reconcilePoll, NOT_FOUND_STATUS } from "./ds-poll.js";
|
|
9
|
+
import { buildRefreshConfigBody, resolveCreateMode } from "./ds-config.js";
|
|
9
10
|
import { registerRefreshHistoryCommand } from "./ds/refresh-history.js";
|
|
10
11
|
// Project #244: print soft, non-blocking shape advisories. Print-and-proceed -
|
|
11
12
|
// these never gate a create or verify.
|
|
@@ -215,127 +216,6 @@ async function scanAndPrintSummary(dsId, dsName, rowCount, force = false, accept
|
|
|
215
216
|
return null;
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Decide whether `ds create` runs in file-upload or SQL mode (Feature #645).
|
|
220
|
-
* Pure + exported so it is unit-testable without a backend.
|
|
221
|
-
*/
|
|
222
|
-
export function resolveCreateMode(opts) {
|
|
223
|
-
if (opts.file) {
|
|
224
|
-
if (opts.sql) {
|
|
225
|
-
return { kind: "error", message: "Use either --file or --sql, not both." };
|
|
226
|
-
}
|
|
227
|
-
return { kind: "file" };
|
|
228
|
-
}
|
|
229
|
-
if (!opts.sql || !opts.name) {
|
|
230
|
-
return {
|
|
231
|
-
kind: "error",
|
|
232
|
-
message: "Provide --file <path> to upload a local Excel/CSV, or both --sql <query> and --name <name> for a SQL data source.",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
return { kind: "sql" };
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Parse one `--table-lookback` spec of the form `TABLE:COLUMN:DAYS`. Split from
|
|
239
|
-
* the RIGHT so schema-qualified table names (`DB.SCHEMA.TABLE`) pass through
|
|
240
|
-
* verbatim - column names can never contain `:` (server-validated identifiers).
|
|
241
|
-
* Pure + exported so it is unit-testable without a backend.
|
|
242
|
-
*/
|
|
243
|
-
export function parseTableLookback(spec) {
|
|
244
|
-
const last = spec.lastIndexOf(":");
|
|
245
|
-
const mid = last === -1 ? -1 : spec.lastIndexOf(":", last - 1);
|
|
246
|
-
const column = mid === -1 ? "" : spec.slice(mid + 1, last);
|
|
247
|
-
if (mid <= 0 || !column) {
|
|
248
|
-
throw new Error(`Invalid --table-lookback "${spec}". Use TABLE:COLUMN:DAYS (e.g. "ANALYTICS.PUBLIC.EVENTS:EVENT_DATE:30").`);
|
|
249
|
-
}
|
|
250
|
-
const days = Number(spec.slice(last + 1));
|
|
251
|
-
if (!Number.isInteger(days) || days < 1 || days > 3650) {
|
|
252
|
-
throw new Error(`Invalid days in --table-lookback "${spec}". Must be an integer between 1 and 3650.`);
|
|
253
|
-
}
|
|
254
|
-
return { table: spec.slice(0, mid), column, lookback_days: days };
|
|
255
|
-
}
|
|
256
|
-
// Mirrors the server's canonical table matching: last dotted segment, uppercased.
|
|
257
|
-
function canonicalTableKey(table) {
|
|
258
|
-
return (table.split(".").pop() ?? table).toUpperCase();
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Validate `ds refresh-config` flags and build the PATCH body. Pure + exported
|
|
262
|
-
* so it is unit-testable without a backend. Throws on invalid input; the action
|
|
263
|
-
* surfaces the message via errorOutput. Sets the COMPLETE incremental config -
|
|
264
|
-
* omitted fields fall back to server defaults (merge window 3, reconciliation
|
|
265
|
-
* off, table lookback windows cleared).
|
|
266
|
-
*/
|
|
267
|
-
export function buildRefreshConfigBody(opts) {
|
|
268
|
-
const mode = opts.mode;
|
|
269
|
-
if (!mode) {
|
|
270
|
-
throw new Error('--mode is required. Use "full" or "incremental".');
|
|
271
|
-
}
|
|
272
|
-
if (mode !== "full" && mode !== "incremental") {
|
|
273
|
-
throw new Error(`Invalid --mode "${mode}". Use "full" or "incremental".`);
|
|
274
|
-
}
|
|
275
|
-
// Mirror the backend 400 client-side so the user fixes it in one turn.
|
|
276
|
-
if (mode === "incremental" && (!opts.watermarkColumn || !opts.watermarkType)) {
|
|
277
|
-
throw new Error("Incremental mode requires --watermark-column and --watermark-type.");
|
|
278
|
-
}
|
|
279
|
-
const WM_TYPES = new Set(["timestamp", "date", "integer"]);
|
|
280
|
-
if (opts.watermarkType && !WM_TYPES.has(opts.watermarkType)) {
|
|
281
|
-
throw new Error(`Invalid --watermark-type "${opts.watermarkType}". Use timestamp, date, or integer.`);
|
|
282
|
-
}
|
|
283
|
-
const RECON = new Set(["daily", "weekly", "monthly", "off"]);
|
|
284
|
-
if (opts.reconciliation !== undefined && !RECON.has(opts.reconciliation)) {
|
|
285
|
-
throw new Error(`Invalid --reconciliation "${opts.reconciliation}". Use daily, weekly, monthly, or off.`);
|
|
286
|
-
}
|
|
287
|
-
// Clean break: the pre-rename --lookback flag set the merge window, while
|
|
288
|
-
// "lookback" now means the per-table read windows - silently honoring
|
|
289
|
-
// either meaning would be a trap. Hidden from help; always errors with the fix.
|
|
290
|
-
if (opts.lookback !== undefined) {
|
|
291
|
-
throw new Error("--lookback was renamed to --merge-window (it sets the merge window - how far back " +
|
|
292
|
-
"each run re-fetches and upserts). For per-table lookback windows use --table-lookback.");
|
|
293
|
-
}
|
|
294
|
-
const body = { refresh_mode: mode };
|
|
295
|
-
if (opts.watermarkColumn)
|
|
296
|
-
body.watermark_column = opts.watermarkColumn;
|
|
297
|
-
if (opts.watermarkType)
|
|
298
|
-
body.watermark_type = opts.watermarkType;
|
|
299
|
-
if (opts.mergeKey)
|
|
300
|
-
body.merge_key = opts.mergeKey;
|
|
301
|
-
if (opts.mergeWindow !== undefined) {
|
|
302
|
-
const n = parseInt(opts.mergeWindow, 10);
|
|
303
|
-
if (isNaN(n)) {
|
|
304
|
-
throw new Error(`Invalid --merge-window "${opts.mergeWindow}". Must be a number.`);
|
|
305
|
-
}
|
|
306
|
-
body.lookback_periods = n;
|
|
307
|
-
}
|
|
308
|
-
if (opts.reconciliation !== undefined) {
|
|
309
|
-
// "off" disables the reconciliation full-refresh backstop.
|
|
310
|
-
body.reconciliation_schedule = opts.reconciliation === "off" ? null : opts.reconciliation;
|
|
311
|
-
}
|
|
312
|
-
// Per-table lookback windows (repeatable TABLE:COLUMN:DAYS). The server
|
|
313
|
-
// clears windows SILENTLY in full mode and rejects integer watermarks -
|
|
314
|
-
// both are mirrored loudly here so the user fixes the call in one turn.
|
|
315
|
-
const specs = (opts.tableLookback ?? []);
|
|
316
|
-
if (specs.length > 0) {
|
|
317
|
-
if (mode !== "incremental") {
|
|
318
|
-
throw new Error("--table-lookback requires --mode incremental.");
|
|
319
|
-
}
|
|
320
|
-
if (opts.watermarkType === "integer") {
|
|
321
|
-
throw new Error("--table-lookback needs --watermark-type date or timestamp (lookback windows are day-based).");
|
|
322
|
-
}
|
|
323
|
-
if (specs.length > 20) {
|
|
324
|
-
throw new Error(`Too many --table-lookback windows (${specs.length}); max 20.`);
|
|
325
|
-
}
|
|
326
|
-
const windows = specs.map(parseTableLookback);
|
|
327
|
-
const seen = new Set();
|
|
328
|
-
for (const w of windows) {
|
|
329
|
-
const key = canonicalTableKey(w.table);
|
|
330
|
-
if (seen.has(key)) {
|
|
331
|
-
throw new Error(`Duplicate --table-lookback for table "${w.table}" (tables match on the last dotted segment, case-insensitive).`);
|
|
332
|
-
}
|
|
333
|
-
seen.add(key);
|
|
334
|
-
}
|
|
335
|
-
body.table_lookback_windows = windows;
|
|
336
|
-
}
|
|
337
|
-
return body;
|
|
338
|
-
}
|
|
339
219
|
/**
|
|
340
220
|
* Create a data source from a local Excel/CSV file (Feature #645). Uploads the
|
|
341
221
|
* file as multipart; the server parses, runs the inline AI scan, encrypts, and
|
|
@@ -393,8 +273,8 @@ export function registerDSCommands(program) {
|
|
|
393
273
|
.description("Create a data source from a SQL query (--sql) or a local Excel/CSV file (--file)")
|
|
394
274
|
.option("--sql <query>", "Source SQL query")
|
|
395
275
|
.option("--name <name>", "Data source name (defaults to the file name in --file mode)")
|
|
396
|
-
.option("--connection <id>", "
|
|
397
|
-
.option("--schema <name>", "Schema name (SQL mode)")
|
|
276
|
+
.option("--connection <id>", "Warehouse connection ID - Snowflake or BigQuery (SQL mode)")
|
|
277
|
+
.option("--schema <name>", "Schema (Snowflake) or dataset (BigQuery) name (SQL mode)")
|
|
398
278
|
.option("--skip-scan", "Skip schema scan after creation (SQL mode)")
|
|
399
279
|
.option("--detect-tables", "Print the tables detected in the SQL and exit, without creating (SQL mode preview)")
|
|
400
280
|
.option("--source-tables <list>", "Comma-separated override for the tables referenced in the SQL (SQL mode; omit to auto-detect)")
|
|
@@ -647,16 +527,12 @@ export function registerDSCommands(program) {
|
|
|
647
527
|
}
|
|
648
528
|
});
|
|
649
529
|
ds.command("update <id>")
|
|
650
|
-
.description("Update data source
|
|
651
|
-
.option("--governed-mode <mode>", "Governance mode: on|off")
|
|
530
|
+
.description("Update a data source row cap")
|
|
652
531
|
.option("--max-rows <n>", "Max rows limit (0 to remove)")
|
|
653
532
|
.action(async function (id) {
|
|
654
533
|
try {
|
|
655
534
|
const opts = this.opts();
|
|
656
535
|
const body = {};
|
|
657
|
-
if (opts.governedMode !== undefined) {
|
|
658
|
-
body.governed_mode = opts.governedMode === "on";
|
|
659
|
-
}
|
|
660
536
|
if (opts.maxRows !== undefined) {
|
|
661
537
|
const val = parseInt(opts.maxRows, 10);
|
|
662
538
|
if (isNaN(val))
|