@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.
Files changed (73) hide show
  1. package/CONTRIBUTING.md +25 -0
  2. package/LICENSE +661 -0
  3. package/LICENSING.md +47 -0
  4. package/README.md +85 -0
  5. package/dist/src/cli/commands/regions.d.ts +4 -0
  6. package/dist/src/cli/commands/regions.d.ts.map +1 -0
  7. package/dist/src/cli/commands/regions.js +66 -0
  8. package/dist/src/cli/commands/regions.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 +85 -0
  24. package/dist/src/cli/run.js.map +1 -0
  25. package/dist/src/cli/shared.d.ts +73 -0
  26. package/dist/src/cli/shared.d.ts.map +1 -0
  27. package/dist/src/cli/shared.js +154 -0
  28. package/dist/src/cli/shared.js.map +1 -0
  29. package/dist/src/client/catalog.d.ts +32 -0
  30. package/dist/src/client/catalog.d.ts.map +1 -0
  31. package/dist/src/client/catalog.js +127 -0
  32. package/dist/src/client/catalog.js.map +1 -0
  33. package/dist/src/client/client.d.ts +56 -0
  34. package/dist/src/client/client.d.ts.map +1 -0
  35. package/dist/src/client/client.js +195 -0
  36. package/dist/src/client/client.js.map +1 -0
  37. package/dist/src/client/engine.d.ts +77 -0
  38. package/dist/src/client/engine.d.ts.map +1 -0
  39. package/dist/src/client/engine.js +167 -0
  40. package/dist/src/client/engine.js.map +1 -0
  41. package/dist/src/client/errors.d.ts +51 -0
  42. package/dist/src/client/errors.d.ts.map +1 -0
  43. package/dist/src/client/errors.js +63 -0
  44. package/dist/src/client/errors.js.map +1 -0
  45. package/dist/src/client/http.d.ts +26 -0
  46. package/dist/src/client/http.d.ts.map +1 -0
  47. package/dist/src/client/http.js +80 -0
  48. package/dist/src/client/http.js.map +1 -0
  49. package/dist/src/client/index.d.ts +15 -0
  50. package/dist/src/client/index.d.ts.map +1 -0
  51. package/dist/src/client/index.js +11 -0
  52. package/dist/src/client/index.js.map +1 -0
  53. package/dist/src/client/levels.d.ts +16 -0
  54. package/dist/src/client/levels.d.ts.map +1 -0
  55. package/dist/src/client/levels.js +59 -0
  56. package/dist/src/client/levels.js.map +1 -0
  57. package/dist/src/client/query.d.ts +9 -0
  58. package/dist/src/client/query.d.ts.map +1 -0
  59. package/dist/src/client/query.js +33 -0
  60. package/dist/src/client/query.js.map +1 -0
  61. package/dist/src/client/sql.d.ts +12 -0
  62. package/dist/src/client/sql.d.ts.map +1 -0
  63. package/dist/src/client/sql.js +64 -0
  64. package/dist/src/client/sql.js.map +1 -0
  65. package/dist/src/client/types.d.ts +137 -0
  66. package/dist/src/client/types.d.ts.map +1 -0
  67. package/dist/src/client/types.js +5 -0
  68. package/dist/src/client/types.js.map +1 -0
  69. package/dist/src/index.d.ts +2 -0
  70. package/dist/src/index.d.ts.map +1 -0
  71. package/dist/src/index.js +3 -0
  72. package/dist/src/index.js.map +1 -0
  73. package/package.json +71 -0
@@ -0,0 +1,77 @@
1
+ import { type Transport } from "./http.js";
2
+ import { type QueryParams } from "./query.js";
3
+ /** The ArcGIS MapServer host that answers the dynamicLayer data queries. */
4
+ export declare const DEFAULT_BASE_URL = "https://www.gis-idmz.nrw.de";
5
+ export interface RawResponse {
6
+ data: Buffer;
7
+ contentType: string;
8
+ status: number;
9
+ }
10
+ export interface EngineOptions {
11
+ /** Base URL of the ArcGIS data host. Defaults to the gis-idmz.nrw.de MapServer host. */
12
+ baseUrl?: string;
13
+ /** Swappable transport. Defaults to the built-in node http/https transport. */
14
+ transport?: Transport;
15
+ /** Value of the User-Agent header. */
16
+ userAgent?: string;
17
+ /** Extra headers sent on every request. */
18
+ defaultHeaders?: Record<string, string>;
19
+ /** Per-request timeout in milliseconds (0 disables). */
20
+ timeoutMs?: number;
21
+ /** Number of automatic retries for transient (429/503) responses. */
22
+ maxRetries?: number;
23
+ /** Base backoff between retries in milliseconds (grows linearly). */
24
+ retryDelayMs?: number;
25
+ /**
26
+ * Hard cap on response body size in bytes (defends against memory exhaustion
27
+ * from a hostile/buggy endpoint). Defaults to 100 MiB; set to 0 for no limit.
28
+ */
29
+ maxResponseBytes?: number;
30
+ /** Injectable sleep, primarily for deterministic tests. */
31
+ sleep?: (ms: number) => Promise<void>;
32
+ }
33
+ /**
34
+ * Strip control characters out of a string that originates in an
35
+ * attacker-controlled response — the ArcGIS `error` detail and the non-JSON body
36
+ * snippet — before it flows into a `RegionalatlasApiError.message` that run.ts
37
+ * prints raw to stderr. `JSON.parse` decodes an escaped ESC (a backslash-u-001b
38
+ * sequence) in an error body into a real ESC byte, so without this a hostile or
39
+ * MITM'd endpoint could inject
40
+ * ANSI/OSC terminal escape sequences (screen clears, title changes, output
41
+ * spoofing) when the message reaches the user's terminal. The success path is
42
+ * already safe (`JSON.stringify` escapes these). Removes all C0 controls (except
43
+ * tab/newline), DEL, and the C1 range; implemented via char codes so this source
44
+ * file never contains a raw control byte.
45
+ */
46
+ export declare function sanitizeServerText(text: string): string;
47
+ export declare class RequestEngine {
48
+ private readonly baseUrl;
49
+ private readonly transport;
50
+ private readonly userAgent;
51
+ private readonly defaultHeaders;
52
+ private readonly timeoutMs;
53
+ private readonly maxRetries;
54
+ private readonly retryDelayMs;
55
+ private readonly maxResponseBytes;
56
+ private readonly sleep;
57
+ constructor(options?: EngineOptions);
58
+ /** Build a fully-qualified URL from a path (on the data host) and optional query. */
59
+ buildUrl(path: string, query?: QueryParams): string;
60
+ /** Build a fully-qualified URL from an absolute base URL and optional query. */
61
+ buildAbsoluteUrl(absoluteUrl: string, query?: QueryParams): string;
62
+ /**
63
+ * Perform a GET with Accept negotiation and transient-error retries. Redirects
64
+ * are NOT followed — the canonical host answers directly, so a 3xx surfaces as
65
+ * an error.
66
+ */
67
+ private requestUrl;
68
+ /** GET a path on the data host with query params. */
69
+ request(path: string, query?: QueryParams, accept?: string): Promise<RawResponse>;
70
+ /** GET a path on the data host and parse the JSON reply into `T`. */
71
+ getJson<T>(path: string, query?: QueryParams): Promise<T>;
72
+ /** GET a fully-qualified absolute URL (e.g. the catalogue host) and parse JSON into `T`. */
73
+ getJsonAbsolute<T>(absoluteUrl: string, query?: QueryParams): Promise<T>;
74
+ private decodeJson;
75
+ private toApiError;
76
+ }
77
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/client/engine.ts"],"names":[],"mappings":"AAWA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAGhE,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAG9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,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;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYvD;AAKD,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,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM;IAMnD,gFAAgF;IAChF,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM;IAMlE;;;;OAIG;YACW,UAAU;IAkCxB,qDAAqD;IAC/C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,MAAM,SAAqB,GAAG,OAAO,CAAC,WAAW,CAAC;IAInG,qEAAqE;IAC/D,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/D,4FAA4F;IACtF,eAAe,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAK9E,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,UAAU;CA0BnB"}
@@ -0,0 +1,167 @@
1
+ // The request engine: turns logical (path, query) calls into HTTP GET requests via
2
+ // a Transport, applies retry/backoff for transient statuses (429, 503), and decodes
3
+ // JSON responses. The Regionalatlas is backed by an ArcGIS MapServer — an
4
+ // unauthenticated GET API whose parameters travel in the query string.
5
+ //
6
+ // Two-host note: the *data* queries hit the ArcGIS MapServer (`baseUrl`, default
7
+ // the gis-idmz.nrw.de host), while the *indicator catalogue* is a static JSON file
8
+ // on the statistikportal.de host. The engine therefore also supports GETting a
9
+ // fully-qualified absolute URL (`requestAbsolute`) so the catalogue can be fetched
10
+ // without changing the data `baseUrl`.
11
+ import { nodeHttpTransport } from "./http.js";
12
+ import { buildQueryString } from "./query.js";
13
+ import { RegionalatlasApiError, RegionalatlasParseError } from "./errors.js";
14
+ /** The ArcGIS MapServer host that answers the dynamicLayer data queries. */
15
+ export const DEFAULT_BASE_URL = "https://www.gis-idmz.nrw.de";
16
+ const DEFAULT_USER_AGENT = "regionalatlas-cli";
17
+ const DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024 * 1024;
18
+ /**
19
+ * Strip control characters out of a string that originates in an
20
+ * attacker-controlled response — the ArcGIS `error` detail and the non-JSON body
21
+ * snippet — before it flows into a `RegionalatlasApiError.message` that run.ts
22
+ * prints raw to stderr. `JSON.parse` decodes an escaped ESC (a backslash-u-001b
23
+ * sequence) in an error body into a real ESC byte, so without this a hostile or
24
+ * MITM'd endpoint could inject
25
+ * ANSI/OSC terminal escape sequences (screen clears, title changes, output
26
+ * spoofing) when the message reaches the user's terminal. The success path is
27
+ * already safe (`JSON.stringify` escapes these). Removes all C0 controls (except
28
+ * tab/newline), DEL, and the C1 range; implemented via char codes so this source
29
+ * file never contains a raw control byte.
30
+ */
31
+ export function sanitizeServerText(text) {
32
+ let out = "";
33
+ for (const ch of text) {
34
+ const n = ch.codePointAt(0) ?? 0;
35
+ if (n === 0x09 || n === 0x0a) {
36
+ out += ch;
37
+ continue;
38
+ }
39
+ if (n <= 8 || (n >= 0x0b && n <= 0x1f) || (n >= 0x7f && n <= 0x9f))
40
+ continue;
41
+ out += ch;
42
+ }
43
+ return out;
44
+ }
45
+ const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
46
+ export class RequestEngine {
47
+ baseUrl;
48
+ transport;
49
+ userAgent;
50
+ defaultHeaders;
51
+ timeoutMs;
52
+ maxRetries;
53
+ retryDelayMs;
54
+ maxResponseBytes;
55
+ sleep;
56
+ constructor(options = {}) {
57
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
58
+ this.transport = options.transport ?? nodeHttpTransport;
59
+ this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
60
+ this.defaultHeaders = options.defaultHeaders ?? {};
61
+ this.timeoutMs = options.timeoutMs ?? 30_000;
62
+ this.maxRetries = options.maxRetries ?? 2;
63
+ this.retryDelayMs = options.retryDelayMs ?? 200;
64
+ this.maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
65
+ this.sleep = options.sleep ?? realSleep;
66
+ }
67
+ /** Build a fully-qualified URL from a path (on the data host) and optional query. */
68
+ buildUrl(path, query) {
69
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
70
+ const qs = query ? buildQueryString(query) : "";
71
+ return `${this.baseUrl}${normalizedPath}${qs ? `?${qs}` : ""}`;
72
+ }
73
+ /** Build a fully-qualified URL from an absolute base URL and optional query. */
74
+ buildAbsoluteUrl(absoluteUrl, query) {
75
+ const qs = query ? buildQueryString(query) : "";
76
+ if (!qs)
77
+ return absoluteUrl;
78
+ return absoluteUrl.includes("?") ? `${absoluteUrl}&${qs}` : `${absoluteUrl}?${qs}`;
79
+ }
80
+ /**
81
+ * Perform a GET with Accept negotiation and transient-error retries. Redirects
82
+ * are NOT followed — the canonical host answers directly, so a 3xx surfaces as
83
+ * an error.
84
+ */
85
+ async requestUrl(url, accept) {
86
+ const headers = {
87
+ ...this.defaultHeaders,
88
+ Accept: accept,
89
+ "User-Agent": this.userAgent,
90
+ };
91
+ let attempt = 0;
92
+ for (;;) {
93
+ const response = await this.transport({
94
+ method: "GET",
95
+ url,
96
+ headers,
97
+ timeoutMs: this.timeoutMs,
98
+ ...(this.maxResponseBytes > 0 ? { maxResponseBytes: this.maxResponseBytes } : {}),
99
+ });
100
+ const status = response.status;
101
+ const retryable = status === 429 || status === 503;
102
+ if (retryable && attempt < this.maxRetries) {
103
+ attempt += 1;
104
+ await this.sleep(this.retryDelayMs * attempt);
105
+ continue;
106
+ }
107
+ const contentType = String(response.headers["content-type"] ?? "");
108
+ if (status < 200 || status >= 300) {
109
+ throw this.toApiError(url, status, response.body);
110
+ }
111
+ return { data: response.body, contentType, status };
112
+ }
113
+ }
114
+ /** GET a path on the data host with query params. */
115
+ async request(path, query, accept = "application/json") {
116
+ return this.requestUrl(this.buildUrl(path, query), accept);
117
+ }
118
+ /** GET a path on the data host and parse the JSON reply into `T`. */
119
+ async getJson(path, query) {
120
+ return this.decodeJson(await this.request(path, query), path);
121
+ }
122
+ /** GET a fully-qualified absolute URL (e.g. the catalogue host) and parse JSON into `T`. */
123
+ async getJsonAbsolute(absoluteUrl, query) {
124
+ const url = this.buildAbsoluteUrl(absoluteUrl, query);
125
+ return this.decodeJson(await this.requestUrl(url, "application/json"), url);
126
+ }
127
+ decodeJson(res, source) {
128
+ const text = res.data.toString("utf8");
129
+ if (res.status === 204 || text.trim().length === 0) {
130
+ return null;
131
+ }
132
+ try {
133
+ return JSON.parse(text);
134
+ }
135
+ catch (cause) {
136
+ throw new RegionalatlasParseError(`Failed to parse JSON response from ${source}`, { cause });
137
+ }
138
+ }
139
+ toApiError(url, status, body) {
140
+ const text = body.toString("utf8");
141
+ let detail;
142
+ try {
143
+ const parsed = JSON.parse(text);
144
+ if (parsed?.error && typeof parsed.error.message === "string")
145
+ detail = parsed.error.message;
146
+ else if (typeof parsed?.message === "string")
147
+ detail = parsed.message;
148
+ else if (typeof parsed?.detail === "string")
149
+ detail = parsed.detail;
150
+ }
151
+ catch {
152
+ // Not JSON (e.g. an HTML error page). Surface a short, whitespace-collapsed
153
+ // snippet of a textual body; skip HTML pages (start with "<").
154
+ const snippet = text.trim().replace(/\s+/g, " ");
155
+ if (snippet.length > 0 && !snippet.startsWith("<")) {
156
+ detail = snippet.length > 200 ? `${snippet.slice(0, 200)}…` : snippet;
157
+ }
158
+ }
159
+ // `detail` came from the attacker-controlled response body; strip control
160
+ // characters so a hostile endpoint cannot drive terminal escape sequences
161
+ // into stderr via the error message.
162
+ if (detail !== undefined)
163
+ detail = sanitizeServerText(detail);
164
+ return new RegionalatlasApiError({ status, url, method: "GET", body: text, detail });
165
+ }
166
+ }
167
+ //# 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,51 @@
1
+ /** Base class for every error originating from this client. */
2
+ export declare class RegionalatlasError extends Error {
3
+ constructor(message: string, options?: {
4
+ cause?: unknown;
5
+ });
6
+ }
7
+ /**
8
+ * The API signalled a failure. The Regionalatlas ArcGIS MapServer is unusual: it
9
+ * answers HTTP 200 even for logical errors, carrying the message in an `error`
10
+ * object (`{code, message, details}`) — e.g. a malformed query or an invalid
11
+ * parameter. This error models both worlds:
12
+ * - `status` is set for a genuine transport/HTTP failure (non-2xx);
13
+ * - `arcgisCode` is set for a logical ArcGIS error (from `error.code`).
14
+ * `detail` holds the human-readable message in either case.
15
+ */
16
+ export declare class RegionalatlasApiError extends RegionalatlasError {
17
+ readonly status: number | undefined;
18
+ readonly arcgisCode: number | undefined;
19
+ readonly detail: string | undefined;
20
+ readonly url: string;
21
+ readonly method: string;
22
+ readonly body: string;
23
+ constructor(args: {
24
+ url: string;
25
+ method: string;
26
+ body: string;
27
+ status?: number;
28
+ arcgisCode?: number;
29
+ detail?: string;
30
+ });
31
+ /** True for HTTP statuses the API treats as transient and retry-able. */
32
+ get isRetryable(): boolean;
33
+ /** True for a transport-level HTTP 404. */
34
+ get isNotFound(): boolean;
35
+ }
36
+ /** A transport-level failure (DNS, connection reset, timeout, ...). */
37
+ export declare class RegionalatlasNetworkError extends RegionalatlasError {
38
+ }
39
+ /**
40
+ * A client-side validation / not-found error made before any request — e.g. an
41
+ * unknown indicator code, an unknown geo level, or a year outside an indicator's
42
+ * available range. Crucially, the indicator/level/year values that enter the raw
43
+ * SQL query are all validated against the catalogue here, so a rejected value
44
+ * never reaches the transport.
45
+ */
46
+ export declare class RegionalatlasValidationError extends RegionalatlasError {
47
+ }
48
+ /** The response body could not be parsed as the expected JSON shape. */
49
+ export declare class RegionalatlasParseError extends RegionalatlasError {
50
+ }
51
+ //# 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,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,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,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IAeD,yEAAyE;IACzE,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,2CAA2C;IAC3C,IAAI,UAAU,IAAI,OAAO,CAExB;CACF;AAED,uEAAuE;AACvE,qBAAa,yBAA0B,SAAQ,kBAAkB;CAAG;AAEpE;;;;;;GAMG;AACH,qBAAa,4BAA6B,SAAQ,kBAAkB;CAAG;AAEvE,wEAAwE;AACxE,qBAAa,uBAAwB,SAAQ,kBAAkB;CAAG"}
@@ -0,0 +1,63 @@
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 RegionalatlasError extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = new.target.name;
8
+ }
9
+ }
10
+ /**
11
+ * The API signalled a failure. The Regionalatlas ArcGIS MapServer is unusual: it
12
+ * answers HTTP 200 even for logical errors, carrying the message in an `error`
13
+ * object (`{code, message, details}`) — e.g. a malformed query or an invalid
14
+ * parameter. This error models both worlds:
15
+ * - `status` is set for a genuine transport/HTTP failure (non-2xx);
16
+ * - `arcgisCode` is set for a logical ArcGIS error (from `error.code`).
17
+ * `detail` holds the human-readable message in either case.
18
+ */
19
+ export class RegionalatlasApiError extends RegionalatlasError {
20
+ status;
21
+ arcgisCode;
22
+ detail;
23
+ url;
24
+ method;
25
+ body;
26
+ constructor(args) {
27
+ const detailPart = args.detail ? `: ${args.detail}` : "";
28
+ const head = args.status !== undefined
29
+ ? `HTTP ${args.status}`
30
+ : `ArcGIS error${args.arcgisCode !== undefined ? ` ${args.arcgisCode}` : ""}`;
31
+ super(`${head} for ${args.method} ${args.url}${detailPart}`);
32
+ this.status = args.status;
33
+ this.arcgisCode = args.arcgisCode;
34
+ this.url = args.url;
35
+ this.method = args.method;
36
+ this.body = args.body;
37
+ this.detail = args.detail;
38
+ }
39
+ /** True for HTTP statuses the API treats as transient and retry-able. */
40
+ get isRetryable() {
41
+ return this.status === 429 || this.status === 503;
42
+ }
43
+ /** True for a transport-level HTTP 404. */
44
+ get isNotFound() {
45
+ return this.status === 404;
46
+ }
47
+ }
48
+ /** A transport-level failure (DNS, connection reset, timeout, ...). */
49
+ export class RegionalatlasNetworkError extends RegionalatlasError {
50
+ }
51
+ /**
52
+ * A client-side validation / not-found error made before any request — e.g. an
53
+ * unknown indicator code, an unknown geo level, or a year outside an indicator's
54
+ * available range. Crucially, the indicator/level/year values that enter the raw
55
+ * SQL query are all validated against the catalogue here, so a rejected value
56
+ * never reaches the transport.
57
+ */
58
+ export class RegionalatlasValidationError extends RegionalatlasError {
59
+ }
60
+ /** The response body could not be parsed as the expected JSON shape. */
61
+ export class RegionalatlasParseError extends RegionalatlasError {
62
+ }
63
+ //# 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,kBAAmB,SAAQ,KAAK;IAC3C,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;;;;;;;;GAQG;AACH,MAAM,OAAO,qBAAsB,SAAQ,kBAAkB;IAClD,MAAM,CAAqB;IAC3B,UAAU,CAAqB;IAC/B,MAAM,CAAqB;IAC3B,GAAG,CAAS;IACZ,MAAM,CAAS;IACf,IAAI,CAAS;IAEtB,YAAY,IAOX;QACC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,GACR,IAAI,CAAC,MAAM,KAAK,SAAS;YACvB,CAAC,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE;YACvB,CAAC,CAAC,eAAe,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAClF,KAAK,CAAC,GAAG,IAAI,QAAQ,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,GAAG,UAAU,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,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,yEAAyE;IACzE,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC;IACpD,CAAC;IAED,2CAA2C;IAC3C,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC;IAC7B,CAAC;CACF;AAED,uEAAuE;AACvE,MAAM,OAAO,yBAA0B,SAAQ,kBAAkB;CAAG;AAEpE;;;;;;GAMG;AACH,MAAM,OAAO,4BAA6B,SAAQ,kBAAkB;CAAG;AAEvE,wEAAwE;AACxE,MAAM,OAAO,uBAAwB,SAAQ,kBAAkB;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"}
@@ -0,0 +1,80 @@
1
+ // HTTP transport built on Node's built-in `http`/`https` modules — no axios,
2
+ // no fetch polyfill, no third-party HTTP client.
3
+ //
4
+ // The transport is a plain function so it can be trivially swapped out in tests
5
+ // (inject a `mock.fn()` returning a canned HttpResponse) without touching the
6
+ // network. The default implementation below is exercised against a real local
7
+ // `http.createServer` in the test-suite.
8
+ import http from "node:http";
9
+ import https from "node:https";
10
+ import { RegionalatlasNetworkError } from "./errors.js";
11
+ /**
12
+ * Default transport. Resolves with the raw response (including non-2xx) — status
13
+ * interpretation is the client's job. Rejects only on transport-level failures
14
+ * (connection errors, timeouts, malformed URLs).
15
+ */
16
+ export const nodeHttpTransport = (request) => new Promise((resolve, reject) => {
17
+ let url;
18
+ try {
19
+ url = new URL(request.url);
20
+ }
21
+ catch {
22
+ reject(new RegionalatlasNetworkError(`Invalid URL: ${request.url}`));
23
+ return;
24
+ }
25
+ // Only http/https are supported. Reject anything else up front with a clear,
26
+ // typed error instead of letting Node throw an opaque ERR_INVALID_PROTOCOL.
27
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
28
+ reject(new RegionalatlasNetworkError(`Unsupported protocol "${url.protocol}" in URL: ${request.url}`));
29
+ return;
30
+ }
31
+ const isHttps = url.protocol === "https:";
32
+ const driver = isHttps ? https : http;
33
+ const maxBytes = request.maxResponseBytes;
34
+ const req = driver.request(url, {
35
+ method: request.method,
36
+ headers: request.headers,
37
+ }, (res) => {
38
+ const chunks = [];
39
+ let received = 0;
40
+ let aborted = false;
41
+ res.on("data", (chunk) => {
42
+ if (aborted)
43
+ return;
44
+ received += chunk.length;
45
+ if (maxBytes !== undefined && received > maxBytes) {
46
+ aborted = true;
47
+ res.destroy();
48
+ reject(new RegionalatlasNetworkError(`Response exceeded maxResponseBytes (${maxBytes})`));
49
+ return;
50
+ }
51
+ chunks.push(chunk);
52
+ });
53
+ res.on("end", () => {
54
+ if (aborted)
55
+ return;
56
+ resolve({
57
+ status: res.statusCode ?? 0,
58
+ headers: res.headers,
59
+ body: Buffer.concat(chunks),
60
+ });
61
+ });
62
+ res.on("error", (err) => {
63
+ if (aborted)
64
+ return; // we already rejected with the size-cap error
65
+ reject(new RegionalatlasNetworkError(`Response stream error: ${err.message}`, { cause: err }));
66
+ });
67
+ });
68
+ if (request.timeoutMs && request.timeoutMs > 0) {
69
+ req.setTimeout(request.timeoutMs, () => {
70
+ req.destroy(new RegionalatlasNetworkError(`Request timed out after ${request.timeoutMs}ms`));
71
+ });
72
+ }
73
+ req.on("error", (err) => {
74
+ reject(err instanceof RegionalatlasNetworkError ? err : new RegionalatlasNetworkError(err.message, { cause: err }));
75
+ });
76
+ if (request.body !== undefined)
77
+ req.write(request.body);
78
+ req.end();
79
+ });
80
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export { RegionalatlasClient, DEFAULT_CATALOG_URL, parseRow, filterByRegion, projectFields, } from "./client.js";
2
+ export type { RegionalatlasClientOptions } from "./client.js";
3
+ export { RequestEngine, DEFAULT_BASE_URL, sanitizeServerText } from "./engine.js";
4
+ export type { EngineOptions, RawResponse } from "./engine.js";
5
+ export { nodeHttpTransport } from "./http.js";
6
+ export type { Transport, HttpRequest, HttpResponse } from "./http.js";
7
+ export { buildQueryString } from "./query.js";
8
+ export type { QueryParams, QueryValue } from "./query.js";
9
+ export { parseThemes, parseIndicators, filterIndicators, resolveIndicator, resolveYear, tableForCode, } from "./catalog.js";
10
+ export type { IndicatorFilter } from "./catalog.js";
11
+ export { GEO_LEVELS, LEVEL_ALIASES, findLevel, resolveLevel, levelForTyp } from "./levels.js";
12
+ export { buildSql, buildLayerParam } from "./sql.js";
13
+ export { RegionalatlasError, RegionalatlasApiError, RegionalatlasNetworkError, RegionalatlasValidationError, RegionalatlasParseError, } from "./errors.js";
14
+ export * from "./types.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,EACR,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClF,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,cAAc,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ // Public entry point for the API client library.
2
+ export { RegionalatlasClient, DEFAULT_CATALOG_URL, parseRow, filterByRegion, projectFields, } from "./client.js";
3
+ export { RequestEngine, DEFAULT_BASE_URL, sanitizeServerText } from "./engine.js";
4
+ export { nodeHttpTransport } from "./http.js";
5
+ export { buildQueryString } from "./query.js";
6
+ export { parseThemes, parseIndicators, filterIndicators, resolveIndicator, resolveYear, tableForCode, } from "./catalog.js";
7
+ export { GEO_LEVELS, LEVEL_ALIASES, findLevel, resolveLevel, levelForTyp } from "./levels.js";
8
+ export { buildSql, buildLayerParam } from "./sql.js";
9
+ export { RegionalatlasError, RegionalatlasApiError, RegionalatlasNetworkError, RegionalatlasValidationError, RegionalatlasParseError, } from "./errors.js";
10
+ export * from "./types.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,iDAAiD;AAEjD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,QAAQ,EACR,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAElF,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,cAAc,YAAY,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { GeoLevelInfo } from "./types.js";
2
+ /** The canonical level definitions (order defines display order). */
3
+ export declare const GEO_LEVELS: readonly GeoLevelInfo[];
4
+ /** Every alias a user may type for a level (for CLI help / completion). */
5
+ export declare const LEVEL_ALIASES: readonly string[];
6
+ /** Look up a canonical level by friendly name/alias, or return undefined. */
7
+ export declare function findLevel(input: string): GeoLevelInfo | undefined;
8
+ /**
9
+ * Resolve a friendly level name to its info, throwing a typed validation error for
10
+ * an unknown level. The returned `typ` is one of the fixed integers 1/2/3/5 — the
11
+ * only value that ends up in SQL.
12
+ */
13
+ export declare function resolveLevel(input: string): GeoLevelInfo;
14
+ /** Get the info for a raw `typ` integer, or undefined if not a known level. */
15
+ export declare function levelForTyp(typ: number): GeoLevelInfo | undefined;
16
+ //# sourceMappingURL=levels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"levels.d.ts","sourceRoot":"","sources":["../../../src/client/levels.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,YAAY,CAAC;AAGzD,qEAAqE;AACrE,eAAO,MAAM,UAAU,EAAE,SAAS,YAAY,EAK7C,CAAC;AA2BF,2EAA2E;AAC3E,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAAyB,CAAC;AAErE,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAIjE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CASxD;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEjE"}
@@ -0,0 +1,59 @@
1
+ // The geographic-level (Gebietstyp / `typ`) allowlist. Friendly names map onto a
2
+ // fixed set of integers; ONLY the resolved integer `typ` is ever interpolated into
3
+ // the SQL query. An unknown level is rejected before any SQL is built.
4
+ import { RegionalatlasValidationError } from "./errors.js";
5
+ /** The canonical level definitions (order defines display order). */
6
+ export const GEO_LEVELS = [
7
+ { name: "land", typ: 1, label: "Bundesländer" },
8
+ { name: "regierungsbezirk", typ: 2, label: "Regierungsbezirke" },
9
+ { name: "kreis", typ: 3, label: "Kreise / kreisfreie Städte" },
10
+ { name: "gemeinde", typ: 5, label: "Gemeinden" },
11
+ ];
12
+ /**
13
+ * All accepted aliases → the canonical name. Kept small and explicit so the mapping
14
+ * is auditable; the CLI value-parser also uses these keys for its choices.
15
+ */
16
+ const ALIASES = {
17
+ land: "land",
18
+ laender: "land",
19
+ "länder": "land",
20
+ bundesland: "land",
21
+ bundeslaender: "land",
22
+ "bundesländer": "land",
23
+ regierungsbezirk: "regierungsbezirk",
24
+ regierungsbezirke: "regierungsbezirk",
25
+ rb: "regierungsbezirk",
26
+ kreis: "kreis",
27
+ kreise: "kreis",
28
+ landkreis: "kreis",
29
+ landkreise: "kreis",
30
+ gemeinde: "gemeinde",
31
+ gemeinden: "gemeinde",
32
+ };
33
+ /** Every alias a user may type for a level (for CLI help / completion). */
34
+ export const LEVEL_ALIASES = Object.keys(ALIASES);
35
+ /** Look up a canonical level by friendly name/alias, or return undefined. */
36
+ export function findLevel(input) {
37
+ const canonical = ALIASES[input.trim().toLowerCase()];
38
+ if (canonical === undefined)
39
+ return undefined;
40
+ return GEO_LEVELS.find((l) => l.name === canonical);
41
+ }
42
+ /**
43
+ * Resolve a friendly level name to its info, throwing a typed validation error for
44
+ * an unknown level. The returned `typ` is one of the fixed integers 1/2/3/5 — the
45
+ * only value that ends up in SQL.
46
+ */
47
+ export function resolveLevel(input) {
48
+ const level = findLevel(input);
49
+ if (level === undefined) {
50
+ const names = GEO_LEVELS.map((l) => l.name).join(", ");
51
+ throw new RegionalatlasValidationError(`Unknown geo level "${input}". Use one of: ${names} (aliases: ${LEVEL_ALIASES.join(", ")}).`);
52
+ }
53
+ return level;
54
+ }
55
+ /** Get the info for a raw `typ` integer, or undefined if not a known level. */
56
+ export function levelForTyp(typ) {
57
+ return GEO_LEVELS.find((l) => l.typ === typ);
58
+ }
59
+ //# sourceMappingURL=levels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"levels.js","sourceRoot":"","sources":["../../../src/client/levels.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,mFAAmF;AACnF,uEAAuE;AAGvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAE3D,qEAAqE;AACrE,MAAM,CAAC,MAAM,UAAU,GAA4B;IACjD,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE;IAC/C,EAAE,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE;IAChE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE;IAC9D,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE;CACjD,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,GAAqC;IAChD,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,MAAM;IACf,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,MAAM;IAClB,aAAa,EAAE,MAAM;IACrB,cAAc,EAAE,MAAM;IAEtB,gBAAgB,EAAE,kBAAkB;IACpC,iBAAiB,EAAE,kBAAkB;IACrC,EAAE,EAAE,kBAAkB;IAEtB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,OAAO;IACf,SAAS,EAAE,OAAO;IAClB,UAAU,EAAE,OAAO;IAEnB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,UAAU;CACtB,CAAC;AAEF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAsB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAErE,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACtD,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,IAAI,4BAA4B,CACpC,sBAAsB,KAAK,kBAAkB,KAAK,cAAc,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC7F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAM,GAAgB,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,9 @@
1
+ export type QueryPrimitive = string | number | boolean | Date | null | undefined;
2
+ export type QueryValue = QueryPrimitive | QueryPrimitive[];
3
+ export type QueryParams = Record<string, QueryValue>;
4
+ /**
5
+ * Build a query string (without a leading `?`) from a params object.
6
+ * Returns an empty string when no parameters survive filtering.
7
+ */
8
+ export declare function buildQueryString(params: QueryParams): string;
9
+ //# sourceMappingURL=query.d.ts.map