@cyanheads/eurostat-mcp-server 0.4.0 → 0.6.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.
Files changed (52) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +31 -9
  3. package/dist/config/server-config.d.ts +2 -0
  4. package/dist/config/server-config.d.ts.map +1 -1
  5. package/dist/config/server-config.js +14 -0
  6. package/dist/config/server-config.js.map +1 -1
  7. package/dist/index.js +6 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +2 -2
  10. package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -1
  11. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +2 -2
  12. package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -1
  13. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +93 -0
  14. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -0
  15. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +362 -0
  16. package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -0
  17. package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js +2 -2
  18. package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js.map +1 -1
  19. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js +2 -2
  20. package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +6 -2
  22. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +28 -20
  24. package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
  25. package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js +3 -3
  26. package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js.map +1 -1
  27. package/dist/services/canvas-accessor.d.ts +5 -0
  28. package/dist/services/canvas-accessor.d.ts.map +1 -1
  29. package/dist/services/canvas-accessor.js +7 -0
  30. package/dist/services/canvas-accessor.js.map +1 -1
  31. package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts +89 -0
  32. package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts.map +1 -0
  33. package/dist/services/eurostat-bulk/eurostat-bulk-service.js +502 -0
  34. package/dist/services/eurostat-bulk/eurostat-bulk-service.js.map +1 -0
  35. package/dist/services/eurostat-bulk/types.d.ts +72 -0
  36. package/dist/services/eurostat-bulk/types.d.ts.map +1 -0
  37. package/dist/services/eurostat-bulk/types.js +9 -0
  38. package/dist/services/eurostat-bulk/types.js.map +1 -0
  39. package/dist/services/eurostat-codelists.d.ts +45 -0
  40. package/dist/services/eurostat-codelists.d.ts.map +1 -0
  41. package/dist/services/eurostat-codelists.js +92 -0
  42. package/dist/services/eurostat-codelists.js.map +1 -0
  43. package/dist/services/eurostat-data/eurostat-data-service.d.ts +46 -5
  44. package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
  45. package/dist/services/eurostat-data/eurostat-data-service.js +85 -14
  46. package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
  47. package/dist/services/eurostat-data/types.d.ts +16 -8
  48. package/dist/services/eurostat-data/types.d.ts.map +1 -1
  49. package/dist/services/eurostat-data/types.js +0 -4
  50. package/dist/services/eurostat-data/types.js.map +1 -1
  51. package/package.json +1 -1
  52. package/server.json +34 -6
@@ -0,0 +1,502 @@
1
+ /**
2
+ * @fileoverview Eurostat Bulk Service — streaming client for the SDMX 2.1 TSV
3
+ * dissemination endpoint. Builds the positional dimension key, sniffs gzip off
4
+ * the stream, enforces a byte budget mid-transfer, classifies XML SOAP faults
5
+ * and the asynchronous queue envelope, and expands the wide TSV layout into
6
+ * one row per observation.
7
+ * @module services/eurostat-bulk/eurostat-bulk-service
8
+ */
9
+ import { notFound, serviceUnavailable, validationError, } from '@cyanheads/mcp-ts-core/errors';
10
+ import { fetchWithTimeout } from '@cyanheads/mcp-ts-core/utils';
11
+ import { getServerConfig } from '../../config/server-config.js';
12
+ import { CONF_STATUS_COLUMN, CONF_STATUS_LABEL_COLUMN, CONF_STATUS_LABELS, OBS_FLAG_COLUMN, OBS_FLAG_LABEL_COLUMN, OBS_FLAG_LABELS, OBS_VALUE_COLUMN, } from '../../services/eurostat-codelists.js';
13
+ import { TIME_COLUMN, } from './types.js';
14
+ // Context satisfies the runtime contract of RequestContext but lacks the index signature
15
+ // required by fetchWithTimeout.
16
+ const asReqCtx = (ctx) => ctx;
17
+ /**
18
+ * Decoded characters buffered before an XML body is classified.
19
+ *
20
+ * Both non-data shapes are small — the largest SOAP fault observed is under 400
21
+ * bytes and the queue envelope under 350 — so this only has to hold one of them.
22
+ * It deliberately does **not** bound the search for the TSV header's line break:
23
+ * that line carries one field per period and runs to 166 KB on a daily series.
24
+ */
25
+ const XML_PEEK_CHARS = 8_192;
26
+ /** Separator between the observation flag and the confidentiality flag inside a TSV cell. */
27
+ const CONF_SEPARATOR = '@';
28
+ /** Eurostat's marker for an observation whose value is not available. */
29
+ const MISSING_VALUE = ':';
30
+ /** The header line's first field ends with this literal before the period columns begin. */
31
+ const TIME_HEADER_SUFFIX = '\\TIME_PERIOD';
32
+ /** Minimal XML entity decoding — the five predefined entities SOAP fault strings use. */
33
+ function decodeEntities(text) {
34
+ return text
35
+ .replaceAll('&lt;', '<')
36
+ .replaceAll('&gt;', '>')
37
+ .replaceAll('&quot;', '"')
38
+ .replaceAll('&apos;', "'")
39
+ .replaceAll('&amp;', '&');
40
+ }
41
+ /**
42
+ * Translate an XML body from the SDMX endpoint into the declared error contract.
43
+ *
44
+ * Two unrelated shapes arrive as XML and neither is data. A `S:Fault` envelope
45
+ * accompanies a 4xx and names the problem in a numeric `faultcode`. The
46
+ * `syncResponse` envelope arrives on **HTTP 200** carrying a queue ticket
47
+ * instead of the dataset — parsed as TSV it yields a header row of XML, so it is
48
+ * detected here rather than downstream.
49
+ *
50
+ * Always throws.
51
+ */
52
+ export function classifyXmlBody(xml, datasetCode) {
53
+ if (xml.includes('syncResponse') || xml.includes('<status>SUBMITTED</status>')) {
54
+ throw serviceUnavailable(`Eurostat queued this extraction instead of returning data: the SDMX endpoint answered with an asynchronous "SUBMITTED" ticket for "${datasetCode}". The queued result is not retrievable through this server. Narrow the request with dimension filters or a period range so Eurostat serves it synchronously.`, { reason: 'async_queued', datasetCode, retryable: false });
55
+ }
56
+ const code = /<faultcode>\s*(\d+)\s*<\/faultcode>/.exec(xml)?.[1];
57
+ const detail = decodeEntities(/<faultstring>([\s\S]*?)<\/faultstring>/.exec(xml)?.[1]?.trim() ?? xml.slice(0, 300));
58
+ if (code === '100') {
59
+ throw notFound(`Dataset "${datasetCode}" is not available for dissemination. Eurostat fault ${code}: ${detail}`, { reason: 'not_found', datasetCode, faultcode: code });
60
+ }
61
+ if (code === '140') {
62
+ throw validationError(`Eurostat rejected the dimension key: the number of filter positions did not match the dataset's dimensions. Eurostat fault ${code}: ${detail}`, { reason: 'filter_arity', datasetCode, faultcode: code });
63
+ }
64
+ if (code === '150') {
65
+ throw validationError(`Eurostat rejected a filter value. This fault also covers a period range outside the dataset's coverage. Eurostat fault ${code}: ${detail}`, { reason: 'invalid_dimension', datasetCode, faultcode: code });
66
+ }
67
+ throw serviceUnavailable(`Eurostat SDMX endpoint returned a fault for "${datasetCode}"${code ? ` (faultcode ${code})` : ''}: ${detail}`, { reason: 'upstream_fault', datasetCode, ...(code && { faultcode: code }) });
68
+ }
69
+ /**
70
+ * Split one wide-TSV cell into its value and flags.
71
+ *
72
+ * The grammar, read off live responses, is `<value>` `SPACE` `<flags>`, where
73
+ * flags is `<obs_flag>` optionally followed by `@<conf_status>` and either part
74
+ * may be empty: `429634.3 `, `3853937.0 p`, `: `, `: @C`. A cell that is empty
75
+ * on both sides of the split is not an observation at all and yields
76
+ * `undefined` — distinct from `: `, which is an observation Eurostat reports as
77
+ * unavailable.
78
+ */
79
+ export function parseCell(cell) {
80
+ const sep = cell.indexOf(' ');
81
+ const rawValue = (sep === -1 ? cell : cell.slice(0, sep)).trim();
82
+ const rawFlags = (sep === -1 ? '' : cell.slice(sep + 1)).trim();
83
+ if (rawValue === '' && rawFlags === '')
84
+ return;
85
+ const atIdx = rawFlags.indexOf(CONF_SEPARATOR);
86
+ const flag = (atIdx === -1 ? rawFlags : rawFlags.slice(0, atIdx)) || null;
87
+ const conf = (atIdx === -1 ? '' : rawFlags.slice(atIdx + 1)) || null;
88
+ if (rawValue === '' || rawValue === MISSING_VALUE)
89
+ return { conf, flag, value: null };
90
+ const num = Number(rawValue);
91
+ return { conf, flag, value: Number.isFinite(num) ? num : null };
92
+ }
93
+ /**
94
+ * Parse the header line of a wide TSV body.
95
+ *
96
+ * The first tab-separated field is the comma-joined dimension list with
97
+ * `\TIME_PERIOD` appended to the last name; the remaining fields are the period
98
+ * codes, each padded with a trailing space Eurostat does not strip.
99
+ */
100
+ export function parseHeader(line) {
101
+ const fields = line.split('\t');
102
+ const keyField = (fields[0] ?? '').trim();
103
+ const dimensionField = keyField.endsWith(TIME_HEADER_SUFFIX)
104
+ ? keyField.slice(0, -TIME_HEADER_SUFFIX.length)
105
+ : keyField;
106
+ const dimensions = dimensionField
107
+ .split(',')
108
+ .map((d) => d.trim())
109
+ .filter((d) => d !== '');
110
+ const periods = fields.slice(1).map((p) => p.trim());
111
+ if (dimensions.length === 0 || periods.length === 0) {
112
+ throw serviceUnavailable(`Eurostat returned a TSV body whose header names no ${dimensions.length === 0 ? 'dimensions' : 'periods'}. Header: ${line.slice(0, 200)}`, { reason: 'upstream_fault' });
113
+ }
114
+ return { dimensions, periods };
115
+ }
116
+ /**
117
+ * Build the positional dimension key the SDMX path takes.
118
+ *
119
+ * The key is a dot-separated segment carrying one position per dimension **in
120
+ * dataset order excluding time**; empty means wildcard and `+` means OR. The
121
+ * count must match exactly — a key with one position too few or too many is
122
+ * rejected upstream with faultcode 140 — so positions are emitted for every
123
+ * dimension in `dimensionOrder`, filtered or not, and never only for the
124
+ * dimensions the caller named.
125
+ *
126
+ * Returns `undefined` when no filter applies. An all-wildcard key is accepted
127
+ * by Eurostat, but omitting the segment asks the same question without staking
128
+ * the request on this server's copy of the dimension order being current.
129
+ */
130
+ export function buildKeyPath(dimensionOrder, filters) {
131
+ const applied = Object.entries(filters).filter(([, values]) => values.length > 0);
132
+ if (applied.length === 0)
133
+ return;
134
+ const unknown = applied.map(([dim]) => dim).filter((dim) => !dimensionOrder.includes(dim));
135
+ if (unknown.length > 0) {
136
+ throw validationError(`Filter names ${unknown.map((d) => `"${d}"`).join(', ')}, which ${unknown.length === 1 ? 'is not a dimension' : 'are not dimensions'} of this dataset. Filterable dimensions, in key order: ${dimensionOrder.join(', ')}. The time dimension is filtered with since_period/until_period instead.`, { reason: 'invalid_dimension', unknownDimensions: unknown, dimensionOrder });
137
+ }
138
+ return dimensionOrder.map((dim) => (filters[dim] ?? []).join('+')).join('.');
139
+ }
140
+ /**
141
+ * Canvas column schema for {@link BulkRow}, declared rather than sniffed.
142
+ *
143
+ * Inference reads only the leading rows, which types an all-integer measure
144
+ * prefix as `BIGINT` — serialized back out as a string — and types an
145
+ * all-missing prefix from nulls alone. Every column is nullable: a measure is
146
+ * routinely unavailable, and flags are the exception rather than the rule.
147
+ */
148
+ export function bulkRowSchema(dimensions) {
149
+ return [
150
+ ...dimensions.map((dim) => ({ name: dim, type: 'VARCHAR', nullable: true })),
151
+ { name: TIME_COLUMN, type: 'VARCHAR', nullable: true },
152
+ { name: OBS_VALUE_COLUMN, type: 'DOUBLE', nullable: true },
153
+ { name: OBS_FLAG_COLUMN, type: 'VARCHAR', nullable: true },
154
+ { name: OBS_FLAG_LABEL_COLUMN, type: 'VARCHAR', nullable: true },
155
+ { name: CONF_STATUS_COLUMN, type: 'VARCHAR', nullable: true },
156
+ { name: CONF_STATUS_LABEL_COLUMN, type: 'VARCHAR', nullable: true },
157
+ ];
158
+ }
159
+ export class EurostatBulkService {
160
+ // config and storage accepted to match the standard service init pattern;
161
+ // this service uses only the Eurostat public API and per-request config.
162
+ // biome-ignore lint/complexity/noUselessConstructor: standard init pattern
163
+ constructor(_config, _storage) { }
164
+ buildUrl(datasetCode, keyPath, sinceP, untilP) {
165
+ const { baseUrl } = getServerConfig();
166
+ const path = keyPath
167
+ ? `${encodeURIComponent(datasetCode)}/${encodeURIComponent(keyPath)}`
168
+ : encodeURIComponent(datasetCode);
169
+ const url = new URL(`${baseUrl}/sdmx/2.1/data/${path}`);
170
+ url.searchParams.set('format', 'TSV');
171
+ if (sinceP)
172
+ url.searchParams.set('startPeriod', sinceP);
173
+ if (untilP)
174
+ url.searchParams.set('endPeriod', untilP);
175
+ return url;
176
+ }
177
+ /**
178
+ * Start a bulk download: issue the request, classify the response, parse the
179
+ * header, and hand back a generator over the body.
180
+ *
181
+ * The request is made once with no retry. A bulk body streams for up to
182
+ * `EUROSTAT_BULK_TIMEOUT_MS`, so re-running it on a transient failure costs
183
+ * the caller another full transfer for a request that is already the most
184
+ * expensive one this server makes; the failure is surfaced instead.
185
+ */
186
+ async startDownload(datasetCode, dimensionOrder, filters, sinceP, untilP, ctx) {
187
+ const { bulkTimeoutMs, bulkMaxBytes } = getServerConfig();
188
+ const keyPath = buildKeyPath(dimensionOrder, filters);
189
+ const url = this.buildUrl(datasetCode, keyPath, sinceP, untilP);
190
+ ctx.log.info('Starting SDMX bulk download', {
191
+ datasetCode,
192
+ keyPath,
193
+ sinceP,
194
+ untilP,
195
+ maxBytes: bulkMaxBytes,
196
+ });
197
+ let response;
198
+ try {
199
+ response = await fetchWithTimeout(url.toString(), bulkTimeoutMs, asReqCtx(ctx), {
200
+ signal: ctx.signal,
201
+ // 400 (bad filter value or arity) and 404 (unknown dataset) are modeled outcomes
202
+ // reclassified below into the declared error contract, not service failures.
203
+ expectedStatuses: [400, 404],
204
+ });
205
+ }
206
+ catch (err) {
207
+ // fetchWithTimeout throws for any non-2xx before the caller sees the body, so the
208
+ // SOAP fault is only reachable through the captured excerpt on err.data. Faults run
209
+ // well under the framework's 500-byte cap, so the excerpt carries the whole envelope.
210
+ const body = err.data;
211
+ if (typeof body?.body === 'string' && body.body.includes('<')) {
212
+ classifyXmlBody(body.body, datasetCode);
213
+ }
214
+ throw err;
215
+ }
216
+ if (!response.body) {
217
+ throw serviceUnavailable(`Eurostat SDMX endpoint returned an empty body for "${datasetCode}".`, { reason: 'upstream_fault', datasetCode });
218
+ }
219
+ const stats = {
220
+ bytesRead: 0,
221
+ budgetExceeded: false,
222
+ compressed: false,
223
+ rowCount: 0,
224
+ missingCount: 0,
225
+ periodsSeen: [],
226
+ };
227
+ const decoded = await decodeBody(response.body, stats, bulkMaxBytes);
228
+ const { header, remainder, iterator } = await readHeader(decoded, datasetCode);
229
+ return {
230
+ header,
231
+ stats,
232
+ url: url.toString(),
233
+ rows: () => expandRows(iterator, remainder, header, stats),
234
+ };
235
+ }
236
+ }
237
+ /**
238
+ * Wrap the response body in a byte-budgeted, gzip-aware text stream.
239
+ *
240
+ * Eurostat compresses large bodies **without** a `Content-Encoding` header — the
241
+ * only header-level tell is a `.tsv.gz` filename on `Content-Disposition`, and
242
+ * that is not what is trusted here. The first two bytes are read off the stream
243
+ * and checked for the gzip magic number, so the decision is made on what
244
+ * actually arrived. The switch is not monotonic in dataset size, so it cannot
245
+ * be predicted from the request either.
246
+ *
247
+ * The budget counts bytes *after* decompression. That is the uniform measure
248
+ * across both encodings, and since a gzip body is never larger than what it
249
+ * expands to, bounding the decoded size bounds the transfer as well. Hitting it
250
+ * cancels the underlying reader rather than draining the rest — the body is
251
+ * chunked with no `Content-Length`, so a budget checked after the fact would
252
+ * have already paid for the whole transfer.
253
+ */
254
+ async function decodeBody(body, stats, maxBytes) {
255
+ const reader = body.getReader();
256
+ // Pull until at least the two magic bytes are in hand; a chunked body can deliver
257
+ // fewer on the first read, and an empty body delivers none.
258
+ const prefix = [];
259
+ let prefixBytes = 0;
260
+ while (prefixBytes < 2) {
261
+ const { done, value } = await reader.read();
262
+ if (done)
263
+ break;
264
+ if (value && value.length > 0) {
265
+ prefix.push(value);
266
+ prefixBytes += value.length;
267
+ }
268
+ }
269
+ const head = concatChunks(prefix, prefixBytes);
270
+ stats.compressed = head.length >= 2 && head[0] === 0x1f && head[1] === 0x8b;
271
+ const replayed = new ReadableStream({
272
+ start(controller) {
273
+ if (head.length > 0)
274
+ controller.enqueue(head);
275
+ },
276
+ async pull(controller) {
277
+ const { done, value } = await reader.read();
278
+ if (done)
279
+ controller.close();
280
+ else
281
+ controller.enqueue(value);
282
+ },
283
+ cancel(reason) {
284
+ return reader.cancel(reason);
285
+ },
286
+ });
287
+ /**
288
+ * `DecompressionStream`'s writable side is typed `BufferSource`, wider than the
289
+ * `Uint8Array` chunks this pipeline carries, so `pipeThrough` will not accept it
290
+ * directly. The cast narrows the transform to the stream types actually in play
291
+ * rather than widening the pipeline to `BufferSource`.
292
+ */
293
+ const byteStream = stats.compressed
294
+ ? replayed.pipeThrough(new DecompressionStream('gzip'))
295
+ : replayed;
296
+ return budgetedText(byteStream, stats, maxBytes);
297
+ }
298
+ function concatChunks(chunks, total) {
299
+ if (chunks.length === 1)
300
+ return chunks[0];
301
+ const out = new Uint8Array(total);
302
+ let offset = 0;
303
+ for (const chunk of chunks) {
304
+ out.set(chunk, offset);
305
+ offset += chunk.length;
306
+ }
307
+ return out;
308
+ }
309
+ /**
310
+ * Decode a byte stream to text, stopping the moment the byte budget is spent.
311
+ *
312
+ * The generator ends cleanly on overspend rather than throwing: the caller has
313
+ * already paid for everything downloaded so far, and discarding it would leave
314
+ * the agent with an error where it could have had a prefix of the dataset plus
315
+ * `budgetExceeded` telling it exactly what happened.
316
+ *
317
+ * Every exit cancels the reader, so nothing further is transferred: overspend,
318
+ * a normal end, and abandonment alike. Abandonment is the case that needs it —
319
+ * a staging failure or an abort part-way through the canvas append runs the
320
+ * `finally`, and releasing the lock alone would leave the response body
321
+ * undrained and its connection held. Cancelling a stream that already ended is
322
+ * a no-op.
323
+ */
324
+ async function* budgetedText(stream, stats, maxBytes) {
325
+ const reader = stream.getReader();
326
+ const decoder = new TextDecoder('utf-8');
327
+ try {
328
+ while (true) {
329
+ const { done, value } = await reader.read();
330
+ if (done)
331
+ break;
332
+ if (!value || value.length === 0)
333
+ continue;
334
+ stats.bytesRead += value.length;
335
+ if (stats.bytesRead > maxBytes) {
336
+ stats.budgetExceeded = true;
337
+ return;
338
+ }
339
+ const text = decoder.decode(value, { stream: true });
340
+ if (text)
341
+ yield text;
342
+ }
343
+ const tail = decoder.decode();
344
+ if (tail)
345
+ yield tail;
346
+ }
347
+ finally {
348
+ await reader.cancel().catch(() => undefined);
349
+ reader.releaseLock();
350
+ }
351
+ }
352
+ /**
353
+ * Consume enough of the text stream to tell data from XML, then take the header.
354
+ *
355
+ * Both non-data shapes announce themselves in the first character, so
356
+ * classification happens before a single row is parsed — a `syncResponse`
357
+ * envelope arrives on HTTP 200 and would otherwise be read as a TSV header of
358
+ * XML followed by no rows, which looks like a successful empty download.
359
+ *
360
+ * A body identified as XML is then buffered to the peek window before it is
361
+ * classified, rather than to the first line break. The XML declaration ends in a
362
+ * newline, so stopping there would hand the classifier the declaration alone and
363
+ * lose the `syncResponse` and `faultcode` markers that follow it.
364
+ *
365
+ * A TSV header, by contrast, is buffered to its line break with no length bound.
366
+ * Its length is proportional to the dataset's period count — 166 KB and 13,852
367
+ * fields on a daily series — so capping the search would parse a prefix of it as
368
+ * the whole header, silently drop every period column past the cap, and read the
369
+ * header's own tail as a data line. The byte budget is what bounds this loop.
370
+ */
371
+ async function readHeader(chunks, datasetCode) {
372
+ let buf = '';
373
+ let exhausted = false;
374
+ const pull = async () => {
375
+ const next = await chunks.next();
376
+ if (next.done)
377
+ exhausted = true;
378
+ else
379
+ buf += next.value;
380
+ };
381
+ while (buf.trim() === '' && !exhausted)
382
+ await pull();
383
+ if (buf.trimStart().startsWith('<')) {
384
+ while (buf.length < XML_PEEK_CHARS && !exhausted)
385
+ await pull();
386
+ classifyXmlBody(buf, datasetCode);
387
+ }
388
+ while (!buf.includes('\n') && !exhausted)
389
+ await pull();
390
+ const newline = buf.indexOf('\n');
391
+ if (newline === -1) {
392
+ if (buf.trim() === '') {
393
+ throw serviceUnavailable(`Eurostat SDMX endpoint returned an empty body for "${datasetCode}".`, { reason: 'upstream_fault', datasetCode });
394
+ }
395
+ // A body that ended without a line break: one header line and no data rows.
396
+ return { header: parseHeader(stripCr(buf)), iterator: chunks, remainder: '' };
397
+ }
398
+ return {
399
+ header: parseHeader(stripCr(buf.slice(0, newline))),
400
+ iterator: chunks,
401
+ remainder: buf.slice(newline + 1),
402
+ };
403
+ }
404
+ function stripCr(line) {
405
+ return line.endsWith('\r') ? line.slice(0, -1) : line;
406
+ }
407
+ /**
408
+ * Expand the wide TSV body into one row per populated cell.
409
+ *
410
+ * Each data line carries a comma-joined dimension key and one cell per period
411
+ * column, so a line becomes as many rows as it has populated cells. Cells that
412
+ * are blank on both sides of the value/flag split carry no observation and are
413
+ * skipped; `:` is kept, because Eurostat reporting a value as unavailable is
414
+ * itself data.
415
+ *
416
+ * Only complete lines are parsed. When the byte budget cancels the transfer
417
+ * mid-line the trailing fragment is dropped rather than parsed into a row with
418
+ * silently truncated fields.
419
+ */
420
+ async function* expandRows(chunks, remainder, header, stats) {
421
+ const seen = new Set();
422
+ let buf = remainder;
423
+ const emit = function* (line) {
424
+ const trimmed = stripCr(line);
425
+ if (trimmed === '')
426
+ return;
427
+ const fields = trimmed.split('\t');
428
+ const keyParts = (fields[0] ?? '').split(',');
429
+ const cellCount = Math.min(fields.length - 1, header.periods.length);
430
+ for (let i = 0; i < cellCount; i++) {
431
+ const parsed = parseCell(fields[i + 1] ?? '');
432
+ if (!parsed)
433
+ continue;
434
+ const period = header.periods[i] ?? '';
435
+ const row = {};
436
+ for (let d = 0; d < header.dimensions.length; d++) {
437
+ row[header.dimensions[d]] = keyParts[d]?.trim() ?? null;
438
+ }
439
+ row[TIME_COLUMN] = period;
440
+ row[OBS_VALUE_COLUMN] = parsed.value;
441
+ row[OBS_FLAG_COLUMN] = parsed.flag;
442
+ row[OBS_FLAG_LABEL_COLUMN] = parsed.flag ? (OBS_FLAG_LABELS[parsed.flag] ?? null) : null;
443
+ row[CONF_STATUS_COLUMN] = parsed.conf;
444
+ row[CONF_STATUS_LABEL_COLUMN] = parsed.conf
445
+ ? (CONF_STATUS_LABELS[parsed.conf] ?? null)
446
+ : null;
447
+ stats.rowCount++;
448
+ if (parsed.value === null)
449
+ stats.missingCount++;
450
+ seen.add(period);
451
+ yield row;
452
+ }
453
+ };
454
+ /**
455
+ * Drain every complete line sitting in the buffer. Called before the read loop
456
+ * as well as inside it: a small body arrives whole while the header is being
457
+ * read, leaving every data line already buffered and no further chunk to
458
+ * trigger a split.
459
+ */
460
+ const flush = function* () {
461
+ let newline = buf.indexOf('\n');
462
+ while (newline !== -1) {
463
+ yield* emit(buf.slice(0, newline));
464
+ buf = buf.slice(newline + 1);
465
+ newline = buf.indexOf('\n');
466
+ }
467
+ };
468
+ try {
469
+ yield* flush();
470
+ for await (const chunk of chunks) {
471
+ buf += chunk;
472
+ yield* flush();
473
+ }
474
+ // The final line of a complete body has no trailing newline. A budget-cancelled
475
+ // body's trailing fragment is not a final line — it is a line cut in half.
476
+ if (!stats.budgetExceeded)
477
+ yield* emit(buf);
478
+ stats.periodsSeen = [...seen].sort();
479
+ }
480
+ finally {
481
+ /**
482
+ * Close the text stream on every exit. A consumer that stops early — a canvas
483
+ * append that throws, an abort — abandons this generator, and a small body
484
+ * whose rows were all buffered while the header was read is abandoned before
485
+ * the `for await` above ever starts it. Neither path would otherwise reach the
486
+ * cancel that frees the response body.
487
+ */
488
+ await chunks.return(undefined);
489
+ }
490
+ }
491
+ // --- Init/accessor pattern ---
492
+ let _service;
493
+ export function initEurostatBulkService(config, storage) {
494
+ _service = new EurostatBulkService(config, storage);
495
+ }
496
+ export function getEurostatBulkService() {
497
+ if (!_service) {
498
+ throw new Error('EurostatBulkService not initialized — call initEurostatBulkService() in setup()');
499
+ }
500
+ return _service;
501
+ }
502
+ //# sourceMappingURL=eurostat-bulk-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eurostat-bulk-service.js","sourceRoot":"","sources":["../../../src/services/eurostat-bulk/eurostat-bulk-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @fileoverview Domain types for the Eurostat SDMX 2.1 bulk (TSV) service. The
3
+ * flag dictionaries and measure column names live in `services/eurostat-codelists`,
4
+ * shared with the JSON-stat service.
5
+ * @module services/eurostat-bulk/types
6
+ */
7
+ /**
8
+ * One observation flattened from the wide TSV layout into a dataframe row.
9
+ *
10
+ * The TSV endpoint carries codes only — no labels — so a row has one column per
11
+ * dimension holding the code, `time` for the period, then the measure columns.
12
+ * There is deliberately no `<dim>_label` companion: staging all-null label
13
+ * columns would claim a lookup the bulk endpoint never performed.
14
+ */
15
+ export type BulkRow = Record<string, string | number | null>;
16
+ /** Period column name. `time` matches the dimension name JSON-stat uses. */
17
+ export declare const TIME_COLUMN = "time";
18
+ /**
19
+ * The header of a wide TSV body, parsed.
20
+ *
21
+ * Read from the response itself rather than from dataset metadata: the first
22
+ * field of the header line names the dimensions in the order the row keys use
23
+ * (`freq,unit,na_item,geo\TIME_PERIOD`), and the remaining tab-separated fields
24
+ * are the period columns. Both arrive with padding Eurostat does not strip.
25
+ */
26
+ export interface TsvHeader {
27
+ /** Dimension codes in row-key order, `time` excluded. */
28
+ dimensions: string[];
29
+ /** Period codes, in column order. */
30
+ periods: string[];
31
+ }
32
+ /**
33
+ * Counters filled in as {@link BulkDownload.rows} is drained.
34
+ *
35
+ * Nothing here is knowable before the stream is consumed — the body is chunked
36
+ * with no `Content-Length` — so a consumer reads these only after the generator
37
+ * has been exhausted (or has stopped on the byte budget).
38
+ */
39
+ export interface BulkStats {
40
+ /**
41
+ * True when the byte budget stopped the transfer before the body ended, so
42
+ * the rows are a prefix of the dataset rather than all of it.
43
+ */
44
+ budgetExceeded: boolean;
45
+ /** Decoded TSV bytes read. The unit the byte budget is enforced in. */
46
+ bytesRead: number;
47
+ /** Whether the response body arrived gzip-compressed. */
48
+ compressed: boolean;
49
+ /** Emitted observations carrying no numeric value. */
50
+ missingCount: number;
51
+ /** Distinct periods actually emitted, ascending. */
52
+ periodsSeen: string[];
53
+ /** Observations emitted — one per populated cell. */
54
+ rowCount: number;
55
+ }
56
+ /**
57
+ * A started bulk download: the header is parsed, the body is not yet drained.
58
+ *
59
+ * Returning before the body is consumed is what lets the caller declare a canvas
60
+ * column schema from {@link header} and then hand {@link rows} straight to the
61
+ * canvas appender, so a multi-million-row download never materializes as an
62
+ * array. {@link stats} is mutated as the generator runs and is only meaningful
63
+ * once it has finished.
64
+ */
65
+ export interface BulkDownload {
66
+ header: TsvHeader;
67
+ rows: () => AsyncGenerator<BulkRow>;
68
+ stats: BulkStats;
69
+ /** The request URL, minus nothing — no credentials are involved. */
70
+ url: string;
71
+ }
72
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/eurostat-bulk/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7D,4EAA4E;AAC5E,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;CACb"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @fileoverview Domain types for the Eurostat SDMX 2.1 bulk (TSV) service. The
3
+ * flag dictionaries and measure column names live in `services/eurostat-codelists`,
4
+ * shared with the JSON-stat service.
5
+ * @module services/eurostat-bulk/types
6
+ */
7
+ /** Period column name. `time` matches the dimension name JSON-stat uses. */
8
+ export const TIME_COLUMN = 'time';
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/eurostat-bulk/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fileoverview Eurostat's published `OBS_FLAG` and `CONF_STATUS` codelists, plus
3
+ * the measure column names both dataframe stagers write. Shared by the JSON-stat
4
+ * and SDMX TSV services so a given observation stages identically whichever
5
+ * endpoint it came from.
6
+ * @module services/eurostat-codelists
7
+ */
8
+ /**
9
+ * Measure column names — the non-dimension columns both stagers write.
10
+ *
11
+ * The `obs_` prefix is Eurostat's own SDMX-CSV vocabulary, which also keeps the
12
+ * measure columns clear of any dimension code. Both services declare their canvas
13
+ * schema from these constants, so a table staged from JSON-stat and one staged
14
+ * from the SDMX TSV endpoint carry the same four flag columns under the same
15
+ * names, and a join across them compares like with like.
16
+ */
17
+ export declare const OBS_VALUE_COLUMN = "obs_value";
18
+ export declare const OBS_FLAG_COLUMN = "obs_flag";
19
+ export declare const OBS_FLAG_LABEL_COLUMN = "obs_flag_label";
20
+ export declare const CONF_STATUS_COLUMN = "conf_status";
21
+ export declare const CONF_STATUS_LABEL_COLUMN = "conf_status_label";
22
+ /**
23
+ * Eurostat's `OBS_FLAG` codelist, verbatim from
24
+ * `sdmx/2.1/codelist/ESTAT/OBS_FLAG` — all 42 codes.
25
+ *
26
+ * Codes are composite: a cell flagged `bdep` is one code meaning "break in time
27
+ * series, definition differs, estimated, provisional", not four codes to be
28
+ * decomposed. No code contains a `|` or an `@`, which is what lets both wire
29
+ * formats use those characters to separate an observation flag from a
30
+ * confidentiality status. Lookups that miss return `undefined`, and the caller
31
+ * reports the code unlabelled rather than inventing a label — Eurostat can ship a
32
+ * code in data that its published codelist omits.
33
+ */
34
+ export declare const OBS_FLAG_LABELS: Readonly<Record<string, string>>;
35
+ /**
36
+ * Eurostat's `CONF_STATUS` codelist, verbatim from
37
+ * `sdmx/2.1/codelist/ESTAT/CONF_STATUS` — all three codes.
38
+ *
39
+ * `C` is the code that appears in data, behind the `@` of a TSV cell like `: @C`
40
+ * and behind the `|` of a JSON-stat status like `|C`. Both wire formats are split
41
+ * on that separator before staging, so the code lands in `conf_status` on either
42
+ * path and never inside `obs_flag`.
43
+ */
44
+ export declare const CONF_STATUS_LABELS: Readonly<Record<string, string>>;
45
+ //# sourceMappingURL=eurostat-codelists.d.ts.map