@maschinenlesbar.org/mudab-cli 0.0.1

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.
Files changed (57) hide show
  1. package/CONTRIBUTING.md +25 -0
  2. package/LICENSE +661 -0
  3. package/LICENSING.md +47 -0
  4. package/README.md +78 -0
  5. package/dist/src/cli/commands/list.d.ts +4 -0
  6. package/dist/src/cli/commands/list.d.ts.map +1 -0
  7. package/dist/src/cli/commands/list.js +70 -0
  8. package/dist/src/cli/commands/list.js.map +1 -0
  9. package/dist/src/cli/index.d.ts +3 -0
  10. package/dist/src/cli/index.d.ts.map +1 -0
  11. package/dist/src/cli/index.js +11 -0
  12. package/dist/src/cli/index.js.map +1 -0
  13. package/dist/src/cli/io.d.ts +12 -0
  14. package/dist/src/cli/io.d.ts.map +1 -0
  15. package/dist/src/cli/io.js +7 -0
  16. package/dist/src/cli/io.js.map +1 -0
  17. package/dist/src/cli/program.d.ts +7 -0
  18. package/dist/src/cli/program.d.ts.map +1 -0
  19. package/dist/src/cli/program.js +54 -0
  20. package/dist/src/cli/program.js.map +1 -0
  21. package/dist/src/cli/run.d.ts +3 -0
  22. package/dist/src/cli/run.d.ts.map +1 -0
  23. package/dist/src/cli/run.js +87 -0
  24. package/dist/src/cli/run.js.map +1 -0
  25. package/dist/src/cli/shared.d.ts +79 -0
  26. package/dist/src/cli/shared.d.ts.map +1 -0
  27. package/dist/src/cli/shared.js +121 -0
  28. package/dist/src/cli/shared.js.map +1 -0
  29. package/dist/src/client/client.d.ts +37 -0
  30. package/dist/src/client/client.d.ts.map +1 -0
  31. package/dist/src/client/client.js +78 -0
  32. package/dist/src/client/client.js.map +1 -0
  33. package/dist/src/client/engine.d.ts +59 -0
  34. package/dist/src/client/engine.d.ts.map +1 -0
  35. package/dist/src/client/engine.js +125 -0
  36. package/dist/src/client/engine.js.map +1 -0
  37. package/dist/src/client/errors.d.ts +36 -0
  38. package/dist/src/client/errors.d.ts.map +1 -0
  39. package/dist/src/client/errors.js +43 -0
  40. package/dist/src/client/errors.js.map +1 -0
  41. package/dist/src/client/http.d.ts +26 -0
  42. package/dist/src/client/http.d.ts.map +1 -0
  43. package/dist/src/client/http.js +80 -0
  44. package/dist/src/client/http.js.map +1 -0
  45. package/dist/src/client/index.d.ts +9 -0
  46. package/dist/src/client/index.d.ts.map +1 -0
  47. package/dist/src/client/index.js +7 -0
  48. package/dist/src/client/index.js.map +1 -0
  49. package/dist/src/client/types.d.ts +173 -0
  50. package/dist/src/client/types.d.ts.map +1 -0
  51. package/dist/src/client/types.js +8 -0
  52. package/dist/src/client/types.js.map +1 -0
  53. package/dist/src/index.d.ts +2 -0
  54. package/dist/src/index.d.ts.map +1 -0
  55. package/dist/src/index.js +3 -0
  56. package/dist/src/index.js.map +1 -0
  57. package/package.json +70 -0
@@ -0,0 +1,121 @@
1
+ // Shared helpers used across CLI command groups: option parsers, the global
2
+ // option resolver, the FilterRequest builder, and JSON rendering.
3
+ import { InvalidArgumentError } from "commander";
4
+ import { MudabValidationError } from "../client/errors.js";
5
+ /** Default `range.count` — MUDAB returns the WHOLE table when no range is sent. */
6
+ export const DEFAULT_COUNT = 100;
7
+ /**
8
+ * commander value-parser: a plain base-10 non-negative integer.
9
+ *
10
+ * Uses a strict regex rather than `Number()` coercion, which would otherwise
11
+ * accept empty/whitespace strings (`Number("") === 0`), hex/binary/scientific
12
+ * literals (`0x10`, `0b10`, `1e3`), signs, padding and decimals.
13
+ */
14
+ export function parseIntArg(value) {
15
+ if (!/^[0-9]+$/.test(value)) {
16
+ throw new InvalidArgumentError("Expected a non-negative integer.");
17
+ }
18
+ const n = Number(value);
19
+ if (!Number.isSafeInteger(n)) {
20
+ throw new InvalidArgumentError("Expected a non-negative integer.");
21
+ }
22
+ return n;
23
+ }
24
+ /** commander value-parser: a non-empty (after trimming) string. */
25
+ export function parseNonEmpty(value) {
26
+ if (value.trim() === "") {
27
+ throw new InvalidArgumentError("Expected a non-empty value.");
28
+ }
29
+ return value;
30
+ }
31
+ /** Build a commander value-parser for an integer constrained to [min, max]. */
32
+ export function parseBoundedInt(min, max) {
33
+ return (value) => {
34
+ const n = parseIntArg(value);
35
+ if (n < min)
36
+ throw new InvalidArgumentError(`Must be >= ${min}.`);
37
+ if (n > max)
38
+ throw new InvalidArgumentError(`Must be <= ${max}.`);
39
+ return n;
40
+ };
41
+ }
42
+ /**
43
+ * commander value-parser for a value that ends up in an HTTP header (User-Agent).
44
+ * Rejects control characters — a CR/LF (or other C0/DEL byte) would otherwise
45
+ * reach Node's HTTP layer and throw an opaque `ERR_INVALID_CHAR`, which escapes
46
+ * typed-error handling and surfaces as an ugly "Unexpected error". Tab (0x09) is
47
+ * allowed; checked by char code so the source stays free of control bytes.
48
+ */
49
+ export function parseHeaderValue(value) {
50
+ for (let i = 0; i < value.length; i++) {
51
+ const c = value.charCodeAt(i);
52
+ if ((c < 0x20 && c !== 0x09) || c === 0x7f) {
53
+ throw new InvalidArgumentError("Value contains control characters.");
54
+ }
55
+ }
56
+ return value;
57
+ }
58
+ /** Translate resolved global CLI options into client options. */
59
+ export function toEngineOptions(global) {
60
+ const options = {};
61
+ if (global.baseUrl !== undefined)
62
+ options.baseUrl = global.baseUrl;
63
+ if (global.timeout !== undefined)
64
+ options.timeoutMs = global.timeout;
65
+ if (global.userAgent !== undefined)
66
+ options.userAgent = global.userAgent;
67
+ if (global.maxRetries !== undefined)
68
+ options.maxRetries = global.maxRetries;
69
+ if (global.maxResponseBytes !== undefined)
70
+ options.maxResponseBytes = global.maxResponseBytes;
71
+ return options;
72
+ }
73
+ /**
74
+ * Add the shared list options (range / filter / orderby) to a command.
75
+ * Kept in one place so every list command exposes an identical surface.
76
+ */
77
+ export function addListOptions(cmd) {
78
+ return cmd
79
+ .option("--from <n>", "skip this many rows (range.from)", parseIntArg)
80
+ .option("--count <n>", `max rows to return (default ${DEFAULT_COUNT})`, parseIntArg)
81
+ .option("--all", "return the whole table (omit the range — can be very large; not combinable with --from/--count)");
82
+ }
83
+ /**
84
+ * Build the request body from parsed list options — a `range` (paging) only.
85
+ *
86
+ * `range` is omitted entirely with `--all`; otherwise `count` defaults to
87
+ * {@link DEFAULT_COUNT} so a bare command never dumps a whole table, and `from`
88
+ * defaults to 0 (the server answers a count-only range with an HTTP 500).
89
+ */
90
+ export function buildFilterRequest(opts) {
91
+ if (opts.all) {
92
+ if (opts.from !== undefined || opts.count !== undefined) {
93
+ throw new MudabValidationError("--all cannot be combined with --from/--count.");
94
+ }
95
+ return {};
96
+ }
97
+ return { range: { from: opts.from ?? 0, count: opts.count ?? DEFAULT_COUNT } };
98
+ }
99
+ /** Render a JSON value to stdout, pretty by default, compact with --compact. */
100
+ export function renderJson(deps, global, value) {
101
+ const text = global.compact ? JSON.stringify(value) : JSON.stringify(value, null, 2);
102
+ deps.io.out(text);
103
+ }
104
+ /**
105
+ * Wrap an async command action with consistent global-option resolution and
106
+ * client construction. The callback receives a context (client + resolved global
107
+ * options + this command's options) and the command's positional arguments.
108
+ *
109
+ * Commander invokes actions as (arg1, ..., argN, options, command); we slice off
110
+ * the trailing options object and command instance to recover the positionals.
111
+ */
112
+ export function action(deps, fn) {
113
+ return async (...args) => {
114
+ const command = args[args.length - 1];
115
+ const positionals = args.slice(0, Math.max(0, args.length - 2));
116
+ const global = command.optsWithGlobals();
117
+ const client = deps.createClient(toEngineOptions(global));
118
+ await fn({ client, global, opts: command.opts() }, positionals);
119
+ };
120
+ }
121
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/cli/shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { type EngineOptions } from "./engine.js";
2
+ import type { FilterRequest, HelcomPLCStation, Messstation, MesswertPLC, Parameter, ParameterPLC, ParameterValue, ProjectStation } from "./types.js";
3
+ /** Options for the MUDAB client (engine options only — the API needs no auth). */
4
+ export type MudabClientOptions = EngineOptions;
5
+ /** The four compartment-specific parameter endpoints. */
6
+ export type ParameterCompartment = "biologie" | "biota" | "wasser" | "sediment";
7
+ /**
8
+ * Extract the row array from a MUDAB response. The API always returns a single-key
9
+ * object wrapping the array, but the key is not reliably the path name (e.g.
10
+ * `/STATION_SMALL` -> key `V_STATION_SMALL`), so we take the first array-valued
11
+ * property rather than trusting a fixed key. Returns `[]` for a null/empty reply.
12
+ */
13
+ export declare function extractRows<T>(res: unknown): T[];
14
+ export declare class MudabClient {
15
+ private readonly engine;
16
+ constructor(options?: MudabClientOptions);
17
+ /** POST a FilterRequest to `resource` and return the extracted row array. */
18
+ private filterList;
19
+ /** Measurement stations (`STATION_SMALL`). */
20
+ stations(req?: FilterRequest): Promise<Messstation[]>;
21
+ /** Project stations (`PROJECTSTATION_SMALL`). */
22
+ projectStations(req?: FilterRequest): Promise<ProjectStation[]>;
23
+ /**
24
+ * Measured parameters (`MV_PARAMETER`). Pass a compartment to hit the
25
+ * compartment-specific endpoint (`MV_PARAMETER_{BIOLOGIE,BIOTA,WASSER,SEDIMENT}`).
26
+ */
27
+ parameters(req?: FilterRequest, compartment?: ParameterCompartment): Promise<Parameter[]>;
28
+ /** Individual station measurements (`MV_STATION_MSMNT`) — a very large table. */
29
+ measurements(req?: FilterRequest): Promise<ParameterValue[]>;
30
+ /** HELCOM PLC stations (`V_PLC_STATION`). */
31
+ plcStations(req?: FilterRequest): Promise<HelcomPLCStation[]>;
32
+ /** Parameters measured at PLC stations (`V_GEMESSENE_PARA_PLC`). */
33
+ plcParameters(req?: FilterRequest): Promise<ParameterPLC[]>;
34
+ /** Measured values at PLC stations (`V_MESSWERTE_PLC`). */
35
+ plcMeasurements(req?: FilterRequest): Promise<MesswertPLC[]>;
36
+ }
37
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/client/client.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,SAAS,EACT,YAAY,EACZ,cAAc,EACd,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,kFAAkF;AAClF,MAAM,MAAM,kBAAkB,GAAG,aAAa,CAAC;AAE/C,yDAAyD;AACzD,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAShF;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,CAAC,EAAE,CAQhD;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,OAAO,GAAE,kBAAuB;IAI5C,6EAA6E;YAC/D,UAAU;IAKxB,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAIrD,iDAAiD;IACjD,eAAe,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAI/D;;;OAGG;IACH,UAAU,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAKzF,iFAAiF;IACjF,YAAY,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAI5D,6CAA6C;IAC7C,WAAW,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAI7D,oEAAoE;IACpE,aAAa,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAI3D,2DAA2D;IAC3D,eAAe,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAG7D"}
@@ -0,0 +1,78 @@
1
+ // MudabClient — a typed client over the MUDAB (Meeresumweltdatenbank) REST API
2
+ // (geoportal.bafg.de/mudab/rest/BaseController/FilterElements): marine-monitoring
3
+ // data from the German coastal Bundesländer and research institutions.
4
+ //
5
+ // Every endpoint is a POST that takes a FilterRequest (filter / range / orderby)
6
+ // and returns a single-key object wrapping the row array. There is NO auth.
7
+ //
8
+ // const c = new MudabClient();
9
+ // await c.stations({ range: { count: 10 } });
10
+ // await c.parameters({ filter: { and: { col: "COMPT_DS", op: "=", value: "CW" } } });
11
+ import { RequestEngine } from "./engine.js";
12
+ const COMPARTMENT_ENDPOINT = {
13
+ biologie: "/MV_PARAMETER_BIOLOGIE",
14
+ biota: "/MV_PARAMETER_BIOTA",
15
+ wasser: "/MV_PARAMETER_WASSER",
16
+ sediment: "/MV_PARAMETER_SEDIMENT",
17
+ };
18
+ /**
19
+ * Extract the row array from a MUDAB response. The API always returns a single-key
20
+ * object wrapping the array, but the key is not reliably the path name (e.g.
21
+ * `/STATION_SMALL` -> key `V_STATION_SMALL`), so we take the first array-valued
22
+ * property rather than trusting a fixed key. Returns `[]` for a null/empty reply.
23
+ */
24
+ export function extractRows(res) {
25
+ if (Array.isArray(res))
26
+ return res;
27
+ if (res && typeof res === "object") {
28
+ for (const value of Object.values(res)) {
29
+ if (Array.isArray(value))
30
+ return value;
31
+ }
32
+ }
33
+ return [];
34
+ }
35
+ export class MudabClient {
36
+ engine;
37
+ constructor(options = {}) {
38
+ this.engine = new RequestEngine(options);
39
+ }
40
+ /** POST a FilterRequest to `resource` and return the extracted row array. */
41
+ async filterList(resource, req = {}) {
42
+ const res = await this.engine.postJson(resource, req);
43
+ return extractRows(res);
44
+ }
45
+ /** Measurement stations (`STATION_SMALL`). */
46
+ stations(req) {
47
+ return this.filterList("/STATION_SMALL", req);
48
+ }
49
+ /** Project stations (`PROJECTSTATION_SMALL`). */
50
+ projectStations(req) {
51
+ return this.filterList("/PROJECTSTATION_SMALL", req);
52
+ }
53
+ /**
54
+ * Measured parameters (`MV_PARAMETER`). Pass a compartment to hit the
55
+ * compartment-specific endpoint (`MV_PARAMETER_{BIOLOGIE,BIOTA,WASSER,SEDIMENT}`).
56
+ */
57
+ parameters(req, compartment) {
58
+ const resource = compartment ? COMPARTMENT_ENDPOINT[compartment] : "/MV_PARAMETER";
59
+ return this.filterList(resource, req);
60
+ }
61
+ /** Individual station measurements (`MV_STATION_MSMNT`) — a very large table. */
62
+ measurements(req) {
63
+ return this.filterList("/MV_STATION_MSMNT", req);
64
+ }
65
+ /** HELCOM PLC stations (`V_PLC_STATION`). */
66
+ plcStations(req) {
67
+ return this.filterList("/V_PLC_STATION", req);
68
+ }
69
+ /** Parameters measured at PLC stations (`V_GEMESSENE_PARA_PLC`). */
70
+ plcParameters(req) {
71
+ return this.filterList("/V_GEMESSENE_PARA_PLC", req);
72
+ }
73
+ /** Measured values at PLC stations (`V_MESSWERTE_PLC`). */
74
+ plcMeasurements(req) {
75
+ return this.filterList("/V_MESSWERTE_PLC", req);
76
+ }
77
+ }
78
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/client/client.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAClF,uEAAuE;AACvE,EAAE;AACF,iFAAiF;AACjF,4EAA4E;AAC5E,EAAE;AACF,iCAAiC;AACjC,gDAAgD;AAChD,wFAAwF;AAExF,OAAO,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAkBhE,MAAM,oBAAoB,GAAyC;IACjE,QAAQ,EAAE,wBAAwB;IAClC,KAAK,EAAE,qBAAqB;IAC5B,MAAM,EAAE,sBAAsB;IAC9B,QAAQ,EAAE,wBAAwB;CACnC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAI,GAAY;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAU,CAAC;IAC1C,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACnC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,GAA8B,CAAC,EAAE,CAAC;YAClE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAY,CAAC;QAChD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,OAAO,WAAW;IACL,MAAM,CAAgB;IAEvC,YAAY,UAA8B,EAAE;QAC1C,IAAI,CAAC,MAAM,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,6EAA6E;IACrE,KAAK,CAAC,UAAU,CAAI,QAAgB,EAAE,MAAqB,EAAE;QACnE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAU,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC/D,OAAO,WAAW,CAAI,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,8CAA8C;IAC9C,QAAQ,CAAC,GAAmB;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,iDAAiD;IACjD,eAAe,CAAC,GAAmB;QACjC,OAAO,IAAI,CAAC,UAAU,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,GAAmB,EAAE,WAAkC;QAChE,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;QACnF,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC;IAED,iFAAiF;IACjF,YAAY,CAAC,GAAmB;QAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC;IAED,6CAA6C;IAC7C,WAAW,CAAC,GAAmB;QAC7B,OAAO,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,oEAAoE;IACpE,aAAa,CAAC,GAAmB;QAC/B,OAAO,IAAI,CAAC,UAAU,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,2DAA2D;IAC3D,eAAe,CAAC,GAAmB;QACjC,OAAO,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,59 @@
1
+ import { type Transport } from "./http.js";
2
+ export declare const DEFAULT_BASE_URL = "https://geoportal.bafg.de/mudab/rest/BaseController/FilterElements";
3
+ export interface RawResponse {
4
+ data: Buffer;
5
+ contentType: string;
6
+ status: number;
7
+ }
8
+ export interface EngineOptions {
9
+ /** Base URL of the API. Defaults to the canonical geoportal.bafg.de MUDAB base. */
10
+ baseUrl?: string;
11
+ /** Swappable transport. Defaults to the built-in node http/https transport. */
12
+ transport?: Transport;
13
+ /** Value of the User-Agent header. */
14
+ userAgent?: string;
15
+ /** Extra headers sent on every request. */
16
+ defaultHeaders?: Record<string, string>;
17
+ /** Per-request timeout in milliseconds (0 disables). */
18
+ timeoutMs?: number;
19
+ /** Number of automatic retries for transient (429/503) responses. */
20
+ maxRetries?: number;
21
+ /** Base backoff between retries in milliseconds (grows linearly). */
22
+ retryDelayMs?: number;
23
+ /**
24
+ * Hard cap on response body size in bytes (defends against memory exhaustion
25
+ * from a hostile/buggy endpoint). Defaults to 100 MiB; set to 0 for no limit.
26
+ */
27
+ maxResponseBytes?: number;
28
+ /** Injectable sleep, primarily for deterministic tests. */
29
+ sleep?: (ms: number) => Promise<void>;
30
+ }
31
+ export declare class RequestEngine {
32
+ private readonly baseUrl;
33
+ private readonly transport;
34
+ private readonly userAgent;
35
+ private readonly defaultHeaders;
36
+ private readonly timeoutMs;
37
+ private readonly maxRetries;
38
+ private readonly retryDelayMs;
39
+ private readonly maxResponseBytes;
40
+ private readonly sleep;
41
+ constructor(options?: EngineOptions);
42
+ /** Build a fully-qualified URL from a path (all parameters travel in the body). */
43
+ buildUrl(path: string): string;
44
+ /**
45
+ * Perform a request with Accept negotiation and transient-error retries.
46
+ *
47
+ * Redirects are deliberately NOT followed: the canonical host answers directly,
48
+ * and following a cross-origin 3xx blindly is a footgun. A 3xx therefore
49
+ * surfaces as an error, with a hint to use the canonical base URL.
50
+ */
51
+ request(method: string, path: string, options?: {
52
+ accept: string;
53
+ body?: Buffer;
54
+ }): Promise<RawResponse>;
55
+ /** POST a JSON payload and parse the JSON reply into `T`. */
56
+ postJson<T>(path: string, payload: unknown): Promise<T>;
57
+ private toApiError;
58
+ }
59
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"AAOA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAG9D,eAAO,MAAM,gBAAgB,uEAAuE,CAAC;AAGrG,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAOD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;gBAE1C,OAAO,GAAE,aAAkB;IAYvC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAK9B;;;;;;OAMG;IACG,OAAO,CACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAmC,GAC1E,OAAO,CAAC,WAAW,CAAC;IAwCvB,6DAA6D;IACvD,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAe7D,OAAO,CAAC,UAAU;CAsBnB"}
@@ -0,0 +1,125 @@
1
+ // The request engine: turns logical (method, path, body) calls into HTTP requests
2
+ // via a Transport, applies retry/backoff for transient statuses (429, 503), and
3
+ // decodes JSON responses.
4
+ //
5
+ // MUDAB is POST-only with a JSON body (the FilterRequest); every parameter travels
6
+ // in the body, so there is no query-string builder. There is no authentication.
7
+ import { nodeHttpTransport } from "./http.js";
8
+ import { MudabApiError, MudabParseError } from "./errors.js";
9
+ export const DEFAULT_BASE_URL = "https://geoportal.bafg.de/mudab/rest/BaseController/FilterElements";
10
+ const DEFAULT_USER_AGENT = "mudab-cli";
11
+ const DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024 * 1024;
12
+ const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13
+ export class RequestEngine {
14
+ baseUrl;
15
+ transport;
16
+ userAgent;
17
+ defaultHeaders;
18
+ timeoutMs;
19
+ maxRetries;
20
+ retryDelayMs;
21
+ maxResponseBytes;
22
+ sleep;
23
+ constructor(options = {}) {
24
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
25
+ this.transport = options.transport ?? nodeHttpTransport;
26
+ this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
27
+ this.defaultHeaders = options.defaultHeaders ?? {};
28
+ this.timeoutMs = options.timeoutMs ?? 30_000;
29
+ this.maxRetries = options.maxRetries ?? 2;
30
+ this.retryDelayMs = options.retryDelayMs ?? 200;
31
+ this.maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
32
+ this.sleep = options.sleep ?? realSleep;
33
+ }
34
+ /** Build a fully-qualified URL from a path (all parameters travel in the body). */
35
+ buildUrl(path) {
36
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
37
+ return `${this.baseUrl}${normalizedPath}`;
38
+ }
39
+ /**
40
+ * Perform a request with Accept negotiation and transient-error retries.
41
+ *
42
+ * Redirects are deliberately NOT followed: the canonical host answers directly,
43
+ * and following a cross-origin 3xx blindly is a footgun. A 3xx therefore
44
+ * surfaces as an error, with a hint to use the canonical base URL.
45
+ */
46
+ async request(method, path, options = { accept: "application/json" }) {
47
+ const url = this.buildUrl(path);
48
+ const headers = {
49
+ ...this.defaultHeaders,
50
+ Accept: options.accept,
51
+ "User-Agent": this.userAgent,
52
+ };
53
+ if (options.body !== undefined) {
54
+ headers["Content-Type"] = "application/json";
55
+ headers["Content-Length"] = String(options.body.length);
56
+ }
57
+ let attempt = 0;
58
+ for (;;) {
59
+ const response = await this.transport({
60
+ method,
61
+ url,
62
+ headers,
63
+ ...(options.body !== undefined ? { body: options.body } : {}),
64
+ timeoutMs: this.timeoutMs,
65
+ ...(this.maxResponseBytes > 0 ? { maxResponseBytes: this.maxResponseBytes } : {}),
66
+ });
67
+ const status = response.status;
68
+ const retryable = status === 429 || status === 503;
69
+ if (retryable && attempt < this.maxRetries) {
70
+ attempt += 1;
71
+ await this.sleep(this.retryDelayMs * attempt);
72
+ continue;
73
+ }
74
+ const contentType = String(response.headers["content-type"] ?? "");
75
+ if (status < 200 || status >= 300) {
76
+ throw this.toApiError(method, url, status, response.body);
77
+ }
78
+ return { data: response.body, contentType, status };
79
+ }
80
+ }
81
+ /** POST a JSON payload and parse the JSON reply into `T`. */
82
+ async postJson(path, payload) {
83
+ const body = Buffer.from(JSON.stringify(payload ?? {}), "utf8");
84
+ const res = await this.request("POST", path, { accept: "application/json", body });
85
+ const text = res.data.toString("utf8");
86
+ // A 204 or empty body is not a parse failure — surface it as null.
87
+ if (res.status === 204 || text.trim().length === 0) {
88
+ return null;
89
+ }
90
+ try {
91
+ return JSON.parse(text);
92
+ }
93
+ catch (cause) {
94
+ throw new MudabParseError(`Failed to parse JSON response from ${path}`, { cause });
95
+ }
96
+ }
97
+ toApiError(method, url, status, body) {
98
+ const text = body.toString("utf8");
99
+ let detail;
100
+ if (status >= 300 && status < 400) {
101
+ detail = "unexpected redirect — use the canonical base URL (default " + DEFAULT_BASE_URL + ")";
102
+ }
103
+ else {
104
+ try {
105
+ const parsed = JSON.parse(text);
106
+ if (typeof parsed?.detail === "string")
107
+ detail = parsed.detail;
108
+ else if (typeof parsed?.message === "string")
109
+ detail = parsed.message;
110
+ else if (typeof parsed?.error === "string")
111
+ detail = parsed.error;
112
+ }
113
+ catch {
114
+ // Not JSON. Surface a short, whitespace-collapsed snippet of a textual
115
+ // body so the failure isn't context-free; skip HTML pages (start with "<").
116
+ const snippet = text.trim().replace(/\s+/g, " ");
117
+ if (snippet.length > 0 && !snippet.startsWith("<")) {
118
+ detail = snippet.length > 200 ? `${snippet.slice(0, 200)}…` : snippet;
119
+ }
120
+ }
121
+ }
122
+ return new MudabApiError({ status, url, method, body: text, detail });
123
+ }
124
+ }
125
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /** Base class for every error originating from this client. */
2
+ export declare class MudabError extends Error {
3
+ constructor(message: string, options?: {
4
+ cause?: unknown;
5
+ });
6
+ }
7
+ /**
8
+ * The API responded with a non-2xx status code. `detail` holds a human-readable
9
+ * message extracted from the response body when one is present.
10
+ */
11
+ export declare class MudabApiError extends MudabError {
12
+ readonly status: number;
13
+ readonly detail: string | undefined;
14
+ readonly url: string;
15
+ readonly method: string;
16
+ readonly body: string;
17
+ constructor(args: {
18
+ status: number;
19
+ url: string;
20
+ method: string;
21
+ body: string;
22
+ detail?: string;
23
+ });
24
+ /** True for statuses the API documents as transient and retry-able. */
25
+ get isRetryable(): boolean;
26
+ }
27
+ /** A transport-level failure (DNS, connection reset, timeout, ...). */
28
+ export declare class MudabNetworkError extends MudabError {
29
+ }
30
+ /** A client-side validation error (e.g. a bad --compartment) — no request made. */
31
+ export declare class MudabValidationError extends MudabError {
32
+ }
33
+ /** The response body could not be parsed as the expected JSON shape. */
34
+ export declare class MudabParseError extends MudabError {
35
+ }
36
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/client/errors.ts"],"names":[],"mappings":"AAGA,+DAA+D;AAC/D,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IAUD,uEAAuE;IACvE,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF;AAED,uEAAuE;AACvE,qBAAa,iBAAkB,SAAQ,UAAU;CAAG;AAEpD,mFAAmF;AACnF,qBAAa,oBAAqB,SAAQ,UAAU;CAAG;AAEvD,wEAAwE;AACxE,qBAAa,eAAgB,SAAQ,UAAU;CAAG"}
@@ -0,0 +1,43 @@
1
+ // Error types raised by the client. Kept free of any I/O so they are trivial to
2
+ // construct in tests and to `instanceof`-check by consumers.
3
+ /** Base class for every error originating from this client. */
4
+ export class MudabError extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = new.target.name;
8
+ }
9
+ }
10
+ /**
11
+ * The API responded with a non-2xx status code. `detail` holds a human-readable
12
+ * message extracted from the response body when one is present.
13
+ */
14
+ export class MudabApiError extends MudabError {
15
+ status;
16
+ detail;
17
+ url;
18
+ method;
19
+ body;
20
+ constructor(args) {
21
+ const detailPart = args.detail ? `: ${args.detail}` : "";
22
+ super(`HTTP ${args.status} for ${args.method} ${args.url}${detailPart}`);
23
+ this.status = args.status;
24
+ this.url = args.url;
25
+ this.method = args.method;
26
+ this.body = args.body;
27
+ this.detail = args.detail;
28
+ }
29
+ /** True for statuses the API documents as transient and retry-able. */
30
+ get isRetryable() {
31
+ return this.status === 429 || this.status === 503;
32
+ }
33
+ }
34
+ /** A transport-level failure (DNS, connection reset, timeout, ...). */
35
+ export class MudabNetworkError extends MudabError {
36
+ }
37
+ /** A client-side validation error (e.g. a bad --compartment) — no request made. */
38
+ export class MudabValidationError extends MudabError {
39
+ }
40
+ /** The response body could not be parsed as the expected JSON shape. */
41
+ export class MudabParseError extends MudabError {
42
+ }
43
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/client/errors.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6DAA6D;AAE7D,+DAA+D;AAC/D,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,UAAU;IAClC,MAAM,CAAS;IACf,MAAM,CAAqB;IAC3B,GAAG,CAAS;IACZ,MAAM,CAAS;IACf,IAAI,CAAS;IAEtB,YAAY,IAMX;QACC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,QAAQ,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,GAAG,UAAU,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED,uEAAuE;IACvE,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC;IACpD,CAAC;CACF;AAED,uEAAuE;AACvE,MAAM,OAAO,iBAAkB,SAAQ,UAAU;CAAG;AAEpD,mFAAmF;AACnF,MAAM,OAAO,oBAAqB,SAAQ,UAAU;CAAG;AAEvD,wEAAwE;AACxE,MAAM,OAAO,eAAgB,SAAQ,UAAU;CAAG"}
@@ -0,0 +1,26 @@
1
+ import http from "node:http";
2
+ export interface HttpRequest {
3
+ method: string;
4
+ /** Fully-qualified absolute URL. */
5
+ url: string;
6
+ headers?: Record<string, string>;
7
+ /** Optional request body (already serialised). */
8
+ body?: string | Buffer;
9
+ /** Per-request timeout in milliseconds. */
10
+ timeoutMs?: number;
11
+ /** Hard cap on the response body size in bytes; the request aborts if exceeded. */
12
+ maxResponseBytes?: number;
13
+ }
14
+ export interface HttpResponse {
15
+ status: number;
16
+ headers: http.IncomingHttpHeaders;
17
+ body: Buffer;
18
+ }
19
+ export type Transport = (request: HttpRequest) => Promise<HttpResponse>;
20
+ /**
21
+ * Default transport. Resolves with the raw response (including non-2xx) — status
22
+ * interpretation is the client's job. Rejects only on transport-level failures
23
+ * (connection errors, timeouts, malformed URLs).
24
+ */
25
+ export declare const nodeHttpTransport: Transport;
26
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"AAQA,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAwE5B,CAAC"}