@datarails-ai/dr-cli 1.6.80 → 1.6.82
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 +3 -3
- package/dist/scopes-map.json +1 -1
- package/dist/skills/create-connector-lib/SKILL.md +93 -0
- package/dist/skills/datarails-connectors-onboard-connector/SKILL.md +36 -7
- package/dist/skills/plugins.json +1 -0
- package/dist/skills/validate-workato-migration/reference/gotchas.md +1 -0
- package/package.json +2 -2
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.82",
|
|
12
12
|
description: "Datarails CLI",
|
|
13
13
|
type: "module",
|
|
14
14
|
engines: {
|
|
@@ -38,7 +38,7 @@ var package_default = {
|
|
|
38
38
|
postinstall: "node scripts/postinstall.mjs"
|
|
39
39
|
},
|
|
40
40
|
dependencies: {
|
|
41
|
-
"@datarails-ai/dr-appspace-cli": "0.10.
|
|
41
|
+
"@datarails-ai/dr-appspace-cli": "0.10.5",
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
43
|
"@napi-rs/keyring": "^1.3.0",
|
|
44
44
|
commander: "^12.1.0",
|
|
@@ -67,7 +67,7 @@ var package_default = {
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
// src/buildInfo.ts
|
|
70
|
-
var BUILD_TIME = "2026-09-
|
|
70
|
+
var BUILD_TIME = "2026-09-30T16:01:09.322Z";
|
|
71
71
|
function formatBuildTime(iso) {
|
|
72
72
|
if (iso === "") return "";
|
|
73
73
|
const d = new Date(iso);
|
package/dist/scopes-map.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"cliVersion": "1.6.
|
|
3
|
+
"cliVersion": "1.6.82",
|
|
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": {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-connector-lib
|
|
3
|
+
description: Use when a vendor's connector code should be shared across Datarails orgs in dr-connectors - a vendor with no `lib/<vendor>/` yet that a second customer is likely to need, a Workato migration of a vendor we already run for another org, a second org asking for the same vendor with different columns, or an existing per-org connector being moved onto `lib/`. Triggers - "create a lib for <vendor>", "make <vendor> a shared lib", "generic connector", "pass the field names", "select_query", "the next customer needs different columns", "move this connector onto lib", "lib/<vendor>", "same vendor, another org".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a connector lib
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
A lib is the vendor's code written once: `lib/<vendor>/` owns the request, auth, pagination and the
|
|
11
|
+
file format, and each org's connector is two thin files that **pass their field names** plus a few
|
|
12
|
+
optional parameters. The worked example is `lib/ramp/dataqueries/select_query.py`; find its thin
|
|
13
|
+
connectors with `grep -rl "lib.ramp.dataqueries.select_query" --include=*.py us us2 uk ca`. Read both first.
|
|
14
|
+
|
|
15
|
+
**REQUIRED:** `datarails-connectors-onboard-connector` for everything around the code: branch,
|
|
16
|
+
publish, datasource, filebox, binding. Its memory, streaming and 4xx rules apply to lib code too.
|
|
17
|
+
|
|
18
|
+
## Layout
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
lib/<vendor>/
|
|
22
|
+
__init__.py what the lib holds: each module and its output, no one who uses it
|
|
23
|
+
_core.py output-neutral: credentials, token, client, pagination, query vars, logger
|
|
24
|
+
connections/base_connection.py the one connection: auth only, no scope or other per-customer value
|
|
25
|
+
dataqueries/select_query.py the generic engine
|
|
26
|
+
tests/test_select_query.py the engine, on synthetic data
|
|
27
|
+
tests/test_artifact.py every thin connector that imports lib.<vendor>, found by glob
|
|
28
|
+
<dc>/<org_id>_(<domain>)/<vendor>/
|
|
29
|
+
connections/<conn>.py subclasses the lib connection and sets its SCOPE (or other auth parameter)
|
|
30
|
+
dataqueries/<dq>.py the org's COLUMNS, OBJECT_TYPES, get_query_class
|
|
31
|
+
tests/test_<dq>.py the org's exact header, each org-specific source it uses, and its SCOPE
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## The engine contract
|
|
35
|
+
|
|
36
|
+
The connector's thin dataquery subclasses `SelectQuery` and sets only data:
|
|
37
|
+
|
|
38
|
+
| Attribute | Meaning |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `PATH` | endpoint under the vendor base URL |
|
|
41
|
+
| `WINDOW` | the endpoint's from/to parameter names, filled from the Sync-query's `startDate`/`endDate`; omit for undated datasets |
|
|
42
|
+
| `COLUMNS` | `(label, source)` pairs in file order |
|
|
43
|
+
|
|
44
|
+
Sources are small functions in the engine module: `field("a.b.c")` (dotted path; a missing step is
|
|
45
|
+
empty), `timestamp(...)` (re-rendered in the source system's format), and one per vendor-specific
|
|
46
|
+
lookup (Ramp: `category(remote_name, key)`; a custom-fields array would be `custom_field(name)`).
|
|
47
|
+
Add a source only when a column needs it. Add an engine attribute (e.g. `EXPLODE` for one row per line
|
|
48
|
+
item) only when a real connector needs it, with its test.
|
|
49
|
+
|
|
50
|
+
`OBJECT_TYPES` is a literal in the thin file. The engine module declares no columns, no object types,
|
|
51
|
+
no `get_query_class`. The connection works the same way: one lib connection whose parameters (Ramp:
|
|
52
|
+
`SCOPE`, the read scopes the customer's app was granted) the thin connection sets; an unset required
|
|
53
|
+
parameter fails in `connect()` before the vendor is called.
|
|
54
|
+
|
|
55
|
+
## Rules
|
|
56
|
+
|
|
57
|
+
- **No company names anywhere under `lib/`**: no customer, org id, domain, or the tool being replaced
|
|
58
|
+
(Workato), in code, docstrings, comments, test names, fixture data or test ids. Test data is
|
|
59
|
+
synthetic. Per-customer values (columns, category or custom-field names) live only in thin files.
|
|
60
|
+
Before committing, grep the lib diff for the customer's name, domain and org id.
|
|
61
|
+
- **Reuse before adding.** Use `_core` helpers and an existing connection that fits; never a second
|
|
62
|
+
copy. Name modules after what they do, never after who uses them. Hand the user a list of lib
|
|
63
|
+
improvements you noticed (duplicated helpers, code that belongs in `_core`), done in their own PR.
|
|
64
|
+
- **Never change what another org lands.** A change to a module other orgs import must be proven
|
|
65
|
+
output-neutral: their tests pass, and for text-only edits, each module's AST with docstrings stripped
|
|
66
|
+
equals `origin/main`'s. A lib merge cuts new versions of every connector that bundles it; none moves
|
|
67
|
+
until its binding is repointed.
|
|
68
|
+
- **Take the file format from a real landed file, not from memory.** Download the file the old
|
|
69
|
+
integration landed (`dr filebox download <fb> --doc-version <id>`) and profile it: BOM, line endings,
|
|
70
|
+
trailing newline, how empty and null cells are written, when values are quoted, number and boolean
|
|
71
|
+
forms, timestamp formats. The engine's cell writer reproduces exactly that.
|
|
72
|
+
- **Credentials resolve at runtime from the datasource** (`get_credentials`), never from code, tests
|
|
73
|
+
or logs (`tech/security.md` §4). Check a migrated credential's shape without printing it; a stored
|
|
74
|
+
header value may carry its scheme (`Basic <base64>`).
|
|
75
|
+
|
|
76
|
+
## Before the first merge
|
|
77
|
+
|
|
78
|
+
Run the thin connector's dataquery locally against the live vendor for one closed period and compare
|
|
79
|
+
its bytes with the old integration's landed file for the same period (the `validate-workato-migration`
|
|
80
|
+
skill's `compare_files.py`). Differences only in fields edited after the old run are data drift;
|
|
81
|
+
anything in quoting, format, order or header is a bug to fix before merging. For a refactor, render
|
|
82
|
+
the same fetched records through the old and new code and require identical bytes.
|
|
83
|
+
|
|
84
|
+
## Common mistakes
|
|
85
|
+
|
|
86
|
+
| Mistake | Fix |
|
|
87
|
+
|---|---|
|
|
88
|
+
| Customer column list or category names as the lib default | Empty default in the lib; the thin file sets them |
|
|
89
|
+
| "A thin connector needs no test" | A thin file that declares columns tests its header and its org-specific sources |
|
|
90
|
+
| One lib connection per scope, named after an output variant (`flat`) | One parameterized connection; the thin file sets `SCOPE` |
|
|
91
|
+
| `csv.writer` / `str(True)` for the file | Write cells the way the landed file shows (`""`, LF, `true`) |
|
|
92
|
+
| Artifact test that lists each client's folder | Discover thin connectors by glob on `from lib.<vendor>.` |
|
|
93
|
+
| A page cap that logs and returns | The cap raises `PermanentError`; test landing exactly on it and exceeding it |
|
|
@@ -85,17 +85,46 @@ CONNECTION = "<conn>"
|
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
- **Pick the variant by output, not by customer.** A lib can hold several output shapes of one vendor
|
|
88
|
-
(`lib/ramp`: `curated` / `reports` / `flat`). Read the lib's `__init__.py` — it names each
|
|
89
|
-
|
|
90
|
-
for Workato parity). Never merge or edit a variant another org lands data through: that
|
|
91
|
-
live output.
|
|
88
|
+
(`lib/ramp`: `curated` / `reports` / `flat` / `pivoted`). Read the lib's `__init__.py` — it names each
|
|
89
|
+
variant and its output — and match the columns the customer expects (a Workato migration wants the
|
|
90
|
+
variant built for Workato parity). Never merge or edit a variant another org lands data through: that
|
|
91
|
+
changes their live output.
|
|
92
|
+
- **Reuse the lib's code before adding any.** A new variant still reuses what the lib already has: the
|
|
93
|
+
helpers in `_core.py` and the lib's one connection. `lib/ramp` has a single
|
|
94
|
+
`connections/base_connection.py`; a thin connection subclasses it and sets `SCOPE`, never a copy. Name a new variant after its output,
|
|
95
|
+
never after the tool or customer it came from. While you're in the lib, list what could be shared or
|
|
96
|
+
cleaned up (a helper two variants duplicate, a pattern that belongs in `_core`) and give that list to
|
|
97
|
+
the user as lib suggestions. Make those changes in their own PR unless the user asks otherwise.
|
|
98
|
+
- **The lib is shared: it never names a client or holds a client's config or data.** No customer or org
|
|
99
|
+
id in its code, docstrings or tests; no client-specific default (a customer's accounting-category
|
|
100
|
+
names, their GL codes); test fixtures are synthetic, never rows copied from a customer's file. Grep the
|
|
101
|
+
lib diff for the customer's name and org id before committing.
|
|
92
102
|
- **A per-org difference is config or a one-attribute extension, never a copy.** First look for a
|
|
93
103
|
binding parameter the lib already reads (`lib/ramp`: optional `scope` in `query_config`). Otherwise
|
|
94
|
-
subclass the lib's class in the thin connection file and override one attribute —
|
|
95
|
-
`samples/sample_lib_connector/` shows
|
|
104
|
+
subclass the lib's class in the thin connection or dataquery file and override one attribute —
|
|
105
|
+
`samples/sample_lib_connector/` shows the connection form. A dataquery extension also defines its own
|
|
106
|
+
`get_query_class`, keeping `OBJECT_TYPES` one import hop from the lib:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# <datacenter>/<org_id>_(<org_name>)/<vendor>/dataqueries/<dq>.py
|
|
110
|
+
from dr_integrations_sdk import DataQuery
|
|
111
|
+
|
|
112
|
+
from lib.<vendor>.dataqueries.<variant> import OBJECT_TYPES, TransactionsQuery as _TransactionsQuery # noqa: F401
|
|
113
|
+
|
|
114
|
+
CONNECTION = "<conn>"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TransactionsQuery(_TransactionsQuery):
|
|
118
|
+
CATEGORIES = ("<the customer's field>", "<another>")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_query_class(name: str) -> type[DataQuery]:
|
|
122
|
+
return TransactionsQuery
|
|
123
|
+
```
|
|
96
124
|
- **No new test is needed**: a dataquery that only references a lib counts as tested by the lib's own
|
|
97
125
|
`lib/<vendor>/tests/` (`scripts/check_connector_tests.py`). An extension that adds behaviour tests that
|
|
98
|
-
behaviour
|
|
126
|
+
behaviour next to the connector (`<vendor>/tests/test_<dq>.py`), since the lib's tests never see the
|
|
127
|
+
customer's values.
|
|
99
128
|
- **The lib is missing a dataset, or has a bug** → change the lib (with its tests) in its own PR. A lib
|
|
100
129
|
change republishes every connector that bundles it — new versions, never promoted — so each org moves
|
|
101
130
|
only when its binding is repointed.
|
package/dist/skills/plugins.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
| `dr connectors publish` aborts "published under org A … authenticated as org B" | Passed the customer's dataquery path with `$STG` | Use `<dc>/<staging_org>_(test-workato-migration.com)/…` + `--tag vN` |
|
|
8
8
|
| Copy workflow: "target exists with DIFFERENT bytes" | A different artifact was already copied for that `vN` | Stop and investigate. Never overwrite: the staging copy must equal what the customer runs |
|
|
9
9
|
| Second sync of a filebox never runs | The previous run still holds the filebox Singleton | Wait for the version to land before re-triggering |
|
|
10
|
+
| Vendor token call 401 "credentials not found or malformed" with a Workato-sourced credential | The environment property holds the whole header value, scheme included (`Basic <base64>`), because the recipe pastes the pill straight into `Authorization` | Strip the scheme before decoding, then store the decoded parts (e.g. `client_id` / `client_secret`) in the datasource. Check the shape (prefix, lengths) without printing the value |
|
|
10
11
|
| One row differs, on a recent period only | Vendor data changed after Workato's run | Re-pick older, closed periods before calling it a connector bug |
|
|
11
12
|
| Only bytes differ, values equal | Quoting / float formatting / line endings | Still a FAIL: match Workato's CSV writer in the connector |
|
|
12
13
|
| Sync-query shows escaped quotes / doesn't render | `config.query` written as a JSON string | Write it as a nested object; the CLI encodes it |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datarails-ai/dr-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.82",
|
|
4
4
|
"description": "Datarails CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@datarails-ai/dr-appspace-cli": "0.10.
|
|
20
|
+
"@datarails-ai/dr-appspace-cli": "0.10.5",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
22
|
"@napi-rs/keyring": "^1.3.0",
|
|
23
23
|
"commander": "^12.1.0",
|