ris-mcp-ts 1.1.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -13
- package/dist/client.d.ts +33 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +181 -76
- package/dist/client.js.map +1 -1
- package/dist/constants.d.ts +12 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -1
- package/dist/formatting.d.ts.map +1 -1
- package/dist/formatting.js +33 -2
- package/dist/formatting.js.map +1 -1
- package/dist/helpers.d.ts +45 -10
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +27 -5
- package/dist/helpers.js.map +1 -1
- package/dist/http.d.ts +33 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +163 -16
- package/dist/http.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +41 -4
- package/dist/parser.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/bezirke.d.ts.map +1 -1
- package/dist/tools/bezirke.js +29 -23
- package/dist/tools/bezirke.js.map +1 -1
- package/dist/tools/bundesgesetzblatt.d.ts.map +1 -1
- package/dist/tools/bundesgesetzblatt.js +29 -22
- package/dist/tools/bundesgesetzblatt.js.map +1 -1
- package/dist/tools/bundesrecht.d.ts +20 -0
- package/dist/tools/bundesrecht.d.ts.map +1 -1
- package/dist/tools/bundesrecht.js +80 -41
- package/dist/tools/bundesrecht.js.map +1 -1
- package/dist/tools/dokument.d.ts.map +1 -1
- package/dist/tools/dokument.js +105 -135
- package/dist/tools/dokument.js.map +1 -1
- package/dist/tools/gemeinden.d.ts.map +1 -1
- package/dist/tools/gemeinden.js +71 -53
- package/dist/tools/gemeinden.js.map +1 -1
- package/dist/tools/history.d.ts.map +1 -1
- package/dist/tools/history.js +29 -23
- package/dist/tools/history.js.map +1 -1
- package/dist/tools/judikatur.d.ts +27 -0
- package/dist/tools/judikatur.d.ts.map +1 -1
- package/dist/tools/judikatur.js +111 -27
- package/dist/tools/judikatur.js.map +1 -1
- package/dist/tools/landesgesetzblatt.d.ts.map +1 -1
- package/dist/tools/landesgesetzblatt.js +26 -20
- package/dist/tools/landesgesetzblatt.js.map +1 -1
- package/dist/tools/landesrecht.d.ts +20 -0
- package/dist/tools/landesrecht.d.ts.map +1 -1
- package/dist/tools/landesrecht.js +83 -31
- package/dist/tools/landesrecht.js.map +1 -1
- package/dist/tools/regierungsvorlagen.d.ts.map +1 -1
- package/dist/tools/regierungsvorlagen.js +59 -53
- package/dist/tools/regierungsvorlagen.js.map +1 -1
- package/dist/tools/sonstige.d.ts.map +1 -1
- package/dist/tools/sonstige.js +97 -91
- package/dist/tools/sonstige.js.map +1 -1
- package/dist/tools/verordnungen.d.ts.map +1 -1
- package/dist/tools/verordnungen.js +26 -20
- package/dist/tools/verordnungen.js.map +1 -1
- package/dist/types.d.ts +142 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +61 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +43 -27
package/README.md
CHANGED
|
@@ -33,11 +33,11 @@ The server translates these natural language requests into structured API calls
|
|
|
33
33
|
|
|
34
34
|
- **12 specialized tools** covering all major RIS collections
|
|
35
35
|
- **Federal law** (ABGB, StGB, UGB, ...) and **state law** for all 9 provinces
|
|
36
|
-
- **Court decisions** from
|
|
36
|
+
- **Court decisions** from 16 court types (Supreme Court, Constitutional Court, Administrative Court, ...)
|
|
37
37
|
- **Law gazettes** — Federal (BGBl) and state (LGBl)
|
|
38
38
|
- **Government bills**, ministerial decrees, cabinet protocols
|
|
39
39
|
- **Full document retrieval** with smart prefix-based routing
|
|
40
|
-
- **Change history tracking** across
|
|
40
|
+
- **Change history tracking** across 36 application types
|
|
41
41
|
- **Markdown and JSON** output formats
|
|
42
42
|
- **Free and open** — uses Austria's Open Government Data API, no API key needed
|
|
43
43
|
|
|
@@ -148,7 +148,7 @@ npx -y ris-mcp-ts
|
|
|
148
148
|
|------|-------------|
|
|
149
149
|
| `ris_bundesrecht` | Search federal laws (ABGB, StGB, UGB, etc.) |
|
|
150
150
|
| `ris_landesrecht` | Search state/provincial laws (all 9 provinces) |
|
|
151
|
-
| `ris_judikatur` | Search court decisions (
|
|
151
|
+
| `ris_judikatur` | Search court decisions (16 court types) |
|
|
152
152
|
| `ris_bundesgesetzblatt` | Search Federal Law Gazettes (BGBl I/II/III) |
|
|
153
153
|
| `ris_landesgesetzblatt` | Search State Law Gazettes (LGBl) |
|
|
154
154
|
| `ris_regierungsvorlagen` | Search government bills |
|
|
@@ -156,7 +156,7 @@ npx -y ris-mcp-ts
|
|
|
156
156
|
| `ris_bezirke` | Search district authority announcements |
|
|
157
157
|
| `ris_gemeinden` | Search municipal law and regulations |
|
|
158
158
|
| `ris_sonstige` | Search miscellaneous collections (8 sub-apps) |
|
|
159
|
-
| `ris_history` | Track document change history (
|
|
159
|
+
| `ris_history` | Track document change history (36 app types) |
|
|
160
160
|
| `ris_verordnungen` | Search state ordinance gazettes |
|
|
161
161
|
|
|
162
162
|
## Tool Reference
|
|
@@ -172,8 +172,9 @@ Search Austrian federal laws such as ABGB, StGB, UGB, and more.
|
|
|
172
172
|
|-----------|------|-------------|
|
|
173
173
|
| `suchworte` | string | Full-text search (e.g., "Mietrecht", "Schadenersatz") |
|
|
174
174
|
| `titel` | string | Search in law titles (e.g., "ABGB", "Strafgesetzbuch") |
|
|
175
|
-
| `paragraph` | string | Section number (e.g., "1295" for §1295) |
|
|
176
|
-
| `
|
|
175
|
+
| `paragraph` | string | Section number (e.g., "1295" for §1295, "7" for Art 7) |
|
|
176
|
+
| `abschnitt_typ` | string | Type of section `paragraph` refers to: "Paragraph" (default), "Artikel" (article-based laws like B-VG), "Anlage" (annex) |
|
|
177
|
+
| `applikation` | string | "BrKons" (consolidated, default), "Begut" (drafts), "BgblAuth" (gazette), "Erv" (English translations) |
|
|
177
178
|
| `fassung_vom` | string | Date for historical version (YYYY-MM-DD) |
|
|
178
179
|
| `seite` | number | Page number (default: 1) |
|
|
179
180
|
| `limit` | number | Results per page: 10, 20, 50, 100 (default: 20) |
|
|
@@ -181,6 +182,8 @@ Search Austrian federal laws such as ABGB, StGB, UGB, and more.
|
|
|
181
182
|
|
|
182
183
|
All parameters are optional. At least one search parameter (`suchworte`, `titel`, or `paragraph`) should be provided.
|
|
183
184
|
|
|
185
|
+
> **Note:** `applikation="Erv"` (English translations) uses a different parameter vocabulary internally (`SearchTerms`/`Title`) and does not support `paragraph`/`abschnitt_typ` or `fassung_vom`.
|
|
186
|
+
|
|
184
187
|
</details>
|
|
185
188
|
|
|
186
189
|
<details>
|
|
@@ -195,6 +198,10 @@ Search state laws of the nine Austrian provinces.
|
|
|
195
198
|
| `suchworte` | string | Full-text search |
|
|
196
199
|
| `titel` | string | Search in law titles |
|
|
197
200
|
| `bundesland` | string | Province: Wien, Niederoesterreich, Oberoesterreich, Salzburg, Tirol, Vorarlberg, Kaernten, Steiermark, Burgenland |
|
|
201
|
+
| `paragraph` | string | Section number (e.g., "1" for §1, "7" for Art 7) |
|
|
202
|
+
| `abschnitt_typ` | string | Type of section `paragraph` refers to: "Paragraph" (default), "Artikel", "Anlage" |
|
|
203
|
+
| `fassung_vom` | string | Date for historical version (YYYY-MM-DD) |
|
|
204
|
+
| `gesetzesnummer` | string | Exact law number (Gesetzesnummer) of a specific state law |
|
|
198
205
|
| `applikation` | string | "LrKons" (consolidated, default) |
|
|
199
206
|
| `seite` | number | Page number |
|
|
200
207
|
| `limit` | number | Results per page |
|
|
@@ -212,18 +219,25 @@ Search court decisions from Austrian courts.
|
|
|
212
219
|
| Parameter | Type | Description |
|
|
213
220
|
|-----------|------|-------------|
|
|
214
221
|
| `suchworte` | string | Full-text search in decisions |
|
|
215
|
-
| `
|
|
222
|
+
| `gerichtsbarkeit` | string | Court system / data collection to search — the RIS "Applikation" (see below; default "Justiz") |
|
|
223
|
+
| `dokumenttyp` | string | "rechtssatz" (headnotes only), "entscheidungstext" (full texts only), or "beide" (both, default) |
|
|
224
|
+
| `gericht` | string | Actual court within the jurisdiction (e.g., "OGH", "OLG Wien"); applies to gerichtsbarkeit="Justiz" |
|
|
225
|
+
| `rechtsgebiet` | string | Broad legal area: "Zivilrecht" or "Strafrecht" (Justiz) |
|
|
226
|
+
| `fachgebiet` | string | OGH subject area (e.g., "Arbeitsrecht"); Justiz, needs dokumenttyp "entscheidungstext"/"beide" |
|
|
227
|
+
| `entscheidungsart` | string | Decision type (e.g., "Erkenntnis", "Beschluss"); allowed values differ by jurisdiction |
|
|
228
|
+
| `sammlungsnummer` | string | Collection number: VfSlg (Vfgh) or VwSlg (Vwgh) |
|
|
216
229
|
| `norm` | string | Legal norm (e.g., "1319a ABGB") |
|
|
217
230
|
| `geschaeftszahl` | string | Case number (e.g., "5Ob234/20b") |
|
|
218
231
|
| `entscheidungsdatum_von` | string | Decision date from (YYYY-MM-DD) |
|
|
219
232
|
| `entscheidungsdatum_bis` | string | Decision date to (YYYY-MM-DD) |
|
|
233
|
+
| `sortierung` | string | Sort by decision date: "datum_auf" (oldest first) or "datum_ab" (newest first) |
|
|
220
234
|
| `seite` | number | Page number |
|
|
221
235
|
| `limit` | number | Results per page |
|
|
222
236
|
| `response_format` | string | "markdown" or "json" |
|
|
223
237
|
|
|
224
|
-
**Available
|
|
238
|
+
**Available `gerichtsbarkeit` values (16):**
|
|
225
239
|
|
|
226
|
-
| Value | Court |
|
|
240
|
+
| Value | Court system |
|
|
227
241
|
|-------|-------|
|
|
228
242
|
| `Justiz` | Supreme Court (OGH), Higher Regional Courts (OLG), Regional Courts (LG) — default |
|
|
229
243
|
| `Vfgh` | Constitutional Court |
|
|
@@ -231,11 +245,16 @@ Search court decisions from Austrian courts.
|
|
|
231
245
|
| `Bvwg` | Federal Administrative Court |
|
|
232
246
|
| `Lvwg` | Provincial Administrative Courts |
|
|
233
247
|
| `Dsk` | Data Protection Authority |
|
|
234
|
-
| `AsylGH` | Asylum Court (historical) |
|
|
248
|
+
| `AsylGH` | Asylum Court (historical, until 2013) |
|
|
235
249
|
| `Normenliste` | Index of legal norms |
|
|
236
250
|
| `Pvak` | Personnel Representation Supervisory Commission |
|
|
237
251
|
| `Gbk` | Equal Treatment Commission |
|
|
238
252
|
| `Dok` | Disciplinary Commission |
|
|
253
|
+
| `Verg` | Federal Procurement Office (historical, dissolved 2014) |
|
|
254
|
+
| `Uvs` | Independent Administrative Senates (historical, dissolved 2014) |
|
|
255
|
+
| `Ubas` | Independent Federal Asylum Senate (historical, dissolved 2014) |
|
|
256
|
+
| `Umse` | Environmental Senate (historical, dissolved 2014) |
|
|
257
|
+
| `Bks` | Federal Communications Board (historical, dissolved 2014) |
|
|
239
258
|
|
|
240
259
|
</details>
|
|
241
260
|
|
|
@@ -325,12 +344,19 @@ At least one of `dokumentnummer` or `url` is required. Long documents are trunca
|
|
|
325
344
|
|--------|------|
|
|
326
345
|
| NOR | Federal norms |
|
|
327
346
|
| LBG, LKT, LNO, LOO, LSB, LST, LTI, LVB, LWI | State laws (9 provinces) |
|
|
328
|
-
| JWR,
|
|
329
|
-
|
|
|
347
|
+
| JWR, JWT | Supreme Administrative Court (VwGH) |
|
|
348
|
+
| JFR, JFT | Constitutional Court (VfGH) |
|
|
349
|
+
| JJR, JJT | Ordinary courts (Justiz) |
|
|
350
|
+
| BVWG, LVWG | Federal / State Administrative Courts |
|
|
351
|
+
| DSB, PDK, GBK, PVAB, DKT, NL, ASYLGH | Data protection, equal treatment, personnel, disciplinary, norm lists, asylum |
|
|
352
|
+
| VERG, JUR, JUT, UBAS, UMSE, BKS | Historical jurisdictions (dissolved 2014) |
|
|
353
|
+
| BGBLA, BGBL, BGBLPDF | Federal Law Gazette (authentic / 1945–2003 / PDF) |
|
|
330
354
|
| REGV | Government bills |
|
|
331
355
|
| BVB | District authorities |
|
|
332
356
|
| VBL | Ordinance gazettes |
|
|
333
|
-
| MRP, ERL | Cabinet protocols,
|
|
357
|
+
| MRP, ERL, PRUEF, AVSV, SPG, KMGER | Cabinet protocols, decrees, trade exams, social insurance, health plans, court announcements |
|
|
358
|
+
|
|
359
|
+
> The full routing registry lives in `DOCUMENT_ROUTES` (`src/client.ts`); unknown prefixes fall back to a Justiz search.
|
|
334
360
|
|
|
335
361
|
</details>
|
|
336
362
|
|
package/dist/client.d.ts
CHANGED
|
@@ -35,35 +35,35 @@ export declare function isAllowedUrl(url: string): boolean;
|
|
|
35
35
|
/**
|
|
36
36
|
* Search federal law (Bundesrecht).
|
|
37
37
|
*/
|
|
38
|
-
export declare function searchBundesrecht(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
38
|
+
export declare function searchBundesrecht(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
39
39
|
/**
|
|
40
40
|
* Search state/provincial law (Landesrecht).
|
|
41
41
|
*/
|
|
42
|
-
export declare function searchLandesrecht(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
42
|
+
export declare function searchLandesrecht(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
43
43
|
/**
|
|
44
44
|
* Search case law/jurisprudence (Judikatur).
|
|
45
45
|
*/
|
|
46
|
-
export declare function searchJudikatur(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
46
|
+
export declare function searchJudikatur(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
47
47
|
/**
|
|
48
48
|
* Search district administrative authorities (Bezirke).
|
|
49
49
|
*/
|
|
50
|
-
export declare function searchBezirke(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
50
|
+
export declare function searchBezirke(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
51
51
|
/**
|
|
52
52
|
* Search municipal law (Gemeinden).
|
|
53
53
|
*/
|
|
54
|
-
export declare function searchGemeinden(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
54
|
+
export declare function searchGemeinden(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
55
55
|
/**
|
|
56
56
|
* Search miscellaneous legal collections (Sonstige).
|
|
57
57
|
*/
|
|
58
|
-
export declare function searchSonstige(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
58
|
+
export declare function searchSonstige(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
59
59
|
/**
|
|
60
60
|
* Search document change history (History).
|
|
61
61
|
*/
|
|
62
|
-
export declare function searchHistory(params: Record<string, unknown>, timeout?: number): Promise<NormalizedSearchResults>;
|
|
62
|
+
export declare function searchHistory(params: Record<string, unknown>, timeout?: number, signal?: AbortSignal): Promise<NormalizedSearchResults>;
|
|
63
63
|
/**
|
|
64
64
|
* Fetch HTML content from a document URL.
|
|
65
65
|
*/
|
|
66
|
-
export declare function getDocumentContent(url: string, timeout?: number): Promise<string>;
|
|
66
|
+
export declare function getDocumentContent(url: string, timeout?: number, signal?: AbortSignal): Promise<string>;
|
|
67
67
|
/**
|
|
68
68
|
* Validate dokumentnummer contains only safe characters.
|
|
69
69
|
* Defense-in-depth check in addition to Zod schema.
|
|
@@ -76,6 +76,28 @@ export declare function getDocumentContent(url: string, timeout?: number): Promi
|
|
|
76
76
|
* Examples: NOR40052761, BVWG_W123_2000000_1_00
|
|
77
77
|
*/
|
|
78
78
|
export declare function isValidDokumentnummer(dokumentnummer: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* RIS search endpoint used for the fallback search when direct URL fetch fails.
|
|
81
|
+
*/
|
|
82
|
+
export type DocumentEndpoint = 'Bundesrecht' | 'Landesrecht' | 'Judikatur' | 'Sonstige' | 'Bezirke';
|
|
83
|
+
/**
|
|
84
|
+
* Routing information for a Dokumentnummer prefix.
|
|
85
|
+
*/
|
|
86
|
+
export interface DocumentRoute {
|
|
87
|
+
/** Folder segment in the ris.bka.gv.at/Dokumente/<folder>/ direct URL. */
|
|
88
|
+
urlFolder: string;
|
|
89
|
+
/** Search endpoint used for the fallback search. */
|
|
90
|
+
endpoint: DocumentEndpoint;
|
|
91
|
+
/** `Applikation` API parameter for the fallback search. */
|
|
92
|
+
applikation: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Look up the routing information for a Dokumentnummer by its prefix.
|
|
96
|
+
*
|
|
97
|
+
* @param dokumentnummer - The RIS document number (e.g., "NOR12019037")
|
|
98
|
+
* @returns The matching route, or null if the prefix is unknown
|
|
99
|
+
*/
|
|
100
|
+
export declare function getDocumentRoute(dokumentnummer: string): DocumentRoute | null;
|
|
79
101
|
/**
|
|
80
102
|
* Construct a direct document URL based on the Dokumentnummer prefix.
|
|
81
103
|
*
|
|
@@ -103,7 +125,9 @@ export type DirectDocumentResult = {
|
|
|
103
125
|
*
|
|
104
126
|
* @param dokumentnummer - The RIS document number (e.g., "NOR12019037")
|
|
105
127
|
* @param timeout - Request timeout in milliseconds
|
|
128
|
+
* @param signal - Caller's cancellation signal; a cancellation is thrown rather
|
|
129
|
+
* than reported as an unsuccessful fetch, so callers do not fall back to a search
|
|
106
130
|
* @returns Result object with HTML content on success, or error details on failure
|
|
107
131
|
*/
|
|
108
|
-
export declare function getDocumentByNumber(dokumentnummer: string, timeout?: number): Promise<DirectDocumentResult>;
|
|
132
|
+
export declare function getDocumentByNumber(dokumentnummer: string, timeout?: number, signal?: AbortSignal): Promise<DirectDocumentResult>;
|
|
109
133
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,uBAAuB,EAIxB,MAAM,YAAY,CAAC;AAMpB;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAgB,UAAU,CAAC,EAAE,MAAM,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAKjD;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,WAAW;gBAClC,OAAO,SAAiC;CAIrD;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,SAAgB,aAAa,CAAC,EAAE,KAAK,CAAC;gBAE1B,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,KAAK;CAKnD;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -46,6 +46,47 @@ export class RISParsingError extends RISAPIError {
|
|
|
46
46
|
// =============================================================================
|
|
47
47
|
const BASE_URL = 'https://data.bka.gv.at/ris/api/v2.6/';
|
|
48
48
|
const DEFAULT_TIMEOUT = 30000; // 30 seconds in milliseconds
|
|
49
|
+
/** HTTP status codes treated as transient and worth a single retry. */
|
|
50
|
+
const TRANSIENT_STATUS_CODES = new Set([502, 503, 504]);
|
|
51
|
+
/** Delay before the single retry of a transient failure. */
|
|
52
|
+
const RETRY_DELAY_MS = 500;
|
|
53
|
+
/**
|
|
54
|
+
* Resolve after the given number of milliseconds, or reject as soon as the
|
|
55
|
+
* caller cancels — a cancelled request should not sit out the retry backoff.
|
|
56
|
+
*/
|
|
57
|
+
function delay(ms, signal) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
if (signal?.aborted) {
|
|
60
|
+
reject(signal.reason);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const timeoutId = setTimeout(resolve, ms);
|
|
64
|
+
signal?.addEventListener('abort', () => {
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
reject(signal.reason);
|
|
67
|
+
}, { once: true });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Combine the per-request timeout with the caller's cancellation signal so the
|
|
72
|
+
* fetch aborts on whichever fires first.
|
|
73
|
+
*/
|
|
74
|
+
function withTimeoutSignal(timeoutSignal, callerSignal) {
|
|
75
|
+
return callerSignal ? AbortSignal.any([timeoutSignal, callerSignal]) : timeoutSignal;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Determine whether an error is transient (a gateway error or timeout) and
|
|
79
|
+
* therefore worth retrying once.
|
|
80
|
+
*/
|
|
81
|
+
function isTransientError(error) {
|
|
82
|
+
if (error instanceof RISTimeoutError) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (error instanceof RISAPIError && error.statusCode !== undefined) {
|
|
86
|
+
return TRANSIENT_STATUS_CODES.has(error.statusCode);
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
49
90
|
/**
|
|
50
91
|
* Allowed hostnames for document content fetching (SSRF protection).
|
|
51
92
|
*/
|
|
@@ -123,20 +164,18 @@ function extractSearchResults(parsedResponse) {
|
|
|
123
164
|
};
|
|
124
165
|
}
|
|
125
166
|
/**
|
|
126
|
-
*
|
|
167
|
+
* Perform a single request attempt against the RIS API.
|
|
127
168
|
*/
|
|
128
|
-
async function
|
|
129
|
-
const url = new URL(endpoint, BASE_URL);
|
|
130
|
-
url.search = buildParams(params).toString();
|
|
169
|
+
async function requestOnce(url, endpoint, timeout, signal) {
|
|
131
170
|
const controller = new AbortController();
|
|
132
171
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
133
172
|
try {
|
|
134
|
-
const response = await fetch(url
|
|
173
|
+
const response = await fetch(url, {
|
|
135
174
|
method: 'GET',
|
|
136
175
|
headers: {
|
|
137
176
|
Accept: 'application/json',
|
|
138
177
|
},
|
|
139
|
-
signal: controller.signal,
|
|
178
|
+
signal: withTimeoutSignal(controller.signal, signal),
|
|
140
179
|
});
|
|
141
180
|
clearTimeout(timeoutId);
|
|
142
181
|
if (!response.ok) {
|
|
@@ -149,6 +188,12 @@ async function request(endpoint, params, timeout = DEFAULT_TIMEOUT) {
|
|
|
149
188
|
}
|
|
150
189
|
catch (e) {
|
|
151
190
|
clearTimeout(timeoutId);
|
|
191
|
+
// The combined signal makes a timeout and a caller cancellation look alike
|
|
192
|
+
// to fetch, so the signals decide: a cancelled request is not an upstream
|
|
193
|
+
// failure and must not be dressed up as one.
|
|
194
|
+
if (signal?.aborted) {
|
|
195
|
+
throw e;
|
|
196
|
+
}
|
|
152
197
|
if (e instanceof RISAPIError || e instanceof RISParsingError) {
|
|
153
198
|
throw e;
|
|
154
199
|
}
|
|
@@ -161,58 +206,80 @@ async function request(endpoint, params, timeout = DEFAULT_TIMEOUT) {
|
|
|
161
206
|
throw new RISAPIError(`Request failed for ${endpoint}: ${String(e)}`);
|
|
162
207
|
}
|
|
163
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Make a request to the RIS API, retrying once on transient failures
|
|
211
|
+
* (HTTP 502/503/504 and timeouts) after a short delay.
|
|
212
|
+
*/
|
|
213
|
+
async function request(endpoint, params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
214
|
+
const url = new URL(endpoint, BASE_URL);
|
|
215
|
+
url.search = buildParams(params).toString();
|
|
216
|
+
const urlString = url.toString();
|
|
217
|
+
try {
|
|
218
|
+
return await requestOnce(urlString, endpoint, timeout, signal);
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
if (!isTransientError(e)) {
|
|
222
|
+
throw e;
|
|
223
|
+
}
|
|
224
|
+
// A cancelled request is not a transient upstream error: `delay` rejects
|
|
225
|
+
// straight away if the caller has cancelled, or as soon as it does, so the
|
|
226
|
+
// second attempt never starts.
|
|
227
|
+
await delay(RETRY_DELAY_MS, signal);
|
|
228
|
+
return requestOnce(urlString, endpoint, timeout, signal);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
164
231
|
/**
|
|
165
232
|
* Search federal law (Bundesrecht).
|
|
166
233
|
*/
|
|
167
|
-
export async function searchBundesrecht(params, timeout = DEFAULT_TIMEOUT) {
|
|
168
|
-
return request('Bundesrecht', params, timeout);
|
|
234
|
+
export async function searchBundesrecht(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
235
|
+
return request('Bundesrecht', params, timeout, signal);
|
|
169
236
|
}
|
|
170
237
|
/**
|
|
171
238
|
* Search state/provincial law (Landesrecht).
|
|
172
239
|
*/
|
|
173
|
-
export async function searchLandesrecht(params, timeout = DEFAULT_TIMEOUT) {
|
|
174
|
-
return request('Landesrecht', params, timeout);
|
|
240
|
+
export async function searchLandesrecht(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
241
|
+
return request('Landesrecht', params, timeout, signal);
|
|
175
242
|
}
|
|
176
243
|
/**
|
|
177
244
|
* Search case law/jurisprudence (Judikatur).
|
|
178
245
|
*/
|
|
179
|
-
export async function searchJudikatur(params, timeout = DEFAULT_TIMEOUT) {
|
|
180
|
-
return request('Judikatur', params, timeout);
|
|
246
|
+
export async function searchJudikatur(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
247
|
+
return request('Judikatur', params, timeout, signal);
|
|
181
248
|
}
|
|
182
249
|
/**
|
|
183
250
|
* Search district administrative authorities (Bezirke).
|
|
184
251
|
*/
|
|
185
|
-
export async function searchBezirke(params, timeout = DEFAULT_TIMEOUT) {
|
|
186
|
-
return request('Bezirke', params, timeout);
|
|
252
|
+
export async function searchBezirke(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
253
|
+
return request('Bezirke', params, timeout, signal);
|
|
187
254
|
}
|
|
188
255
|
/**
|
|
189
256
|
* Search municipal law (Gemeinden).
|
|
190
257
|
*/
|
|
191
|
-
export async function searchGemeinden(params, timeout = DEFAULT_TIMEOUT) {
|
|
192
|
-
return request('Gemeinden', params, timeout);
|
|
258
|
+
export async function searchGemeinden(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
259
|
+
return request('Gemeinden', params, timeout, signal);
|
|
193
260
|
}
|
|
194
261
|
/**
|
|
195
262
|
* Search miscellaneous legal collections (Sonstige).
|
|
196
263
|
*/
|
|
197
|
-
export async function searchSonstige(params, timeout = DEFAULT_TIMEOUT) {
|
|
198
|
-
return request('Sonstige', params, timeout);
|
|
264
|
+
export async function searchSonstige(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
265
|
+
return request('Sonstige', params, timeout, signal);
|
|
199
266
|
}
|
|
200
267
|
/**
|
|
201
268
|
* Search document change history (History).
|
|
202
269
|
*/
|
|
203
|
-
export async function searchHistory(params, timeout = DEFAULT_TIMEOUT) {
|
|
204
|
-
return request('History', params, timeout);
|
|
270
|
+
export async function searchHistory(params, timeout = DEFAULT_TIMEOUT, signal) {
|
|
271
|
+
return request('History', params, timeout, signal);
|
|
205
272
|
}
|
|
206
273
|
/**
|
|
207
274
|
* Fetch HTML content from a document URL.
|
|
208
275
|
*/
|
|
209
|
-
export async function getDocumentContent(url, timeout = DEFAULT_TIMEOUT) {
|
|
276
|
+
export async function getDocumentContent(url, timeout = DEFAULT_TIMEOUT, signal) {
|
|
210
277
|
const controller = new AbortController();
|
|
211
278
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
212
279
|
try {
|
|
213
280
|
const response = await fetch(url, {
|
|
214
281
|
method: 'GET',
|
|
215
|
-
signal: controller.signal,
|
|
282
|
+
signal: withTimeoutSignal(controller.signal, signal),
|
|
216
283
|
});
|
|
217
284
|
clearTimeout(timeoutId);
|
|
218
285
|
if (!response.ok) {
|
|
@@ -223,6 +290,10 @@ export async function getDocumentContent(url, timeout = DEFAULT_TIMEOUT) {
|
|
|
223
290
|
}
|
|
224
291
|
catch (e) {
|
|
225
292
|
clearTimeout(timeoutId);
|
|
293
|
+
// See requestOnce: only the signals distinguish a cancellation from a timeout.
|
|
294
|
+
if (signal?.aborted) {
|
|
295
|
+
throw e;
|
|
296
|
+
}
|
|
226
297
|
if (e instanceof RISAPIError) {
|
|
227
298
|
throw e;
|
|
228
299
|
}
|
|
@@ -258,52 +329,83 @@ export function isValidDokumentnummer(dokumentnummer) {
|
|
|
258
329
|
return /^[A-Z][A-Z0-9_]+$/.test(dokumentnummer);
|
|
259
330
|
}
|
|
260
331
|
/**
|
|
261
|
-
*
|
|
262
|
-
*
|
|
332
|
+
* Single source of truth mapping Dokumentnummer prefixes to their direct-URL
|
|
333
|
+
* folder and fallback-search routing. Used by both {@link constructDocumentUrl}
|
|
334
|
+
* (direct URL) and the ris_dokument fallback search (via {@link getDocumentRoute}).
|
|
335
|
+
* Prefixes are matched longest-first.
|
|
336
|
+
*
|
|
337
|
+
* The Judikatur document-ID scheme is `J<court><R|T>` where R = Rechtssatz and
|
|
338
|
+
* T = Entscheidungstext: JJ = Justiz, JW = VwGH, JF = VfGH, JU = UVS.
|
|
339
|
+
*
|
|
340
|
+
* Folder segments and endpoints verified against RIS API v2.6
|
|
341
|
+
* (data.bka.gv.at / ris.bka.gv.at) on 2026-07-03.
|
|
263
342
|
*/
|
|
264
|
-
const
|
|
265
|
-
// Bundesrecht (Federal Law)
|
|
266
|
-
NOR: '
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
343
|
+
const DOCUMENT_ROUTES = {
|
|
344
|
+
// --- Bundesrecht (Federal Law) ---
|
|
345
|
+
NOR: { urlFolder: 'Bundesnormen', endpoint: 'Bundesrecht', applikation: 'BrKons' },
|
|
346
|
+
BGBLPDF: { urlFolder: 'BgblPdf', endpoint: 'Bundesrecht', applikation: 'BgblPdf' },
|
|
347
|
+
BGBLA: { urlFolder: 'BgblAuth', endpoint: 'Bundesrecht', applikation: 'BgblAuth' },
|
|
348
|
+
BGBL: { urlFolder: 'BgblAlt', endpoint: 'Bundesrecht', applikation: 'BgblAlt' },
|
|
349
|
+
REGV: { urlFolder: 'RegV', endpoint: 'Bundesrecht', applikation: 'RegV' },
|
|
350
|
+
// --- Landesrecht (State Law) — one prefix per state ---
|
|
351
|
+
LBG: { urlFolder: 'LrBgld', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
352
|
+
LKT: { urlFolder: 'LrK', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
353
|
+
LNO: { urlFolder: 'LrNO', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
354
|
+
LOO: { urlFolder: 'LrOO', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
355
|
+
LSB: { urlFolder: 'LrSbg', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
356
|
+
LST: { urlFolder: 'LrStmk', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
357
|
+
LTI: { urlFolder: 'LrT', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
358
|
+
LVB: { urlFolder: 'LrVbg', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
359
|
+
LWI: { urlFolder: 'LrW', endpoint: 'Landesrecht', applikation: 'LrKons' },
|
|
360
|
+
VBL: { urlFolder: 'Vbl', endpoint: 'Landesrecht', applikation: 'Vbl' },
|
|
361
|
+
// --- Judikatur (Case Law) ---
|
|
362
|
+
JWR: { urlFolder: 'Vwgh', endpoint: 'Judikatur', applikation: 'Vwgh' },
|
|
363
|
+
JWT: { urlFolder: 'Vwgh', endpoint: 'Judikatur', applikation: 'Vwgh' },
|
|
364
|
+
JFR: { urlFolder: 'Vfgh', endpoint: 'Judikatur', applikation: 'Vfgh' },
|
|
365
|
+
JFT: { urlFolder: 'Vfgh', endpoint: 'Judikatur', applikation: 'Vfgh' },
|
|
366
|
+
JJR: { urlFolder: 'Justiz', endpoint: 'Judikatur', applikation: 'Justiz' },
|
|
367
|
+
JJT: { urlFolder: 'Justiz', endpoint: 'Judikatur', applikation: 'Justiz' },
|
|
368
|
+
BVWG: { urlFolder: 'Bvwg', endpoint: 'Judikatur', applikation: 'Bvwg' },
|
|
369
|
+
LVWG: { urlFolder: 'Lvwg', endpoint: 'Judikatur', applikation: 'Lvwg' },
|
|
370
|
+
DSB: { urlFolder: 'Dsk', endpoint: 'Judikatur', applikation: 'Dsk' },
|
|
371
|
+
PDK: { urlFolder: 'Dsk', endpoint: 'Judikatur', applikation: 'Dsk' },
|
|
372
|
+
GBK: { urlFolder: 'Gbk', endpoint: 'Judikatur', applikation: 'Gbk' },
|
|
373
|
+
PVAB: { urlFolder: 'Pvak', endpoint: 'Judikatur', applikation: 'Pvak' },
|
|
374
|
+
DKT: { urlFolder: 'Dok', endpoint: 'Judikatur', applikation: 'Dok' },
|
|
375
|
+
VERG: { urlFolder: 'Verg', endpoint: 'Judikatur', applikation: 'Verg' },
|
|
376
|
+
JUR: { urlFolder: 'Uvs', endpoint: 'Judikatur', applikation: 'Uvs' },
|
|
377
|
+
JUT: { urlFolder: 'Uvs', endpoint: 'Judikatur', applikation: 'Uvs' },
|
|
378
|
+
UBAS: { urlFolder: 'Ubas', endpoint: 'Judikatur', applikation: 'Ubas' },
|
|
379
|
+
UMSE: { urlFolder: 'Umse', endpoint: 'Judikatur', applikation: 'Umse' },
|
|
380
|
+
BKS: { urlFolder: 'Bks', endpoint: 'Judikatur', applikation: 'Bks' },
|
|
381
|
+
ASYLGH: { urlFolder: 'AsylGH', endpoint: 'Judikatur', applikation: 'AsylGH' },
|
|
382
|
+
NL: { urlFolder: 'Normenliste', endpoint: 'Judikatur', applikation: 'Normenliste' },
|
|
383
|
+
// --- Sonstige (Miscellaneous) ---
|
|
384
|
+
MRP: { urlFolder: 'Mrp', endpoint: 'Sonstige', applikation: 'Mrp' },
|
|
385
|
+
ERL: { urlFolder: 'Erlaesse', endpoint: 'Sonstige', applikation: 'Erlaesse' },
|
|
386
|
+
PRUEF: { urlFolder: 'PruefGewO', endpoint: 'Sonstige', applikation: 'PruefGewO' },
|
|
387
|
+
AVSV: { urlFolder: 'Avsv', endpoint: 'Sonstige', applikation: 'Avsv' },
|
|
388
|
+
SPG: { urlFolder: 'Spg', endpoint: 'Sonstige', applikation: 'Spg' },
|
|
389
|
+
KMGER: { urlFolder: 'KmGer', endpoint: 'Sonstige', applikation: 'KmGer' },
|
|
390
|
+
// --- Bezirke (District Administrative Authorities) ---
|
|
391
|
+
BVB: { urlFolder: 'Bvb', endpoint: 'Bezirke', applikation: 'Bvb' },
|
|
306
392
|
};
|
|
393
|
+
/** Prefixes sorted longest-first so specific prefixes win over shorter ones. */
|
|
394
|
+
const DOCUMENT_ROUTE_PREFIXES = Object.keys(DOCUMENT_ROUTES).sort((a, b) => b.length - a.length);
|
|
395
|
+
/**
|
|
396
|
+
* Look up the routing information for a Dokumentnummer by its prefix.
|
|
397
|
+
*
|
|
398
|
+
* @param dokumentnummer - The RIS document number (e.g., "NOR12019037")
|
|
399
|
+
* @returns The matching route, or null if the prefix is unknown
|
|
400
|
+
*/
|
|
401
|
+
export function getDocumentRoute(dokumentnummer) {
|
|
402
|
+
for (const prefix of DOCUMENT_ROUTE_PREFIXES) {
|
|
403
|
+
if (dokumentnummer.startsWith(prefix)) {
|
|
404
|
+
return DOCUMENT_ROUTES[prefix];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
307
409
|
/**
|
|
308
410
|
* Construct a direct document URL based on the Dokumentnummer prefix.
|
|
309
411
|
*
|
|
@@ -315,15 +417,11 @@ export function constructDocumentUrl(dokumentnummer) {
|
|
|
315
417
|
if (!isValidDokumentnummer(dokumentnummer)) {
|
|
316
418
|
return null;
|
|
317
419
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (dokumentnummer.startsWith(prefix)) {
|
|
322
|
-
const pattern = DOCUMENT_URL_PATTERNS[prefix];
|
|
323
|
-
return pattern.replace(/{dokumentnummer}/g, dokumentnummer);
|
|
324
|
-
}
|
|
420
|
+
const route = getDocumentRoute(dokumentnummer);
|
|
421
|
+
if (!route) {
|
|
422
|
+
return null;
|
|
325
423
|
}
|
|
326
|
-
return
|
|
424
|
+
return `https://ris.bka.gv.at/Dokumente/${route.urlFolder}/${dokumentnummer}/${dokumentnummer}.html`;
|
|
327
425
|
}
|
|
328
426
|
/**
|
|
329
427
|
* Attempt to fetch a document directly by its Dokumentnummer using URL construction.
|
|
@@ -333,9 +431,11 @@ export function constructDocumentUrl(dokumentnummer) {
|
|
|
333
431
|
*
|
|
334
432
|
* @param dokumentnummer - The RIS document number (e.g., "NOR12019037")
|
|
335
433
|
* @param timeout - Request timeout in milliseconds
|
|
434
|
+
* @param signal - Caller's cancellation signal; a cancellation is thrown rather
|
|
435
|
+
* than reported as an unsuccessful fetch, so callers do not fall back to a search
|
|
336
436
|
* @returns Result object with HTML content on success, or error details on failure
|
|
337
437
|
*/
|
|
338
|
-
export async function getDocumentByNumber(dokumentnummer, timeout = DEFAULT_TIMEOUT) {
|
|
438
|
+
export async function getDocumentByNumber(dokumentnummer, timeout = DEFAULT_TIMEOUT, signal) {
|
|
339
439
|
// Validate dokumentnummer before any URL operations
|
|
340
440
|
if (!isValidDokumentnummer(dokumentnummer)) {
|
|
341
441
|
return {
|
|
@@ -351,10 +451,15 @@ export async function getDocumentByNumber(dokumentnummer, timeout = DEFAULT_TIME
|
|
|
351
451
|
};
|
|
352
452
|
}
|
|
353
453
|
try {
|
|
354
|
-
const html = await getDocumentContent(url, timeout);
|
|
454
|
+
const html = await getDocumentContent(url, timeout, signal);
|
|
355
455
|
return { success: true, html, url };
|
|
356
456
|
}
|
|
357
457
|
catch (e) {
|
|
458
|
+
// Reporting a cancellation as a failed direct fetch would send the caller
|
|
459
|
+
// into its fallback search — a second round-trip nobody is waiting for.
|
|
460
|
+
if (signal?.aborted) {
|
|
461
|
+
throw e;
|
|
462
|
+
}
|
|
358
463
|
if (e instanceof RISAPIError) {
|
|
359
464
|
return {
|
|
360
465
|
success: false,
|