@maschinenlesbar.org/marktstammdatenregister-cli 0.0.7 → 0.1.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.
- package/README.md +24 -4
- package/dist/src/cli/commands/units.d.ts +0 -1
- package/dist/src/cli/commands/units.js +38 -27
- package/dist/src/cli/index.d.ts +0 -1
- package/dist/src/cli/index.js +0 -1
- package/dist/src/cli/io.d.ts +0 -1
- package/dist/src/cli/io.js +0 -1
- package/dist/src/cli/program.d.ts +0 -1
- package/dist/src/cli/program.js +2 -2
- package/dist/src/cli/run.d.ts +0 -1
- package/dist/src/cli/run.js +0 -1
- package/dist/src/cli/shared.d.ts +29 -19
- package/dist/src/cli/shared.js +42 -46
- package/dist/src/client/client.d.ts +26 -4
- package/dist/src/client/client.js +105 -19
- package/dist/src/client/engine.d.ts +96 -18
- package/dist/src/client/engine.js +154 -43
- package/dist/src/client/errors.d.ts +12 -2
- package/dist/src/client/errors.js +29 -4
- package/dist/src/client/filter.d.ts +56 -0
- package/dist/src/client/filter.js +144 -0
- package/dist/src/client/http.d.ts +0 -1
- package/dist/src/client/http.js +45 -35
- package/dist/src/client/index.d.ts +7 -4
- package/dist/src/client/index.js +5 -4
- package/dist/src/client/query.d.ts +0 -1
- package/dist/src/client/query.js +0 -1
- package/dist/src/client/types.d.ts +29 -12
- package/dist/src/client/types.js +0 -1
- package/dist/src/client/validate.d.ts +63 -0
- package/dist/src/client/validate.js +131 -0
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +0 -1
- package/package.json +2 -1
- package/dist/src/cli/commands/units.d.ts.map +0 -1
- package/dist/src/cli/commands/units.js.map +0 -1
- package/dist/src/cli/index.d.ts.map +0 -1
- package/dist/src/cli/index.js.map +0 -1
- package/dist/src/cli/io.d.ts.map +0 -1
- package/dist/src/cli/io.js.map +0 -1
- package/dist/src/cli/program.d.ts.map +0 -1
- package/dist/src/cli/program.js.map +0 -1
- package/dist/src/cli/run.d.ts.map +0 -1
- package/dist/src/cli/run.js.map +0 -1
- package/dist/src/cli/shared.d.ts.map +0 -1
- package/dist/src/cli/shared.js.map +0 -1
- package/dist/src/client/client.d.ts.map +0 -1
- package/dist/src/client/client.js.map +0 -1
- package/dist/src/client/engine.d.ts.map +0 -1
- package/dist/src/client/engine.js.map +0 -1
- package/dist/src/client/errors.d.ts.map +0 -1
- package/dist/src/client/errors.js.map +0 -1
- package/dist/src/client/http.d.ts.map +0 -1
- package/dist/src/client/http.js.map +0 -1
- package/dist/src/client/index.d.ts.map +0 -1
- package/dist/src/client/index.js.map +0 -1
- package/dist/src/client/query.d.ts.map +0 -1
- package/dist/src/client/query.js.map +0 -1
- package/dist/src/client/types.d.ts.map +0 -1
- package/dist/src/client/types.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -42,11 +42,15 @@ mastr filters stromerzeugung # the filterable columns + d
|
|
|
42
42
|
|
|
43
43
|
Categories: `stromerzeugung`, `stromverbrauch`, `gaserzeugung`, `gasverbrauch`.
|
|
44
44
|
|
|
45
|
-
**Filtering** uses the register's own syntax — `FilterName~op~'value'~
|
|
45
|
+
**Filtering** uses the register's own syntax — `FilterName~op~'value'~and~…`
|
|
46
46
|
(ops `eq|neq|sw|ct|nct|ew|null|nn`, plus the strict `gt`/`lt` for numbers and dates);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
there is **no working `~or~`** (the register drops everything after it, so the CLI
|
|
48
|
+
rejects it) — for several codes of one dropdown, list them in one value:
|
|
49
|
+
`Energieträger~eq~'2497,2498'`. Discover the German field names and dropdown codes with `mastr filters <category>`. A
|
|
50
|
+
**wrong `FilterName` is silently ignored** (you get the unfiltered set), and a **wrong
|
|
51
|
+
`--sort` key returns 0 rows**; a malformed spec or an unknown operator (e.g. `gte`) is
|
|
52
|
+
rejected before any request (exit 2), because the register would answer it with a wrong
|
|
53
|
+
count. Verify filter names
|
|
50
54
|
with `mastr filters` and sanity-check with `--total`; sort keys are record field names
|
|
51
55
|
(`Bruttoleistung`), not FilterNames — list them with
|
|
52
56
|
`mastr stromerzeugung --page-size 1 --compact | jq '.data[0] | keys'`. **Dates** come as Microsoft
|
|
@@ -63,9 +67,25 @@ import { MastrClient, parseMsDate } from "@maschinenlesbar.org/marktstammdatenre
|
|
|
63
67
|
const mastr = new MastrClient();
|
|
64
68
|
const solar = await mastr.stromerzeugung({ filter: "Energieträger~eq~'2495'", pageSize: 10 });
|
|
65
69
|
solar.total; // total matching units
|
|
70
|
+
await mastr.count("stromerzeugung", { filter: "Energieträger~eq~'2495'" }); // just the count, one-row request
|
|
66
71
|
parseMsDate(String(solar.data[0]?.EinheitRegistrierungsdatum)); // Date | null
|
|
67
72
|
```
|
|
68
73
|
|
|
74
|
+
Build filters from user input with `buildFilter`, not string interpolation: a filter
|
|
75
|
+
value cannot contain `~` (the register splits on it and has no escape), so
|
|
76
|
+
`` `Ort~eq~'${input}'` `` lets an input like `Münster'~and~Energieträger~eq~'2497` add a
|
|
77
|
+
condition. `buildFilter` quotes each value and throws `MastrValidationError` for a `~`:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { buildFilter } from "@maschinenlesbar.org/marktstammdatenregister-cli";
|
|
81
|
+
|
|
82
|
+
const filter = buildFilter([
|
|
83
|
+
{ name: "Ort", op: "eq", value: town }, // quoted, ~ refused
|
|
84
|
+
{ name: "Energieträger", op: "eq", value: ["2497", "2498"] }, // comma list = any of
|
|
85
|
+
]);
|
|
86
|
+
await mastr.stromerzeugung({ filter, pageSize: 1 });
|
|
87
|
+
```
|
|
88
|
+
|
|
69
89
|
## Documentation
|
|
70
90
|
|
|
71
91
|
- [Usage.md](Usage.md) — commands, filter syntax, exit codes
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
// gas × generation / consumption), each a paged, optionally-filtered search, plus a
|
|
3
3
|
// `filters` command to discover the filterable columns and their dropdown codes.
|
|
4
4
|
import { Argument } from "commander";
|
|
5
|
-
import {
|
|
5
|
+
import { MAX_PAGE, MAX_PAGE_SIZE } from "../../client/client.js";
|
|
6
|
+
import { action, parseBoundedInt, parseFilter, parseSort, renderJson } from "../shared.js";
|
|
7
|
+
// `sortKey` is a record field that exists in that category's rows (live 2026-09-26):
|
|
8
|
+
// only stromerzeugung rows carry Bruttoleistung/Nettonennleistung, so the stderr hint
|
|
9
|
+
// must not suggest it for the other three.
|
|
6
10
|
const CATEGORIES = [
|
|
7
|
-
{ name: "stromerzeugung", desc: "Electricity-generation units (Stromerzeugung)" },
|
|
8
|
-
{ name: "stromverbrauch", desc: "Electricity-consumption units (Stromverbrauch)" },
|
|
9
|
-
{ name: "gaserzeugung", desc: "Gas-generation units (Gaserzeugung)" },
|
|
10
|
-
{ name: "gasverbrauch", desc: "Gas-consumption units (Gasverbrauch)" },
|
|
11
|
+
{ name: "stromerzeugung", desc: "Electricity-generation units (Stromerzeugung)", sortKey: "Bruttoleistung" },
|
|
12
|
+
{ name: "stromverbrauch", desc: "Electricity-consumption units (Stromverbrauch)", sortKey: "InbetriebnahmeDatum" },
|
|
13
|
+
{ name: "gaserzeugung", desc: "Gas-generation units (Gaserzeugung)", sortKey: "Erzeugungsleistung" },
|
|
14
|
+
{ name: "gasverbrauch", desc: "Gas-consumption units (Gasverbrauch)", sortKey: "MaximaleGasbezugsLeistung" },
|
|
11
15
|
];
|
|
12
16
|
const RUN = {
|
|
13
17
|
stromerzeugung: (c, q) => c.stromerzeugung(q),
|
|
@@ -15,21 +19,22 @@ const RUN = {
|
|
|
15
19
|
gaserzeugung: (c, q) => c.gaserzeugung(q),
|
|
16
20
|
gasverbrauch: (c, q) => c.gasverbrauch(q),
|
|
17
21
|
};
|
|
22
|
+
/** Build a CountQuery (filter and sort) from this command's parsed options. */
|
|
23
|
+
function buildCountQuery(opts) {
|
|
24
|
+
const q = {};
|
|
25
|
+
if (typeof opts["sort"] === "string")
|
|
26
|
+
q.sort = opts["sort"];
|
|
27
|
+
if (typeof opts["filter"] === "string")
|
|
28
|
+
q.filter = opts["filter"];
|
|
29
|
+
return q;
|
|
30
|
+
}
|
|
18
31
|
/** Build a UnitQuery from this command's parsed options. */
|
|
19
32
|
function buildQuery(opts) {
|
|
20
|
-
const q =
|
|
33
|
+
const q = buildCountQuery(opts);
|
|
21
34
|
if (typeof opts["page"] === "number")
|
|
22
35
|
q.page = opts["page"];
|
|
23
36
|
if (typeof opts["pageSize"] === "number")
|
|
24
37
|
q.pageSize = opts["pageSize"];
|
|
25
|
-
if (typeof opts["sort"] === "string")
|
|
26
|
-
q.sort = opts["sort"];
|
|
27
|
-
if (typeof opts["filter"] === "string")
|
|
28
|
-
q.filter = opts["filter"];
|
|
29
|
-
// `--total` needs only the match count (returned regardless of page size), so
|
|
30
|
-
// request a single row instead of fetching and discarding a full page.
|
|
31
|
-
if (opts["total"] === true)
|
|
32
|
-
q.pageSize = 1;
|
|
33
38
|
return q;
|
|
34
39
|
}
|
|
35
40
|
export function registerCommands(program, deps) {
|
|
@@ -37,31 +42,38 @@ export function registerCommands(program, deps) {
|
|
|
37
42
|
program
|
|
38
43
|
.command(cat.name)
|
|
39
44
|
.description(cat.desc)
|
|
40
|
-
.option("--page <n>", "1-based page number", parseBoundedInt(1,
|
|
41
|
-
.option("--page-size <n>",
|
|
45
|
+
.option("--page <n>", "1-based page number", parseBoundedInt(1, MAX_PAGE), 1)
|
|
46
|
+
.option("--page-size <n>", `rows per page (1..${MAX_PAGE_SIZE})`, parseBoundedInt(1, MAX_PAGE_SIZE), 25)
|
|
42
47
|
.option("--sort <spec>", "sort: FieldKey-asc | FieldKey-desc, where FieldKey is a record field name, not a " +
|
|
43
|
-
|
|
44
|
-
.option("--filter <spec>", "filter: FilterName~op~'value'~
|
|
45
|
-
"gt/lt strict;
|
|
46
|
-
|
|
48
|
+
`FilterName (e.g. ${cat.sortKey}-desc)`, parseSort)
|
|
49
|
+
.option("--filter <spec>", "filter: FilterName~op~'value'~and~… (see `filters`; ops eq|neq|sw|ct|nct|ew|null|nn|gt|lt, " +
|
|
50
|
+
"gt/lt strict; null/nn take ''). A malformed spec or unknown op is rejected. No ~or~: " +
|
|
51
|
+
"for several dropdown codes use one comma list, e.g. Energieträger~eq~'2497,2498'", parseFilter)
|
|
52
|
+
.option("--total", "print only the total match count, not the rows (a one-row request; --page and --page-size are ignored)")
|
|
47
53
|
.action(action(deps, async ({ client, global, opts }) => {
|
|
48
|
-
|
|
54
|
+
// `--total` is the library's count(): a one-row request for the match count.
|
|
55
|
+
const total = opts["total"] === true
|
|
56
|
+
? await client.count(cat.name, buildCountQuery(opts))
|
|
57
|
+
: undefined;
|
|
58
|
+
const page = total === undefined ? await RUN[cat.name](client, buildQuery(opts)) : { total };
|
|
49
59
|
// An unknown --sort key or --filter operator makes the server return 0 rows
|
|
50
60
|
// (not an error), which reads like "no matches". Nudge the user toward the
|
|
51
61
|
// likely cause. Sort keys are the record's field names (`Bruttoleistung`);
|
|
52
62
|
// the FilterNames from `mastr filters` ("Bruttoleistung der Einheit") don't sort.
|
|
53
63
|
if (page.total === 0 && typeof opts["sort"] === "string") {
|
|
54
64
|
deps.io.err("Note: 0 results with --sort set. If you expected matches, an unknown sort " +
|
|
55
|
-
|
|
65
|
+
`key returns 0 rows. Sort keys are record field names (e.g. ${cat.sortKey}), ` +
|
|
56
66
|
"not the FilterNames from `mastr filters`; list them with " +
|
|
57
67
|
`\`mastr ${cat.name} --page-size 1 --compact | jq '.data[0] | keys'\`.`);
|
|
58
68
|
}
|
|
69
|
+
// An unknown operator is already a usage error (filterProblem); what is left
|
|
70
|
+
// that silently gives 0 rows is a value the register can't match.
|
|
59
71
|
if (page.total === 0 && typeof opts["filter"] === "string") {
|
|
60
|
-
deps.io.err("Note: 0 results with --filter set. If you expected matches, check the
|
|
61
|
-
"
|
|
62
|
-
"
|
|
72
|
+
deps.io.err("Note: 0 results with --filter set. If you expected matches, check the values: a " +
|
|
73
|
+
"dropdown takes its code (the Value from `mastr filters`), not its label, decimals " +
|
|
74
|
+
"take a point ('4999.999'), and gt/lt are strict.");
|
|
63
75
|
}
|
|
64
|
-
renderJson(deps, global,
|
|
76
|
+
renderJson(deps, global, total ?? page);
|
|
65
77
|
}));
|
|
66
78
|
}
|
|
67
79
|
program
|
|
@@ -72,4 +84,3 @@ export function registerCommands(program, deps) {
|
|
|
72
84
|
renderJson(deps, global, await client.filterColumns(category));
|
|
73
85
|
}));
|
|
74
86
|
}
|
|
75
|
-
//# sourceMappingURL=units.js.map
|
package/dist/src/cli/index.d.ts
CHANGED
package/dist/src/cli/index.js
CHANGED
package/dist/src/cli/io.d.ts
CHANGED
package/dist/src/cli/io.js
CHANGED
package/dist/src/cli/program.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Command } from "commander";
|
|
|
7
7
|
import { defaultIO } from "./io.js";
|
|
8
8
|
import { MastrClient } from "../client/client.js";
|
|
9
9
|
import { MAX_TIMEOUT_MS } from "../client/http.js";
|
|
10
|
+
import { MAX_RETRIES } from "../client/engine.js";
|
|
10
11
|
import { parseIntArg, parseBoundedInt, parseHeaderValue, parseBaseUrl } from "./shared.js";
|
|
11
12
|
import { registerCommands } from "./commands/units.js";
|
|
12
13
|
/**
|
|
@@ -44,7 +45,7 @@ export function buildProgram(deps = defaultDeps) {
|
|
|
44
45
|
.option("--base-url <url>", "API base URL (http/https only)", parseBaseUrl, "https://www.marktstammdatenregister.de/MaStR")
|
|
45
46
|
.option("--timeout <ms>", "time limit per request in ms, whole response included (0 = no timeout)", parseBoundedInt(0, MAX_TIMEOUT_MS))
|
|
46
47
|
.option("--user-agent <ua>", "User-Agent header value", parseHeaderValue)
|
|
47
|
-
.option("--max-retries <n>",
|
|
48
|
+
.option("--max-retries <n>", `retries for transient 429/503 responses (0..${MAX_RETRIES}; each waits the server's Retry-After, up to 30 s)`, parseBoundedInt(0, MAX_RETRIES))
|
|
48
49
|
.option("--max-response-bytes <n>", "cap response body size in bytes (0 = unlimited; default 100 MiB)", parseIntArg)
|
|
49
50
|
.option("--compact", "print JSON on a single line instead of pretty-printed")
|
|
50
51
|
.option("--iso-dates", "rewrite MaStR /Date(ms)/ timestamps to ISO-8601")
|
|
@@ -52,4 +53,3 @@ export function buildProgram(deps = defaultDeps) {
|
|
|
52
53
|
registerCommands(program, deps);
|
|
53
54
|
return program;
|
|
54
55
|
}
|
|
55
|
-
//# sourceMappingURL=program.js.map
|
package/dist/src/cli/run.d.ts
CHANGED
package/dist/src/cli/run.js
CHANGED
package/dist/src/cli/shared.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CliDeps } from "./io.js";
|
|
2
2
|
import type { MastrClientOptions } from "../client/client.js";
|
|
3
|
+
import { type Problem } from "../client/validate.js";
|
|
3
4
|
/**
|
|
4
5
|
* commander value-parser: a plain base-10 non-negative integer.
|
|
5
6
|
*
|
|
@@ -8,27 +9,36 @@ import type { MastrClientOptions } from "../client/client.js";
|
|
|
8
9
|
* literals (`0x10`, `0b10`, `1e3`), signs, padding and decimals.
|
|
9
10
|
*/
|
|
10
11
|
export declare function parseIntArg(value: string): number;
|
|
11
|
-
/** commander value-parser: a non-empty (after trimming) string. */
|
|
12
|
-
export declare function parseNonEmpty(value: string): string;
|
|
13
12
|
/**
|
|
14
|
-
* commander value-parser
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
* Build a commander value-parser from a library rule: the rule's reason becomes the
|
|
14
|
+
* usage error, a valid value passes unchanged.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseWith(problem: Problem<string>): (value: string) => string;
|
|
17
|
+
/** commander value-parser: a non-empty (after trimming) string (the library's `nonBlankProblem`). */
|
|
18
|
+
export declare const parseNonEmpty: (value: string) => string;
|
|
19
|
+
/** commander value-parser for `--sort`: the library's `sortProblem` (not blank). */
|
|
20
|
+
export declare const parseSort: (value: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* commander value-parser for `--filter`: non-blank, and not a spec the register would
|
|
23
|
+
* misread (see `filterProblem`), so it becomes a usage error before any request.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseFilter(value: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* commander value-parser for `--base-url`: the library's `baseUrlProblem` (non-blank,
|
|
28
|
+
* no whitespace, an absolute http(s) URL without a query or fragment), whose reason
|
|
29
|
+
* becomes a usage error (exit 2) naming the value the user typed. The CLI keeps no
|
|
30
|
+
* rules of its own; the client constructor enforces the same rule for library callers.
|
|
21
31
|
*/
|
|
22
|
-
export declare
|
|
32
|
+
export declare const parseBaseUrl: (value: string) => string;
|
|
23
33
|
/** Build a commander value-parser for an integer constrained to [min, max]. */
|
|
24
34
|
export declare function parseBoundedInt(min: number, max: number): (value: string) => number;
|
|
25
35
|
/**
|
|
26
|
-
* commander value-parser for a value that ends up in an HTTP header (User-Agent)
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
36
|
+
* commander value-parser for a value that ends up in an HTTP header (User-Agent):
|
|
37
|
+
* the library's `headerValueProblem` (not blank, Latin-1 without control
|
|
38
|
+
* characters; tab is fine), whose reason becomes the usage error. The engine runs
|
|
39
|
+
* the same rule on `userAgent`.
|
|
30
40
|
*/
|
|
31
|
-
export declare
|
|
41
|
+
export declare const parseHeaderValue: (value: string) => string;
|
|
32
42
|
export interface GlobalOptions {
|
|
33
43
|
baseUrl?: string;
|
|
34
44
|
timeout?: number;
|
|
@@ -41,9 +51,10 @@ export interface GlobalOptions {
|
|
|
41
51
|
/** Translate resolved global CLI options into client options. */
|
|
42
52
|
export declare function toEngineOptions(global: GlobalOptions): MastrClientOptions;
|
|
43
53
|
/**
|
|
44
|
-
* Escape the
|
|
45
|
-
* ESC) but not DEL
|
|
46
|
-
*
|
|
54
|
+
* Escape the characters JSON.stringify leaves raw although a terminal acts on them.
|
|
55
|
+
* It escapes C0 (including ESC) but not DEL, the C1 range U+0080–U+009F (U+009B is
|
|
56
|
+
* the 8-bit form of CSI) or the bidi formatting characters (isBidiControl), which
|
|
57
|
+
* reorder the text that follows. The output is server data, so escape them; the
|
|
47
58
|
* result is equivalent, valid JSON (these characters only occur inside strings).
|
|
48
59
|
* Checked by char code so the source stays free of control bytes.
|
|
49
60
|
*/
|
|
@@ -68,4 +79,3 @@ export interface ActionContext {
|
|
|
68
79
|
* the trailing options object and command instance to recover the positionals.
|
|
69
80
|
*/
|
|
70
81
|
export declare function action(deps: CliDeps, fn: (ctx: ActionContext, positionals: string[]) => Promise<void>): (...args: unknown[]) => Promise<void>;
|
|
71
|
-
//# sourceMappingURL=shared.d.ts.map
|
package/dist/src/cli/shared.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import { InvalidArgumentError } from "commander";
|
|
4
4
|
import { isoifyDates } from "../client/client.js";
|
|
5
5
|
import { MastrParseError } from "../client/errors.js";
|
|
6
|
+
import { filterProblem } from "../client/filter.js";
|
|
7
|
+
import { isBidiControl } from "../client/engine.js";
|
|
8
|
+
import { baseUrlProblem, headerValueProblem, nonBlankProblem, sortProblem, } from "../client/validate.js";
|
|
6
9
|
/**
|
|
7
10
|
* commander value-parser: a plain base-10 non-negative integer.
|
|
8
11
|
*
|
|
@@ -20,39 +23,40 @@ export function parseIntArg(value) {
|
|
|
20
23
|
}
|
|
21
24
|
return n;
|
|
22
25
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return value
|
|
26
|
+
/**
|
|
27
|
+
* Build a commander value-parser from a library rule: the rule's reason becomes the
|
|
28
|
+
* usage error, a valid value passes unchanged.
|
|
29
|
+
*/
|
|
30
|
+
export function parseWith(problem) {
|
|
31
|
+
return (value) => {
|
|
32
|
+
const reason = problem(value);
|
|
33
|
+
if (reason !== undefined)
|
|
34
|
+
throw new InvalidArgumentError(reason);
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
29
37
|
}
|
|
38
|
+
/** commander value-parser: a non-empty (after trimming) string (the library's `nonBlankProblem`). */
|
|
39
|
+
export const parseNonEmpty = parseWith(nonBlankProblem);
|
|
40
|
+
/** commander value-parser for `--sort`: the library's `sortProblem` (not blank). */
|
|
41
|
+
export const parseSort = parseWith(sortProblem);
|
|
30
42
|
/**
|
|
31
|
-
* commander value-parser for `--
|
|
32
|
-
*
|
|
33
|
-
* The transport already rejects any non-http(s) scheme, so this is defence in
|
|
34
|
-
* depth — but doing the check at parse time turns a bad scheme (e.g. `file:` /
|
|
35
|
-
* `data:` / `ftp:`) into a friendly usage error (exit 2) with the value the user
|
|
36
|
-
* actually typed, rather than a network error (exit 6) echoing the fully built
|
|
37
|
-
* request URL. The transport check stays the enforcement point for library callers.
|
|
43
|
+
* commander value-parser for `--filter`: non-blank, and not a spec the register would
|
|
44
|
+
* misread (see `filterProblem`), so it becomes a usage error before any request.
|
|
38
45
|
*/
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
let parsed;
|
|
45
|
-
try {
|
|
46
|
-
parsed = new URL(trimmed);
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
throw new InvalidArgumentError("Expected an absolute http(s) URL.");
|
|
50
|
-
}
|
|
51
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
52
|
-
throw new InvalidArgumentError("Only http and https URLs are supported.");
|
|
53
|
-
}
|
|
46
|
+
export function parseFilter(value) {
|
|
47
|
+
parseNonEmpty(value);
|
|
48
|
+
const problem = filterProblem(value);
|
|
49
|
+
if (problem !== undefined)
|
|
50
|
+
throw new InvalidArgumentError(problem);
|
|
54
51
|
return value;
|
|
55
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* commander value-parser for `--base-url`: the library's `baseUrlProblem` (non-blank,
|
|
55
|
+
* no whitespace, an absolute http(s) URL without a query or fragment), whose reason
|
|
56
|
+
* becomes a usage error (exit 2) naming the value the user typed. The CLI keeps no
|
|
57
|
+
* rules of its own; the client constructor enforces the same rule for library callers.
|
|
58
|
+
*/
|
|
59
|
+
export const parseBaseUrl = parseWith(baseUrlProblem);
|
|
56
60
|
/** Build a commander value-parser for an integer constrained to [min, max]. */
|
|
57
61
|
export function parseBoundedInt(min, max) {
|
|
58
62
|
return (value) => {
|
|
@@ -65,20 +69,12 @@ export function parseBoundedInt(min, max) {
|
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
/**
|
|
68
|
-
* commander value-parser for a value that ends up in an HTTP header (User-Agent)
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
+
* commander value-parser for a value that ends up in an HTTP header (User-Agent):
|
|
73
|
+
* the library's `headerValueProblem` (not blank, Latin-1 without control
|
|
74
|
+
* characters; tab is fine), whose reason becomes the usage error. The engine runs
|
|
75
|
+
* the same rule on `userAgent`.
|
|
72
76
|
*/
|
|
73
|
-
export
|
|
74
|
-
for (let i = 0; i < value.length; i++) {
|
|
75
|
-
const c = value.charCodeAt(i);
|
|
76
|
-
if ((c < 0x20 && c !== 0x09) || c === 0x7f) {
|
|
77
|
-
throw new InvalidArgumentError("Value contains control characters.");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return value;
|
|
81
|
-
}
|
|
77
|
+
export const parseHeaderValue = parseWith(headerValueProblem);
|
|
82
78
|
/** Translate resolved global CLI options into client options. */
|
|
83
79
|
export function toEngineOptions(global) {
|
|
84
80
|
const options = {};
|
|
@@ -95,9 +91,10 @@ export function toEngineOptions(global) {
|
|
|
95
91
|
return options;
|
|
96
92
|
}
|
|
97
93
|
/**
|
|
98
|
-
* Escape the
|
|
99
|
-
* ESC) but not DEL
|
|
100
|
-
*
|
|
94
|
+
* Escape the characters JSON.stringify leaves raw although a terminal acts on them.
|
|
95
|
+
* It escapes C0 (including ESC) but not DEL, the C1 range U+0080–U+009F (U+009B is
|
|
96
|
+
* the 8-bit form of CSI) or the bidi formatting characters (isBidiControl), which
|
|
97
|
+
* reorder the text that follows. The output is server data, so escape them; the
|
|
101
98
|
* result is equivalent, valid JSON (these characters only occur inside strings).
|
|
102
99
|
* Checked by char code so the source stays free of control bytes.
|
|
103
100
|
*/
|
|
@@ -106,7 +103,7 @@ export function escapeControlChars(json) {
|
|
|
106
103
|
let from = 0;
|
|
107
104
|
for (let i = 0; i < json.length; i++) {
|
|
108
105
|
const c = json.charCodeAt(i);
|
|
109
|
-
if (c >= 0x7f && c <= 0x9f) {
|
|
106
|
+
if ((c >= 0x7f && c <= 0x9f) || isBidiControl(c)) {
|
|
110
107
|
result += json.slice(from, i) + "\\u" + c.toString(16).padStart(4, "0");
|
|
111
108
|
from = i + 1;
|
|
112
109
|
}
|
|
@@ -152,4 +149,3 @@ export function action(deps, fn) {
|
|
|
152
149
|
await fn({ client, global, opts: command.opts() }, positionals);
|
|
153
150
|
};
|
|
154
151
|
}
|
|
155
|
-
//# sourceMappingURL=shared.js.map
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { type EngineOptions } from "./engine.js";
|
|
2
|
-
import type { FilterColumn, UnitCategory, UnitPage, UnitQuery } from "./types.js";
|
|
2
|
+
import type { CountQuery, FilterColumn, UnitCategory, UnitPage, UnitQuery } from "./types.js";
|
|
3
|
+
/** Largest `page` the client (and the CLI's `--page`) accepts. */
|
|
4
|
+
export declare const MAX_PAGE = 1000000;
|
|
5
|
+
/** Largest `pageSize` the client (and the CLI's `--page-size`) accepts. */
|
|
6
|
+
export declare const MAX_PAGE_SIZE = 5000;
|
|
3
7
|
/** Options for the MaStR client (engine options only — the API needs no auth). */
|
|
4
8
|
export type MastrClientOptions = EngineOptions;
|
|
5
9
|
/**
|
|
6
10
|
* Parse a MaStR Microsoft-AJAX date string (`"/Date(1548979200000)/"`) into a Date.
|
|
7
|
-
*
|
|
11
|
+
* The offset form `"/Date(1548979200000+0100)/"` is accepted too; its milliseconds
|
|
12
|
+
* are UTC already, the offset only names the sender's zone. Returns null if the
|
|
13
|
+
* string is not in that format or the value is outside the Date range.
|
|
8
14
|
*/
|
|
9
15
|
export declare function parseMsDate(value: string): Date | null;
|
|
10
16
|
/**
|
|
@@ -19,8 +25,21 @@ export declare class MastrClient {
|
|
|
19
25
|
* Fetch one page of units for a category. Sends the full Kendo param set —
|
|
20
26
|
* `sort`, `page`, `pageSize`, `group`, `filter` — always, because the server
|
|
21
27
|
* rejects a request with `group`/`filter` missing ("Die Anfrage ist Null.").
|
|
28
|
+
* An unknown category, a `page` outside 1..`MAX_PAGE`, a `pageSize` outside
|
|
29
|
+
* 1..`MAX_PAGE_SIZE`, a blank `sort` ({@link sortProblem}) or a filter the register
|
|
30
|
+
* would misread (e.g. `~or~`, see {@link validateFilter}) is rejected with a
|
|
31
|
+
* `MastrValidationError` before any request.
|
|
22
32
|
*/
|
|
23
33
|
units(category: UnitCategory, query?: UnitQuery): Promise<UnitPage>;
|
|
34
|
+
/**
|
|
35
|
+
* The number of units in a category that match `filter` (all units without one).
|
|
36
|
+
* Fetches a single row (`page=1`, `pageSize=1`) and returns the envelope's
|
|
37
|
+
* `Total`, which counts every match regardless of the page. `sort` is forwarded:
|
|
38
|
+
* an unknown sort key makes the register answer 0. Paging options are refused
|
|
39
|
+
* (`countQueryProblem`), as are the inputs `units()` refuses, all with a
|
|
40
|
+
* `MastrValidationError` before any request.
|
|
41
|
+
*/
|
|
42
|
+
count(category: UnitCategory, query?: CountQuery): Promise<number>;
|
|
24
43
|
/** Electricity-generation units (`Stromerzeugung`). */
|
|
25
44
|
stromerzeugung(query?: UnitQuery): Promise<UnitPage>;
|
|
26
45
|
/** Electricity-consumption units (`Stromverbrauch`). */
|
|
@@ -29,7 +48,10 @@ export declare class MastrClient {
|
|
|
29
48
|
gaserzeugung(query?: UnitQuery): Promise<UnitPage>;
|
|
30
49
|
/** Gas-consumption units (`Gasverbrauch`). */
|
|
31
50
|
gasverbrauch(query?: UnitQuery): Promise<UnitPage>;
|
|
32
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* The filterable columns (names, types, dropdown codes) for a category. An `Errors`
|
|
53
|
+
* envelope throws `MastrApiError`, any other non-array reply `MastrParseError` —
|
|
54
|
+
* never an empty list, which would read as "this category has no filters".
|
|
55
|
+
*/
|
|
33
56
|
filterColumns(category: UnitCategory): Promise<FilterColumn[]>;
|
|
34
57
|
}
|
|
35
|
-
//# sourceMappingURL=client.d.ts.map
|