@maschinenlesbar.org/regionalatlas-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.
- package/CONTRIBUTING.md +25 -0
- package/LICENSE +661 -0
- package/LICENSING.md +47 -0
- package/README.md +85 -0
- package/dist/src/cli/commands/regions.d.ts +4 -0
- package/dist/src/cli/commands/regions.d.ts.map +1 -0
- package/dist/src/cli/commands/regions.js +66 -0
- package/dist/src/cli/commands/regions.js.map +1 -0
- package/dist/src/cli/index.d.ts +3 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +11 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/io.d.ts +12 -0
- package/dist/src/cli/io.d.ts.map +1 -0
- package/dist/src/cli/io.js +7 -0
- package/dist/src/cli/io.js.map +1 -0
- package/dist/src/cli/program.d.ts +7 -0
- package/dist/src/cli/program.d.ts.map +1 -0
- package/dist/src/cli/program.js +54 -0
- package/dist/src/cli/program.js.map +1 -0
- package/dist/src/cli/run.d.ts +3 -0
- package/dist/src/cli/run.d.ts.map +1 -0
- package/dist/src/cli/run.js +85 -0
- package/dist/src/cli/run.js.map +1 -0
- package/dist/src/cli/shared.d.ts +73 -0
- package/dist/src/cli/shared.d.ts.map +1 -0
- package/dist/src/cli/shared.js +154 -0
- package/dist/src/cli/shared.js.map +1 -0
- package/dist/src/client/catalog.d.ts +32 -0
- package/dist/src/client/catalog.d.ts.map +1 -0
- package/dist/src/client/catalog.js +127 -0
- package/dist/src/client/catalog.js.map +1 -0
- package/dist/src/client/client.d.ts +56 -0
- package/dist/src/client/client.d.ts.map +1 -0
- package/dist/src/client/client.js +195 -0
- package/dist/src/client/client.js.map +1 -0
- package/dist/src/client/engine.d.ts +77 -0
- package/dist/src/client/engine.d.ts.map +1 -0
- package/dist/src/client/engine.js +167 -0
- package/dist/src/client/engine.js.map +1 -0
- package/dist/src/client/errors.d.ts +51 -0
- package/dist/src/client/errors.d.ts.map +1 -0
- package/dist/src/client/errors.js +63 -0
- package/dist/src/client/errors.js.map +1 -0
- package/dist/src/client/http.d.ts +26 -0
- package/dist/src/client/http.d.ts.map +1 -0
- package/dist/src/client/http.js +80 -0
- package/dist/src/client/http.js.map +1 -0
- package/dist/src/client/index.d.ts +15 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +11 -0
- package/dist/src/client/index.js.map +1 -0
- package/dist/src/client/levels.d.ts +16 -0
- package/dist/src/client/levels.d.ts.map +1 -0
- package/dist/src/client/levels.js +59 -0
- package/dist/src/client/levels.js.map +1 -0
- package/dist/src/client/query.d.ts +9 -0
- package/dist/src/client/query.d.ts.map +1 -0
- package/dist/src/client/query.js +33 -0
- package/dist/src/client/query.js.map +1 -0
- package/dist/src/client/sql.d.ts +12 -0
- package/dist/src/client/sql.d.ts.map +1 -0
- package/dist/src/client/sql.js +64 -0
- package/dist/src/client/sql.js.map +1 -0
- package/dist/src/client/types.d.ts +137 -0
- package/dist/src/client/types.d.ts.map +1 -0
- package/dist/src/client/types.js +5 -0
- package/dist/src/client/types.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Shared helpers used across CLI command groups: option parsers, the global
|
|
2
|
+
// option resolver, and JSON rendering.
|
|
3
|
+
import { InvalidArgumentError } from "commander";
|
|
4
|
+
import { findLevel, GEO_LEVELS, LEVEL_ALIASES } from "../client/levels.js";
|
|
5
|
+
/**
|
|
6
|
+
* commander value-parser: a plain base-10 non-negative integer.
|
|
7
|
+
*
|
|
8
|
+
* Uses a strict regex rather than `Number()` coercion, which would otherwise
|
|
9
|
+
* accept empty/whitespace strings (`Number("") === 0`), hex/binary/scientific
|
|
10
|
+
* literals, signs, padding and decimals.
|
|
11
|
+
*/
|
|
12
|
+
export function parseIntArg(value) {
|
|
13
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
14
|
+
throw new InvalidArgumentError("Expected a non-negative integer.");
|
|
15
|
+
}
|
|
16
|
+
const n = Number(value);
|
|
17
|
+
if (!Number.isSafeInteger(n)) {
|
|
18
|
+
throw new InvalidArgumentError("Expected a non-negative integer.");
|
|
19
|
+
}
|
|
20
|
+
return n;
|
|
21
|
+
}
|
|
22
|
+
/** commander value-parser: a 4-digit year (integer 1000..9999). */
|
|
23
|
+
export function parseYear(value) {
|
|
24
|
+
if (!/^[0-9]{4}$/.test(value)) {
|
|
25
|
+
throw new InvalidArgumentError("Expected a 4-digit year (e.g. 2020).");
|
|
26
|
+
}
|
|
27
|
+
return Number(value);
|
|
28
|
+
}
|
|
29
|
+
/** commander value-parser: a non-empty (after trimming) string. */
|
|
30
|
+
export function parseNonEmpty(value) {
|
|
31
|
+
if (value.trim() === "") {
|
|
32
|
+
throw new InvalidArgumentError("Expected a non-empty value.");
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* commander value-parser for `--level`: resolves a friendly name/alias to the
|
|
38
|
+
* canonical level name, rejecting an unknown level at parse time (exit 2) with a
|
|
39
|
+
* clear message. The client re-resolves it (defence in depth) and only the fixed
|
|
40
|
+
* integer `typ` ever enters SQL.
|
|
41
|
+
*/
|
|
42
|
+
export function parseLevel(value) {
|
|
43
|
+
const level = findLevel(value);
|
|
44
|
+
if (level === undefined) {
|
|
45
|
+
const names = GEO_LEVELS.map((l) => l.name).join(", ");
|
|
46
|
+
throw new InvalidArgumentError(`Unknown geo level. Use one of: ${names} (aliases: ${LEVEL_ALIASES.join(", ")}).`);
|
|
47
|
+
}
|
|
48
|
+
return level.name;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* commander value-parser for a comma-separated field list. Splits on commas,
|
|
52
|
+
* trims, and drops empty entries. Field names are validated/projected client-side
|
|
53
|
+
* later, so this only produces a clean array.
|
|
54
|
+
*/
|
|
55
|
+
export function parseFieldList(value) {
|
|
56
|
+
const fields = value
|
|
57
|
+
.split(",")
|
|
58
|
+
.map((f) => f.trim())
|
|
59
|
+
.filter((f) => f !== "");
|
|
60
|
+
if (fields.length === 0) {
|
|
61
|
+
throw new InvalidArgumentError("Expected a comma-separated list of field names.");
|
|
62
|
+
}
|
|
63
|
+
return fields;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* commander value-parser for `--base-url` / `--catalog-url`: a non-empty,
|
|
67
|
+
* well-formed URL whose scheme is `http:` or `https:`. Validating here (parse time)
|
|
68
|
+
* rejects a bad scheme (`file:`, `ftp:`, ...) as a usage error (exit 2) with a clear
|
|
69
|
+
* message, rather than letting it reach the transport and surface as a network error
|
|
70
|
+
* (exit 6). The transport re-checks the scheme as defence in depth.
|
|
71
|
+
*/
|
|
72
|
+
export function parseHttpUrl(value) {
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (trimmed === "") {
|
|
75
|
+
throw new InvalidArgumentError("Expected a non-empty value.");
|
|
76
|
+
}
|
|
77
|
+
let url;
|
|
78
|
+
try {
|
|
79
|
+
url = new URL(trimmed);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new InvalidArgumentError("Expected a valid URL (e.g. https://host/path).");
|
|
83
|
+
}
|
|
84
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
85
|
+
throw new InvalidArgumentError("Only http: and https: URLs are supported.");
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
/** Build a commander value-parser for an integer constrained to [min, max]. */
|
|
90
|
+
export function parseBoundedInt(min, max) {
|
|
91
|
+
return (value) => {
|
|
92
|
+
const n = parseIntArg(value);
|
|
93
|
+
if (n < min)
|
|
94
|
+
throw new InvalidArgumentError(`Must be >= ${min}.`);
|
|
95
|
+
if (n > max)
|
|
96
|
+
throw new InvalidArgumentError(`Must be <= ${max}.`);
|
|
97
|
+
return n;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* commander value-parser for a value that ends up in an HTTP header (User-Agent).
|
|
102
|
+
* Rejects control characters — a CR/LF (or other C0/DEL byte) would otherwise reach
|
|
103
|
+
* Node's HTTP layer and throw an opaque `ERR_INVALID_CHAR`. Tab (0x09) is allowed;
|
|
104
|
+
* checked by char code so the source stays free of control bytes.
|
|
105
|
+
*/
|
|
106
|
+
export function parseHeaderValue(value) {
|
|
107
|
+
for (let i = 0; i < value.length; i++) {
|
|
108
|
+
const c = value.charCodeAt(i);
|
|
109
|
+
if ((c < 0x20 && c !== 0x09) || c === 0x7f) {
|
|
110
|
+
throw new InvalidArgumentError("Value contains control characters.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
/** Translate resolved global CLI options into client options. */
|
|
116
|
+
export function toEngineOptions(global) {
|
|
117
|
+
const options = {};
|
|
118
|
+
if (global.baseUrl !== undefined)
|
|
119
|
+
options.baseUrl = global.baseUrl;
|
|
120
|
+
if (global.catalogUrl !== undefined)
|
|
121
|
+
options.catalogUrl = global.catalogUrl;
|
|
122
|
+
if (global.timeout !== undefined)
|
|
123
|
+
options.timeoutMs = global.timeout;
|
|
124
|
+
if (global.userAgent !== undefined)
|
|
125
|
+
options.userAgent = global.userAgent;
|
|
126
|
+
if (global.maxRetries !== undefined)
|
|
127
|
+
options.maxRetries = global.maxRetries;
|
|
128
|
+
if (global.maxResponseBytes !== undefined)
|
|
129
|
+
options.maxResponseBytes = global.maxResponseBytes;
|
|
130
|
+
return options;
|
|
131
|
+
}
|
|
132
|
+
/** Render a JSON value to stdout, pretty by default, compact with --compact. */
|
|
133
|
+
export function renderJson(deps, global, value) {
|
|
134
|
+
const text = global.compact ? JSON.stringify(value) : JSON.stringify(value, null, 2);
|
|
135
|
+
deps.io.out(text);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Wrap an async command action with consistent global-option resolution and
|
|
139
|
+
* client construction. The callback receives a context (client + resolved global
|
|
140
|
+
* options + this command's options) and the command's positional arguments.
|
|
141
|
+
*
|
|
142
|
+
* Commander invokes actions as (arg1, ..., argN, options, command); we slice off
|
|
143
|
+
* the trailing options object and command instance to recover the positionals.
|
|
144
|
+
*/
|
|
145
|
+
export function action(deps, fn) {
|
|
146
|
+
return async (...args) => {
|
|
147
|
+
const command = args[args.length - 1];
|
|
148
|
+
const positionals = args.slice(0, Math.max(0, args.length - 2));
|
|
149
|
+
const global = command.optsWithGlobals();
|
|
150
|
+
const client = deps.createClient(toEngineOptions(global));
|
|
151
|
+
await fn({ client, global, opts: command.opts() }, positionals);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# 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,32 @@
|
|
|
1
|
+
import type { Indicator, Theme } from "./types.js";
|
|
2
|
+
/** Derive the SQL table name from a catalogue code: lowercase, `-` → `_`. */
|
|
3
|
+
export declare function tableForCode(code: string): string;
|
|
4
|
+
/** Parse the raw services.json array into a flat list of indicators. */
|
|
5
|
+
export declare function parseIndicators(raw: unknown): Indicator[];
|
|
6
|
+
/** Parse the raw services.json into a list of themes with their indicator counts. */
|
|
7
|
+
export declare function parseThemes(raw: unknown): Theme[];
|
|
8
|
+
/** Filters for listing indicators. */
|
|
9
|
+
export interface IndicatorFilter {
|
|
10
|
+
/** Case-insensitive substring on the theme title. */
|
|
11
|
+
theme?: string;
|
|
12
|
+
/** Membership: the indicator must offer this year. */
|
|
13
|
+
year?: string | number;
|
|
14
|
+
/** Case-insensitive substring over code + short + long titles. */
|
|
15
|
+
search?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Apply the (optional) filters to a flat indicator list. */
|
|
18
|
+
export declare function filterIndicators(indicators: Indicator[], filter?: IndicatorFilter): Indicator[];
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a user-supplied indicator string against the catalogue allowlist. Accepts
|
|
21
|
+
* either the code form (`AI002-1-5`, case-insensitive) or the table form
|
|
22
|
+
* (`ai002_1_5`). Throws a typed validation error if not found — BEFORE any SQL is
|
|
23
|
+
* built. The returned indicator's `table` is the only value interpolated into SQL.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveIndicator(indicators: Indicator[], input: string): Indicator;
|
|
26
|
+
/**
|
|
27
|
+
* Validate and resolve the year for an indicator. When `year` is undefined, the
|
|
28
|
+
* latest available year is used. A provided year must be an integer AND present in
|
|
29
|
+
* the indicator's catalogue years. Only the validated integer enters SQL.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveYear(indicator: Indicator, year?: number): number;
|
|
32
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/client/catalog.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,SAAS,EAIT,KAAK,EACN,MAAM,YAAY,CAAC;AAGpB,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAWD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,EAAE,CA+BzD;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,EAAE,CAajD;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,eAAoB,GAAG,SAAS,EAAE,CAcnG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAgBlF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBvE"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The indicator catalogue: parse the raw services.json into flat `Theme`/
|
|
2
|
+
// `Indicator` lists, filter them, and — crucially — resolve a user-supplied
|
|
3
|
+
// indicator string against the catalogue allowlist.
|
|
4
|
+
//
|
|
5
|
+
// The catalogue is the security boundary for the raw SQL query: the only table
|
|
6
|
+
// name ever interpolated into SQL is the `table` of a *matched* catalogue entry,
|
|
7
|
+
// never raw user text.
|
|
8
|
+
import { RegionalatlasParseError, RegionalatlasValidationError } from "./errors.js";
|
|
9
|
+
/** Derive the SQL table name from a catalogue code: lowercase, `-` → `_`. */
|
|
10
|
+
export function tableForCode(code) {
|
|
11
|
+
return code.toLowerCase().replace(/-/g, "_");
|
|
12
|
+
}
|
|
13
|
+
/** Normalise a user indicator string to compare against a code or a table form. */
|
|
14
|
+
function normalizeIndicatorKey(input) {
|
|
15
|
+
return input.trim().toLowerCase().replace(/-/g, "_");
|
|
16
|
+
}
|
|
17
|
+
function asString(value) {
|
|
18
|
+
return typeof value === "string" ? value : "";
|
|
19
|
+
}
|
|
20
|
+
/** Parse the raw services.json array into a flat list of indicators. */
|
|
21
|
+
export function parseIndicators(raw) {
|
|
22
|
+
if (!Array.isArray(raw)) {
|
|
23
|
+
throw new RegionalatlasParseError("Unexpected catalogue shape: expected a JSON array of themes from services.json.");
|
|
24
|
+
}
|
|
25
|
+
const catalog = raw;
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const theme of catalog) {
|
|
28
|
+
if (theme === null || typeof theme !== "object")
|
|
29
|
+
continue;
|
|
30
|
+
const t = theme;
|
|
31
|
+
const themeTitle = asString(t.title);
|
|
32
|
+
const children = Array.isArray(t.children) ? t.children : [];
|
|
33
|
+
for (const child of children) {
|
|
34
|
+
if (child === null || typeof child !== "object")
|
|
35
|
+
continue;
|
|
36
|
+
const c = child;
|
|
37
|
+
const code = asString(c.code);
|
|
38
|
+
if (code === "")
|
|
39
|
+
continue;
|
|
40
|
+
const years = c.years && typeof c.years === "object" ? Object.keys(c.years).sort() : [];
|
|
41
|
+
out.push({
|
|
42
|
+
code,
|
|
43
|
+
table: tableForCode(code),
|
|
44
|
+
theme: themeTitle,
|
|
45
|
+
titleShort: asString(c.title_short),
|
|
46
|
+
titleLong: asString(c.title_long),
|
|
47
|
+
years,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Parse the raw services.json into a list of themes with their indicator counts. */
|
|
54
|
+
export function parseThemes(raw) {
|
|
55
|
+
if (!Array.isArray(raw)) {
|
|
56
|
+
throw new RegionalatlasParseError("Unexpected catalogue shape: expected a JSON array of themes from services.json.");
|
|
57
|
+
}
|
|
58
|
+
const catalog = raw;
|
|
59
|
+
return catalog
|
|
60
|
+
.filter((t) => t !== null && typeof t === "object")
|
|
61
|
+
.map((t) => ({
|
|
62
|
+
title: asString(t.title),
|
|
63
|
+
indicatorCount: Array.isArray(t.children) ? t.children.length : 0,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/** Apply the (optional) filters to a flat indicator list. */
|
|
67
|
+
export function filterIndicators(indicators, filter = {}) {
|
|
68
|
+
const theme = filter.theme?.trim().toLowerCase();
|
|
69
|
+
const search = filter.search?.trim().toLowerCase();
|
|
70
|
+
const year = filter.year !== undefined ? String(filter.year) : undefined;
|
|
71
|
+
return indicators.filter((ind) => {
|
|
72
|
+
if (theme && !ind.theme.toLowerCase().includes(theme))
|
|
73
|
+
return false;
|
|
74
|
+
if (year && !ind.years.includes(year))
|
|
75
|
+
return false;
|
|
76
|
+
if (search) {
|
|
77
|
+
const hay = `${ind.code} ${ind.titleShort} ${ind.titleLong}`.toLowerCase();
|
|
78
|
+
if (!hay.includes(search))
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a user-supplied indicator string against the catalogue allowlist. Accepts
|
|
86
|
+
* either the code form (`AI002-1-5`, case-insensitive) or the table form
|
|
87
|
+
* (`ai002_1_5`). Throws a typed validation error if not found — BEFORE any SQL is
|
|
88
|
+
* built. The returned indicator's `table` is the only value interpolated into SQL.
|
|
89
|
+
*/
|
|
90
|
+
export function resolveIndicator(indicators, input) {
|
|
91
|
+
const trimmed = input.trim();
|
|
92
|
+
if (trimmed === "") {
|
|
93
|
+
throw new RegionalatlasValidationError("An indicator code is required (e.g. AI002-1-5).");
|
|
94
|
+
}
|
|
95
|
+
const key = normalizeIndicatorKey(trimmed);
|
|
96
|
+
// A code's normalized form equals its table form, so one key matches both the
|
|
97
|
+
// code (case-insensitive, hyphen or underscore) and the table name.
|
|
98
|
+
const match = indicators.find((ind) => ind.table === key);
|
|
99
|
+
if (match === undefined) {
|
|
100
|
+
throw new RegionalatlasValidationError(`Unknown indicator "${input}". It is not in the catalogue — list indicators with ` +
|
|
101
|
+
`\`regionalatlas indicators\` (accepts the code form AI002-1-5 or the table form ai002_1_5).`);
|
|
102
|
+
}
|
|
103
|
+
return match;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate and resolve the year for an indicator. When `year` is undefined, the
|
|
107
|
+
* latest available year is used. A provided year must be an integer AND present in
|
|
108
|
+
* the indicator's catalogue years. Only the validated integer enters SQL.
|
|
109
|
+
*/
|
|
110
|
+
export function resolveYear(indicator, year) {
|
|
111
|
+
if (indicator.years.length === 0) {
|
|
112
|
+
throw new RegionalatlasValidationError(`Indicator "${indicator.code}" has no years listed in the catalogue.`);
|
|
113
|
+
}
|
|
114
|
+
if (year === undefined) {
|
|
115
|
+
// Latest available year (years are 4-digit strings; compare numerically).
|
|
116
|
+
return Math.max(...indicator.years.map((y) => Number(y)));
|
|
117
|
+
}
|
|
118
|
+
if (!Number.isInteger(year)) {
|
|
119
|
+
throw new RegionalatlasValidationError(`Year must be an integer, got "${year}".`);
|
|
120
|
+
}
|
|
121
|
+
if (!indicator.years.includes(String(year))) {
|
|
122
|
+
throw new RegionalatlasValidationError(`Year ${year} is not available for indicator "${indicator.code}". ` +
|
|
123
|
+
`Available: ${indicator.years.join(", ")}.`);
|
|
124
|
+
}
|
|
125
|
+
return year;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/client/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type EngineOptions } from "./engine.js";
|
|
2
|
+
import { type IndicatorFilter } from "./catalog.js";
|
|
3
|
+
import type { Indicator, QueryOptions, RawFeatureAttributes, RegionRow, Theme } from "./types.js";
|
|
4
|
+
/** The default catalogue URL (statistikportal.de services.json). */
|
|
5
|
+
export declare const DEFAULT_CATALOG_URL = "https://regionalatlas.statistikportal.de/taskrunner/services.json";
|
|
6
|
+
/** Options for the client (engine options plus the catalogue URL — no auth). */
|
|
7
|
+
export interface RegionalatlasClientOptions extends EngineOptions {
|
|
8
|
+
/** Full URL of the indicator catalogue (services.json). Defaults to statistikportal.de. */
|
|
9
|
+
catalogUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The catalogue is fetched once per query/list call and cached for the lifetime of
|
|
13
|
+
* the client instance, so a `query` (which needs the catalogue to resolve the
|
|
14
|
+
* indicator) does not double-fetch.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RegionalatlasClient {
|
|
17
|
+
private readonly engine;
|
|
18
|
+
private readonly catalogUrl;
|
|
19
|
+
private indicatorsCache;
|
|
20
|
+
private rawCatalogCache;
|
|
21
|
+
constructor(options?: RegionalatlasClientOptions);
|
|
22
|
+
/** Fetch and cache the raw services.json (an array of themes). */
|
|
23
|
+
private rawCatalog;
|
|
24
|
+
/** Fetch and cache the flat indicator list. */
|
|
25
|
+
private allIndicators;
|
|
26
|
+
/** The 21 subject areas (Themenbereiche), each with its indicator count. */
|
|
27
|
+
themes(): Promise<Theme[]>;
|
|
28
|
+
/** The flat indicator list, optionally filtered by theme / year / search. */
|
|
29
|
+
indicators(filter?: IndicatorFilter): Promise<Indicator[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Run a data query. Resolves the indicator against the catalogue allowlist, maps
|
|
32
|
+
* the level to a `typ`, validates/defaults the year, then builds the SQL from
|
|
33
|
+
* ONLY those validated pieces. `region` and `fields` are applied client-side and
|
|
34
|
+
* never enter the request.
|
|
35
|
+
*/
|
|
36
|
+
query(opts: QueryOptions): Promise<RegionRow[]>;
|
|
37
|
+
/** GET the dynamicLayer data query, then throw on the ArcGIS `error` envelope. */
|
|
38
|
+
private getData;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse one raw ArcGIS feature into a `RegionRow`. Trims the leading-space padding
|
|
42
|
+
* of `gen2`, prefers `gen` for the name, and keeps only the indicator value fields
|
|
43
|
+
* (dropping the join columns and the `<field>v` precision-flag variants).
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseRow(attrs: RawFeatureAttributes, typ: 1 | 2 | 3 | 5, level: string, year: number): RegionRow;
|
|
46
|
+
/**
|
|
47
|
+
* Client-side region filter. A numeric input is matched as an exact `ags` (ignoring
|
|
48
|
+
* leading zeros on both sides); otherwise a case-insensitive substring of the name.
|
|
49
|
+
*/
|
|
50
|
+
export declare function filterByRegion(rows: RegionRow[], region: string): RegionRow[];
|
|
51
|
+
/**
|
|
52
|
+
* Client-side field projection: keep only the named value fields. Unknown field
|
|
53
|
+
* names are ignored (never sent upstream). Names are matched case-insensitively.
|
|
54
|
+
*/
|
|
55
|
+
export declare function projectFields(rows: RegionRow[], fields: string[]): RegionRow[];
|
|
56
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/client/client.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEpF,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EAEV,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,SAAS,EACT,KAAK,EACN,MAAM,YAAY,CAAC;AAEpB,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,sEACqC,CAAC;AAMtE,gFAAgF;AAChF,MAAM,WAAW,0BAA2B,SAAQ,aAAa;IAC/D,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,eAAe,CAAU;gBAErB,OAAO,GAAE,0BAA+B;IAKpD,kEAAkE;YACpD,UAAU;IAaxB,+CAA+C;YACjC,aAAa;IAO3B,4EAA4E;IACtE,MAAM,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IAIhC,6EAA6E;IACvE,UAAU,CAAC,MAAM,GAAE,eAAoB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAIpE;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA8BrD,kFAAkF;YACpE,OAAO;CA6BtB;AAkBD;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,oBAAoB,EAC3B,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,SAAS,CAkBX;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAS7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAU9E"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// RegionalatlasClient — a typed client over the Regionalatlas Deutschland of the
|
|
2
|
+
// Statistische Ämter des Bundes und der Länder.
|
|
3
|
+
//
|
|
4
|
+
// TWO upstream hosts (see DEVELOPING.md):
|
|
5
|
+
// (A) data — the ArcGIS MapServer `dynamicLayer/query` on `baseUrl`
|
|
6
|
+
// (default https://www.gis-idmz.nrw.de), which runs a raw SQL join.
|
|
7
|
+
// (B) catalogue — the static `services.json` on statistikportal.de, listing the
|
|
8
|
+
// 21 themes / 70 indicators (`catalogUrl`).
|
|
9
|
+
//
|
|
10
|
+
// The ArcGIS server answers HTTP 200 even for logical errors, carrying them in an
|
|
11
|
+
// `error` object — the client checks for it and throws.
|
|
12
|
+
//
|
|
13
|
+
// const c = new RegionalatlasClient();
|
|
14
|
+
// await c.themes(); // the 21 subject areas
|
|
15
|
+
// await c.indicators({ search: "bevölkerung" }); // matching indicators
|
|
16
|
+
// await c.query({ indicator: "AI002-1-5", level: "land", year: 2020 }); // 16 rows
|
|
17
|
+
import { RequestEngine, sanitizeServerText } from "./engine.js";
|
|
18
|
+
import { RegionalatlasApiError, RegionalatlasParseError } from "./errors.js";
|
|
19
|
+
import { filterIndicators, parseIndicators, parseThemes, resolveIndicator, resolveYear, } from "./catalog.js";
|
|
20
|
+
import { resolveLevel } from "./levels.js";
|
|
21
|
+
import { buildLayerParam } from "./sql.js";
|
|
22
|
+
/** The default catalogue URL (statistikportal.de services.json). */
|
|
23
|
+
export const DEFAULT_CATALOG_URL = "https://regionalatlas.statistikportal.de/taskrunner/services.json";
|
|
24
|
+
/** The ArcGIS MapServer path for the dynamicLayer data query (on `baseUrl`). */
|
|
25
|
+
const DATA_PATH = "/arcgis/rest/services/stba/regionalatlas/MapServer/dynamicLayer/query";
|
|
26
|
+
/**
|
|
27
|
+
* The catalogue is fetched once per query/list call and cached for the lifetime of
|
|
28
|
+
* the client instance, so a `query` (which needs the catalogue to resolve the
|
|
29
|
+
* indicator) does not double-fetch.
|
|
30
|
+
*/
|
|
31
|
+
export class RegionalatlasClient {
|
|
32
|
+
engine;
|
|
33
|
+
catalogUrl;
|
|
34
|
+
indicatorsCache;
|
|
35
|
+
rawCatalogCache;
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
this.engine = new RequestEngine(options);
|
|
38
|
+
this.catalogUrl = options.catalogUrl ?? DEFAULT_CATALOG_URL;
|
|
39
|
+
}
|
|
40
|
+
/** Fetch and cache the raw services.json (an array of themes). */
|
|
41
|
+
async rawCatalog() {
|
|
42
|
+
if (this.rawCatalogCache === undefined) {
|
|
43
|
+
const raw = await this.engine.getJsonAbsolute(this.catalogUrl);
|
|
44
|
+
if (raw === null || raw === undefined) {
|
|
45
|
+
throw new RegionalatlasParseError(`The catalogue at ${this.catalogUrl} returned an empty body.`);
|
|
46
|
+
}
|
|
47
|
+
this.rawCatalogCache = raw;
|
|
48
|
+
}
|
|
49
|
+
return this.rawCatalogCache;
|
|
50
|
+
}
|
|
51
|
+
/** Fetch and cache the flat indicator list. */
|
|
52
|
+
async allIndicators() {
|
|
53
|
+
if (this.indicatorsCache === undefined) {
|
|
54
|
+
this.indicatorsCache = parseIndicators(await this.rawCatalog());
|
|
55
|
+
}
|
|
56
|
+
return this.indicatorsCache;
|
|
57
|
+
}
|
|
58
|
+
/** The 21 subject areas (Themenbereiche), each with its indicator count. */
|
|
59
|
+
async themes() {
|
|
60
|
+
return parseThemes(await this.rawCatalog());
|
|
61
|
+
}
|
|
62
|
+
/** The flat indicator list, optionally filtered by theme / year / search. */
|
|
63
|
+
async indicators(filter = {}) {
|
|
64
|
+
return filterIndicators(await this.allIndicators(), filter);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run a data query. Resolves the indicator against the catalogue allowlist, maps
|
|
68
|
+
* the level to a `typ`, validates/defaults the year, then builds the SQL from
|
|
69
|
+
* ONLY those validated pieces. `region` and `fields` are applied client-side and
|
|
70
|
+
* never enter the request.
|
|
71
|
+
*/
|
|
72
|
+
async query(opts) {
|
|
73
|
+
// 1. Resolve the indicator against the catalogue allowlist (throws if unknown).
|
|
74
|
+
const indicators = await this.allIndicators();
|
|
75
|
+
const indicator = resolveIndicator(indicators, opts.indicator);
|
|
76
|
+
// 2. Map level → typ (throws if unknown).
|
|
77
|
+
const level = resolveLevel(opts.level);
|
|
78
|
+
// 3. Validate/default the year (throws if not an integer in the indicator's years).
|
|
79
|
+
const year = resolveYear(indicator, opts.year);
|
|
80
|
+
// 4. Build the SQL from validated pieces only.
|
|
81
|
+
const layer = buildLayerParam(indicator.table, level.typ, year);
|
|
82
|
+
const res = await this.getData({
|
|
83
|
+
layer: JSON.stringify(layer),
|
|
84
|
+
f: "json",
|
|
85
|
+
outFields: "*",
|
|
86
|
+
returnGeometry: false,
|
|
87
|
+
where: "1=1",
|
|
88
|
+
spatialRel: "esriSpatialRelIntersects",
|
|
89
|
+
});
|
|
90
|
+
const rows = (res.features ?? []).map((f) => parseRow(f.attributes, level.typ, level.name, year));
|
|
91
|
+
// 5. Client-side region filter + field projection (no user text upstream).
|
|
92
|
+
const filtered = opts.region ? filterByRegion(rows, opts.region) : rows;
|
|
93
|
+
return opts.fields && opts.fields.length > 0 ? projectFields(filtered, opts.fields) : filtered;
|
|
94
|
+
}
|
|
95
|
+
/** GET the dynamicLayer data query, then throw on the ArcGIS `error` envelope. */
|
|
96
|
+
async getData(params) {
|
|
97
|
+
const res = await this.engine.getJson(DATA_PATH, params);
|
|
98
|
+
// The endpoint answers with a JSON envelope object. A null (empty/204 body) or
|
|
99
|
+
// non-object reply means the endpoint did not return the expected shape.
|
|
100
|
+
if (res === null || typeof res !== "object") {
|
|
101
|
+
throw new RegionalatlasParseError(`Expected a JSON object from the data query but received ${res === null ? "an empty body" : typeof res}.`);
|
|
102
|
+
}
|
|
103
|
+
// Logical errors arrive as HTTP 200 with a top-level `error` key — sniff for it.
|
|
104
|
+
const err = res.error;
|
|
105
|
+
if (err && typeof err === "object") {
|
|
106
|
+
// The ArcGIS `error` message/details come from the (attacker-controllable)
|
|
107
|
+
// response body and flow into an Error.message printed raw to stderr; strip
|
|
108
|
+
// control characters so a hostile endpoint cannot inject terminal escapes.
|
|
109
|
+
const detail = [err.message, ...(Array.isArray(err.details) ? err.details : [])]
|
|
110
|
+
.filter((s) => typeof s === "string" && s.length > 0)
|
|
111
|
+
.map(sanitizeServerText)
|
|
112
|
+
.join("; ");
|
|
113
|
+
throw new RegionalatlasApiError({
|
|
114
|
+
url: this.engine.buildUrl(DATA_PATH, params),
|
|
115
|
+
method: "GET",
|
|
116
|
+
body: JSON.stringify(res),
|
|
117
|
+
arcgisCode: typeof err.code === "number" ? err.code : undefined,
|
|
118
|
+
detail: detail || undefined,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return res;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// --------------------------------------------------------------------------
|
|
125
|
+
// Row parsing & client-side filtering (exported for tests)
|
|
126
|
+
// --------------------------------------------------------------------------
|
|
127
|
+
/** The non-value join columns present on every feature (excluded from `values`). */
|
|
128
|
+
const JOIN_FIELDS = new Set([
|
|
129
|
+
"id",
|
|
130
|
+
"typ",
|
|
131
|
+
"ags",
|
|
132
|
+
"jahr",
|
|
133
|
+
"gen",
|
|
134
|
+
"jahr2",
|
|
135
|
+
"ags2",
|
|
136
|
+
"gen2",
|
|
137
|
+
]);
|
|
138
|
+
/**
|
|
139
|
+
* Parse one raw ArcGIS feature into a `RegionRow`. Trims the leading-space padding
|
|
140
|
+
* of `gen2`, prefers `gen` for the name, and keeps only the indicator value fields
|
|
141
|
+
* (dropping the join columns and the `<field>v` precision-flag variants).
|
|
142
|
+
*/
|
|
143
|
+
export function parseRow(attrs, typ, level, year) {
|
|
144
|
+
const ags = String(attrs.ags ?? "").trim();
|
|
145
|
+
const rawName = typeof attrs.gen === "string" ? attrs.gen : attrs.gen2;
|
|
146
|
+
const name = typeof rawName === "string" ? rawName.trim() : "";
|
|
147
|
+
const values = {};
|
|
148
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
149
|
+
if (JOIN_FIELDS.has(key))
|
|
150
|
+
continue;
|
|
151
|
+
// Drop the precision-flag variants: `<field>v` when `<field>` is also present.
|
|
152
|
+
if (key.endsWith("v")) {
|
|
153
|
+
const base = key.slice(0, -1);
|
|
154
|
+
if (Object.prototype.hasOwnProperty.call(attrs, base))
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
values[key] = typeof value === "number" ? value : value === null ? null : Number(value);
|
|
158
|
+
if (Number.isNaN(values[key]))
|
|
159
|
+
values[key] = null;
|
|
160
|
+
}
|
|
161
|
+
return { ags, name, typ, level, year, values };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Client-side region filter. A numeric input is matched as an exact `ags` (ignoring
|
|
165
|
+
* leading zeros on both sides); otherwise a case-insensitive substring of the name.
|
|
166
|
+
*/
|
|
167
|
+
export function filterByRegion(rows, region) {
|
|
168
|
+
const trimmed = region.trim();
|
|
169
|
+
if (trimmed === "")
|
|
170
|
+
return rows;
|
|
171
|
+
if (/^\d+$/.test(trimmed)) {
|
|
172
|
+
const target = String(Number(trimmed)); // strip leading zeros
|
|
173
|
+
return rows.filter((r) => String(Number(r.ags.replace(/\D/g, "") || "0")) === target);
|
|
174
|
+
}
|
|
175
|
+
const needle = trimmed.toLowerCase();
|
|
176
|
+
return rows.filter((r) => r.name.toLowerCase().includes(needle));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Client-side field projection: keep only the named value fields. Unknown field
|
|
180
|
+
* names are ignored (never sent upstream). Names are matched case-insensitively.
|
|
181
|
+
*/
|
|
182
|
+
export function projectFields(rows, fields) {
|
|
183
|
+
const wanted = new Set(fields.map((f) => f.trim().toLowerCase()).filter((f) => f !== ""));
|
|
184
|
+
if (wanted.size === 0)
|
|
185
|
+
return rows;
|
|
186
|
+
return rows.map((r) => {
|
|
187
|
+
const values = {};
|
|
188
|
+
for (const [key, value] of Object.entries(r.values)) {
|
|
189
|
+
if (wanted.has(key.toLowerCase()))
|
|
190
|
+
values[key] = value;
|
|
191
|
+
}
|
|
192
|
+
return { ...r, values };
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/client/client.ts"],"names":[],"mappings":"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"}
|