jsonstat-io 0.1.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 (99) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +200 -0
  3. package/dist/arrow/index.cjs +60 -0
  4. package/dist/arrow/index.cjs.map +1 -0
  5. package/dist/arrow/index.d.cts +125 -0
  6. package/dist/arrow/index.d.ts +125 -0
  7. package/dist/arrow/index.js +3 -0
  8. package/dist/arrow/index.js.map +1 -0
  9. package/dist/arrowToCube-BdRWZ5oG.d.cts +29 -0
  10. package/dist/arrowToCube-Mjr79ZHM.d.ts +29 -0
  11. package/dist/chunk-2HY7NRKX.js +259 -0
  12. package/dist/chunk-2HY7NRKX.js.map +1 -0
  13. package/dist/chunk-62NIY7ML.js +191 -0
  14. package/dist/chunk-62NIY7ML.js.map +1 -0
  15. package/dist/chunk-AXUZF6O5.cjs +352 -0
  16. package/dist/chunk-AXUZF6O5.cjs.map +1 -0
  17. package/dist/chunk-EFRDFQOT.js +335 -0
  18. package/dist/chunk-EFRDFQOT.js.map +1 -0
  19. package/dist/chunk-GDKDYLZ4.cjs +263 -0
  20. package/dist/chunk-GDKDYLZ4.cjs.map +1 -0
  21. package/dist/chunk-IDTQZG4B.js +432 -0
  22. package/dist/chunk-IDTQZG4B.js.map +1 -0
  23. package/dist/chunk-KFIKGBIJ.cjs +200 -0
  24. package/dist/chunk-KFIKGBIJ.cjs.map +1 -0
  25. package/dist/chunk-L65CRXTV.js +64 -0
  26. package/dist/chunk-L65CRXTV.js.map +1 -0
  27. package/dist/chunk-PHLCWTHB.cjs +446 -0
  28. package/dist/chunk-PHLCWTHB.cjs.map +1 -0
  29. package/dist/chunk-VQCS4LVD.cjs +166 -0
  30. package/dist/chunk-VQCS4LVD.cjs.map +1 -0
  31. package/dist/chunk-VYS7DPB3.js +162 -0
  32. package/dist/chunk-VYS7DPB3.js.map +1 -0
  33. package/dist/chunk-ZEDTWRDN.cjs +70 -0
  34. package/dist/chunk-ZEDTWRDN.cjs.map +1 -0
  35. package/dist/cli/index.cjs +337 -0
  36. package/dist/cli/index.cjs.map +1 -0
  37. package/dist/cli/index.d.cts +90 -0
  38. package/dist/cli/index.d.ts +90 -0
  39. package/dist/cli/index.js +333 -0
  40. package/dist/cli/index.js.map +1 -0
  41. package/dist/cubeBuilder-BEGA4C5Y.js +4 -0
  42. package/dist/cubeBuilder-BEGA4C5Y.js.map +1 -0
  43. package/dist/cubeBuilder-ZU76VVEX.cjs +25 -0
  44. package/dist/cubeBuilder-ZU76VVEX.cjs.map +1 -0
  45. package/dist/cubeReader-7QVUF3TI.js +4 -0
  46. package/dist/cubeReader-7QVUF3TI.js.map +1 -0
  47. package/dist/cubeReader-UN2BHQWK.cjs +21 -0
  48. package/dist/cubeReader-UN2BHQWK.cjs.map +1 -0
  49. package/dist/index.cjs +172 -0
  50. package/dist/index.cjs.map +1 -0
  51. package/dist/index.d.cts +445 -0
  52. package/dist/index.d.ts +445 -0
  53. package/dist/index.js +7 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/ir-BArfNxo-.d.cts +143 -0
  56. package/dist/ir-Jae2ymov.d.ts +143 -0
  57. package/dist/jsonstat-BX4mBLci.d.cts +179 -0
  58. package/dist/jsonstat-BX4mBLci.d.ts +179 -0
  59. package/dist/sources/csv.cjs +40 -0
  60. package/dist/sources/csv.cjs.map +1 -0
  61. package/dist/sources/csv.d.cts +112 -0
  62. package/dist/sources/csv.d.ts +112 -0
  63. package/dist/sources/csv.js +3 -0
  64. package/dist/sources/csv.js.map +1 -0
  65. package/dist/sources/csvw.cjs +236 -0
  66. package/dist/sources/csvw.cjs.map +1 -0
  67. package/dist/sources/csvw.d.cts +143 -0
  68. package/dist/sources/csvw.d.ts +143 -0
  69. package/dist/sources/csvw.js +230 -0
  70. package/dist/sources/csvw.js.map +1 -0
  71. package/dist/sources/duckdb.cjs +121 -0
  72. package/dist/sources/duckdb.cjs.map +1 -0
  73. package/dist/sources/duckdb.d.cts +111 -0
  74. package/dist/sources/duckdb.d.ts +111 -0
  75. package/dist/sources/duckdb.js +115 -0
  76. package/dist/sources/duckdb.js.map +1 -0
  77. package/dist/sources/parquet.cjs +117 -0
  78. package/dist/sources/parquet.cjs.map +1 -0
  79. package/dist/sources/parquet.d.cts +83 -0
  80. package/dist/sources/parquet.d.ts +83 -0
  81. package/dist/sources/parquet.js +111 -0
  82. package/dist/sources/parquet.js.map +1 -0
  83. package/dist/sources/polars.cjs +94 -0
  84. package/dist/sources/polars.cjs.map +1 -0
  85. package/dist/sources/polars.d.cts +91 -0
  86. package/dist/sources/polars.d.ts +91 -0
  87. package/dist/sources/polars.js +87 -0
  88. package/dist/sources/polars.js.map +1 -0
  89. package/docs/api.md +322 -0
  90. package/docs/architecture.md +140 -0
  91. package/docs/cli.md +159 -0
  92. package/docs/formats/arrow.md +111 -0
  93. package/docs/formats/csv.md +115 -0
  94. package/docs/formats/csvw.md +152 -0
  95. package/docs/formats/duckdb.md +151 -0
  96. package/docs/formats/parquet.md +132 -0
  97. package/docs/formats/polars.md +111 -0
  98. package/docs/mapping.md +136 -0
  99. package/package.json +119 -0
@@ -0,0 +1,112 @@
1
+ import { R as RoleMap, O as Observations } from '../ir-Jae2ymov.js';
2
+ import { J as JsonStatDataset } from '../jsonstat-BX4mBLci.js';
3
+
4
+ /**
5
+ * CSV adapter — `jsonstat-io/csv`.
6
+ *
7
+ * **Import:** unlike Parquet/DuckDB/Polars (which funnel through the Arrow
8
+ * hub), plain CSV has no native Arrow producer in this package, so this adapter
9
+ * builds the [`Observations`](../model/ir.ts) IR **directly** from parsed rows.
10
+ *
11
+ * **Export:** [`cubeToCsv`] writes the [`Observations`] IR to RFC-4180-style
12
+ * CSV text (dimension columns + measure column + optional status column).
13
+ *
14
+ * ## Mapping heuristics (no metadata)
15
+ *
16
+ * Without a schema, the adapter infers:
17
+ * - The **measure** is the first column that parses as a number for every
18
+ * non-empty row (or the one named by `options.measure`).
19
+ * - Every other column is a **dimension** (string-typed), in file order.
20
+ * - A column named `status` (case-insensitive) is treated as the status
21
+ * column unless `options.status` overrides it.
22
+ *
23
+ * Empty cells become `null` in the measure and `""` in dimensions (then
24
+ * rejected by the builder; sources should pre-clean). For richer, lossless
25
+ * mapping with declared types/labels/roles, use CSVW (see [`csvw`](./csvw.ts)).
26
+ */
27
+
28
+ declare class CsvSourceError extends Error {
29
+ constructor(message: string);
30
+ }
31
+ interface CsvToCubeOptions {
32
+ /** Explicit measure column name. Overrides inference. */
33
+ measure?: string;
34
+ /** Explicit dimension column names, in order. */
35
+ dimensions?: string[];
36
+ /** Explicit status column name. Overrides the `status`-name heuristic. */
37
+ status?: string;
38
+ /** Role assignments. */
39
+ roles?: RoleMap;
40
+ /** Value-form hint. */
41
+ valueForm?: "auto" | "dense" | "sparse";
42
+ /** CSV delimiter (default ","). */
43
+ delimiter?: string;
44
+ /** Treat the first row as a header (default true). */
45
+ header?: boolean;
46
+ }
47
+ /**
48
+ * A tiny, dependency-free RFC-4180-ish CSV parser. Handles quoted fields,
49
+ * embedded quotes (doubled), and embedded newlines. Sufficient for the
50
+ * importer's needs; for heavy CSV work, prefer piping through DuckDB/Polars.
51
+ */
52
+ declare function parseCsv(input: string, delimiter?: string): string[][];
53
+ /**
54
+ * Parse CSV text into the [`Observations`] IR.
55
+ *
56
+ * @throws [`CsvSourceError`] on structural problems.
57
+ */
58
+ declare function csvToCube(text: string, options?: CsvToCubeOptions): Observations;
59
+ /** Convenience: CSV text → JSON-stat [`Dataset`]. */
60
+ declare function csvToDataset(text: string, options?: CsvToCubeOptions): Promise<JsonStatDataset>;
61
+ interface CubeToCsvOptions {
62
+ /** CSV delimiter (default ","). */
63
+ delimiter?: string;
64
+ /** Line terminator: "\r\n" (RFC-4180 default) or "\n". */
65
+ lineTerminator?: string;
66
+ }
67
+ /**
68
+ * Resolve the ordered list of (column name, row value extractor) pairs for a
69
+ * dataset IR. Shared by [`cubeToCsv`] and [`cubeToCsvw`] so the two writers
70
+ * agree on column layout.
71
+ *
72
+ * Column order: dimension IDs (in `model.dimensionIds` order) → measure
73
+ * (named `measure.name ?? "value"`) → status (`"status"`, if present).
74
+ *
75
+ * @internal
76
+ */
77
+ declare function cubeColumns(obs: Observations): {
78
+ header: string[];
79
+ rows: string[];
80
+ };
81
+ /**
82
+ * Quote a single CSV field per RFC-4180. A field needs quoting if it contains
83
+ * the delimiter, a double quote, a CR, or an LF. Embedded quotes are doubled.
84
+ */
85
+ declare function quoteCsvField(value: string, delimiter: string, force?: boolean): string;
86
+ /**
87
+ * Serialize a CSV header + rows (as produced by [`cubeColumns`]) into RFC-4180
88
+ * text. Extracted so CSV and CSVW writers share one escaping path.
89
+ *
90
+ * @internal
91
+ */
92
+ declare function serializeRows(header: string[], rows: string[], options?: CubeToCsvOptions): string;
93
+ /**
94
+ * Write the [`Observations`](../model/ir.ts) IR to RFC-4180-style CSV text.
95
+ *
96
+ * The output is a tidy long table: one header row + one row per observation.
97
+ * Columns are the dimension IDs (in `model.dimensionIds` order), then the
98
+ * measure column (`measure.name ?? "value"`), then an optional `status`
99
+ * column. Null measures are emitted as empty cells.
100
+ *
101
+ * This is the lossy inverse of [`csvToCube`]: labels, roles, and category
102
+ * metadata are not preserved (use [`cubeToCsvw`] for a lossless export).
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { cubeToCsv } from "jsonstat-io/csv";
107
+ * const csv = cubeToCsv(observations);
108
+ * ```
109
+ */
110
+ declare function cubeToCsv(obs: Observations, options?: CubeToCsvOptions): string;
111
+
112
+ export { CsvSourceError, type CsvToCubeOptions, type CubeToCsvOptions, csvToCube, csvToDataset, cubeColumns, cubeToCsv, parseCsv, quoteCsvField, serializeRows };
@@ -0,0 +1,3 @@
1
+ export { CsvSourceError, csvToCube, csvToDataset, cubeColumns, cubeToCsv, parseCsv, quoteCsvField, serializeRows } from '../chunk-62NIY7ML.js';
2
+ //# sourceMappingURL=csv.js.map
3
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"csv.js"}
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ var chunkKFIKGBIJ_cjs = require('../chunk-KFIKGBIJ.cjs');
4
+
5
+ // src/sources/csvw.ts
6
+ var CsvwSourceError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "CsvwSourceError";
10
+ }
11
+ };
12
+ function firstTitle(col) {
13
+ if (!col.titles) return void 0;
14
+ return Array.isArray(col.titles) ? col.titles[0] : col.titles;
15
+ }
16
+ function isNumericDatatype(dt) {
17
+ if (!dt) return false;
18
+ const base = typeof dt === "string" ? dt : dt.base;
19
+ return base === "decimal" || base === "integer" || base === "double" || base === "float" || base === "number" || base === "long" || base === "int" || base === "short" || base === "byte";
20
+ }
21
+ function roleFromPropertyUrl(url) {
22
+ if (!url) return void 0;
23
+ const u = url.toLowerCase();
24
+ if (u.includes("time") || u.includes("date")) return "time";
25
+ if (u.includes("geo") || u.includes("spatial") || u.includes("place")) return "geo";
26
+ if (u.includes("metric") || u.includes("measure") || u.includes("obsvalue")) return "metric";
27
+ return void 0;
28
+ }
29
+ function csvwToCube(csvText, metadata, options = {}) {
30
+ if (!metadata.tableSchema?.columns?.length) {
31
+ throw new CsvwSourceError("CSVW metadata is missing tableSchema.columns");
32
+ }
33
+ const delimiter = options.delimiter ?? ",";
34
+ const header = options.header ?? true;
35
+ const schema = metadata.tableSchema;
36
+ const rows = chunkKFIKGBIJ_cjs.parseCsv(csvText, delimiter);
37
+ if (rows.length === 0) throw new CsvwSourceError("CSV has no rows");
38
+ let dataRows;
39
+ if (header) {
40
+ dataRows = rows.slice(1);
41
+ } else {
42
+ dataRows = rows;
43
+ }
44
+ const cols = schema.columns.filter((c) => !c.virtual);
45
+ const colCount = cols.length;
46
+ if (dataRows.length === 0) throw new CsvwSourceError("CSV has no data rows");
47
+ const columnValues = cols.map(() => []);
48
+ for (const r of dataRows) {
49
+ for (let c = 0; c < colCount; c++) {
50
+ const cell = r[c] ?? cols[c].default ?? "";
51
+ columnValues[c].push(cell);
52
+ }
53
+ }
54
+ let measureIdx = cols.findIndex((c) => c.name === options.measure);
55
+ if (measureIdx === -1) {
56
+ measureIdx = cols.findIndex((c) => isNumericDatatype(c.datatype));
57
+ if (measureIdx === -1) {
58
+ throw new CsvwSourceError(
59
+ "No measure column: no column has a numeric datatype. Pass options.measure to name one."
60
+ );
61
+ }
62
+ }
63
+ let statusIdx = cols.findIndex((c) => c.name === options.status);
64
+ if (statusIdx === -1 && !options.status) {
65
+ statusIdx = cols.findIndex(
66
+ (c) => c.name.toLowerCase() === "status" || roleFromPropertyUrl(c.propertyUrl) === void 0 && c.name.toLowerCase() === "status"
67
+ );
68
+ if (statusIdx === measureIdx) statusIdx = -1;
69
+ }
70
+ let dimensionIdxs;
71
+ if (options.dimensions) {
72
+ dimensionIdxs = options.dimensions.map((n) => {
73
+ const idx = cols.findIndex((c) => c.name === n);
74
+ if (idx === -1) throw new CsvwSourceError(`Dimension column "${n}" not in schema`);
75
+ return idx;
76
+ });
77
+ } else {
78
+ const pk = schema.primaryKey ? Array.isArray(schema.primaryKey) ? schema.primaryKey : [schema.primaryKey] : [];
79
+ if (pk.length > 0) {
80
+ dimensionIdxs = pk.map((n) => {
81
+ const idx = cols.findIndex((c) => c.name === n);
82
+ if (idx === -1) throw new CsvwSourceError(`primaryKey column "${n}" not in schema`);
83
+ return idx;
84
+ });
85
+ for (let i = 0; i < cols.length; i++) {
86
+ if (i === measureIdx || i === statusIdx || dimensionIdxs.includes(i)) continue;
87
+ dimensionIdxs.push(i);
88
+ }
89
+ } else {
90
+ dimensionIdxs = cols.map((_, i) => i).filter((i) => i !== measureIdx && i !== statusIdx);
91
+ }
92
+ }
93
+ if (dimensionIdxs.length === 0) {
94
+ throw new CsvwSourceError("No dimension columns resolved");
95
+ }
96
+ const dimensions = {};
97
+ for (const idx of dimensionIdxs) {
98
+ const col = cols[idx];
99
+ const label = firstTitle(col);
100
+ dimensions[col.name] = {
101
+ id: col.name,
102
+ values: columnValues[idx].map((v) => v === "" ? "" : v),
103
+ label
104
+ };
105
+ }
106
+ const measure = {
107
+ name: cols[measureIdx].name,
108
+ values: columnValues[measureIdx].map((v) => {
109
+ if (v === "") return null;
110
+ const n = Number(v);
111
+ return Number.isFinite(n) ? n : null;
112
+ })
113
+ };
114
+ let status;
115
+ if (statusIdx !== -1) {
116
+ status = { values: columnValues[statusIdx].slice() };
117
+ }
118
+ const roles = {};
119
+ const addRole = (role, id) => {
120
+ (roles[role] ?? (roles[role] = [])).push(id);
121
+ };
122
+ if (metadata["jsonstat:roles"]) {
123
+ for (const r of ["time", "geo", "metric"]) {
124
+ for (const id of metadata["jsonstat:roles"][r] ?? []) addRole(r, id);
125
+ }
126
+ }
127
+ for (const idx of dimensionIdxs) {
128
+ const role = roleFromPropertyUrl(cols[idx].propertyUrl);
129
+ if (role) addRole(role, cols[idx].name);
130
+ }
131
+ if (options.roles) {
132
+ for (const r of ["time", "geo", "metric"]) {
133
+ for (const id of options.roles[r] ?? []) addRole(r, id);
134
+ }
135
+ }
136
+ const observations = {
137
+ dimensions,
138
+ measure,
139
+ status,
140
+ model: {
141
+ dimensionIds: dimensionIdxs.map((i) => cols[i].name),
142
+ roles: Object.keys(roles).length ? roles : void 0,
143
+ meta: {
144
+ label: metadata["dc:title"],
145
+ source: metadata["dc:source"],
146
+ updated: metadata["dcat:modified"],
147
+ extension: metadata["jsonstat:extension"]
148
+ },
149
+ valueForm: metadata["jsonstat:valueForm"] ?? "auto",
150
+ statusForm: status ? "auto" : "none"
151
+ }
152
+ };
153
+ return observations;
154
+ }
155
+ async function csvwToDataset(csvText, metadata, options) {
156
+ const { toDataset } = await import('../cubeBuilder-ZU76VVEX.cjs');
157
+ return toDataset(csvwToCube(csvText, metadata, options));
158
+ }
159
+ function parseCsvwMetadata(json) {
160
+ const m = json;
161
+ if (!m || !m.tableSchema || !Array.isArray(m.tableSchema.columns)) {
162
+ throw new CsvwSourceError("Invalid CSVW metadata: missing tableSchema.columns");
163
+ }
164
+ return m;
165
+ }
166
+ function propertyUrlForRole(role) {
167
+ if (role === "time") return "schema:DateTime";
168
+ if (role === "geo") return "schema:Place";
169
+ if (role === "metric") return "schema:Measure";
170
+ return void 0;
171
+ }
172
+ function roleOf(obs, id) {
173
+ const roles = obs.model.roles;
174
+ if (!roles) return void 0;
175
+ if (roles.time?.includes(id)) return "time";
176
+ if (roles.geo?.includes(id)) return "geo";
177
+ if (roles.metric?.includes(id)) return "metric";
178
+ return void 0;
179
+ }
180
+ function cubeToCsvw(obs, options = {}) {
181
+ const { header, rows } = chunkKFIKGBIJ_cjs.cubeColumns(obs);
182
+ const csv = chunkKFIKGBIJ_cjs.serializeRows(header, rows, options);
183
+ const dimIds = obs.model.dimensionIds;
184
+ const measureName = obs.measure.name ?? "value";
185
+ const hasStatus = obs.status !== void 0;
186
+ const meta = obs.model.meta;
187
+ const columns = [];
188
+ for (const id of dimIds) {
189
+ const col = obs.dimensions[id];
190
+ const role = roleOf(obs, id);
191
+ const propUrl = propertyUrlForRole(role);
192
+ columns.push({
193
+ name: id,
194
+ titles: col?.label ?? id,
195
+ datatype: "string",
196
+ ...propUrl ? { propertyUrl: propUrl } : {}
197
+ });
198
+ }
199
+ columns.push({
200
+ name: measureName,
201
+ titles: measureName,
202
+ datatype: "decimal",
203
+ propertyUrl: "schema:value",
204
+ required: false
205
+ });
206
+ if (hasStatus) {
207
+ columns.push({
208
+ name: "status",
209
+ titles: "status",
210
+ datatype: "string"
211
+ });
212
+ }
213
+ const metadata = {
214
+ url: options.url ?? "data.csv",
215
+ "@context": ["http://www.w3.org/ns/csvw", { "@language": "en" }],
216
+ tableSchema: {
217
+ columns,
218
+ primaryKey: dimIds.slice()
219
+ }
220
+ };
221
+ if (meta?.label) metadata["dc:title"] = meta.label;
222
+ if (meta?.source) metadata["dc:source"] = meta.source;
223
+ if (meta?.updated) metadata["dcat:modified"] = meta.updated;
224
+ if (meta?.extension) metadata["jsonstat:extension"] = meta.extension;
225
+ if (obs.model.roles) metadata["jsonstat:roles"] = obs.model.roles;
226
+ if (obs.model.valueForm) metadata["jsonstat:valueForm"] = obs.model.valueForm;
227
+ return { csv, metadata };
228
+ }
229
+
230
+ exports.CsvwSourceError = CsvwSourceError;
231
+ exports.csvwToCube = csvwToCube;
232
+ exports.csvwToDataset = csvwToDataset;
233
+ exports.cubeToCsvw = cubeToCsvw;
234
+ exports.parseCsvwMetadata = parseCsvwMetadata;
235
+ //# sourceMappingURL=csvw.cjs.map
236
+ //# sourceMappingURL=csvw.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sources/csvw.ts"],"names":["parseCsv","cubeColumns","serializeRows"],"mappings":"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","file":"csvw.cjs","sourcesContent":["/**\n * CSVW (CSV on the Web) adapter — `jsonstat-io/csvw`.\n *\n * **Import:** CSVW pairs a CSV file with a JSON-LD metadata document that\n * declares a `tableSchema` — column names, types, `titles` (human labels),\n * `primaryKey`, and property URLs. This metadata enables a **lossless** mapping\n * straight to the [`Observations`](../model/ir.ts) IR, without the inference\n * heuristics of plain [`csv`](./csv.ts).\n *\n * **Export:** [`cubeToCsvw`] generates both the CSV text and a CSVW metadata\n * document from the [`Observations`] IR.\n *\n * ## Mapping\n *\n * - Columns whose `datatype` is numeric → measure candidates.\n * - The measure is the first numeric column, or the one flagged via\n * `options.measure`. Other columns are dimensions.\n * - `tableSchema.primaryKey` columns are treated as dimensions (and can pin\n * the dimension order via `options.dimensions`).\n * - `titles` → dimension/category labels; `name` → dimension id.\n * - `primaryKey` → dimension id list (used as `dimensionIds` when\n * `options.dimensions` is not given).\n * - `propertyUrl` containing `time`/`geo`/`metric` → role hints.\n *\n * The adapter does not implement the full CSVW spec (foreign keys, URL\n * templates, cell value transformations); it covers the subset needed to\n * faithfully round-trip statistical cubes. See [docs/formats/csvw.md](../../docs/formats/csvw.md).\n */\n\nimport type {\n DimensionColumn,\n MeasureColumn,\n Observations,\n RoleMap,\n StatusColumn,\n} from \"../model/ir\";\nimport type { JsonStatDataset } from \"../model/jsonstat\";\nimport { parseCsv, cubeColumns, serializeRows } from \"./csv\";\n\n// ---------------------------------------------------------------------------\n// Errors & CSVW metadata types\n// ---------------------------------------------------------------------------\n\nexport class CsvwSourceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CsvwSourceError\";\n }\n}\n\n/** A column declaration in a CSVW `tableSchema`. */\nexport interface CsvwColumn {\n name: string;\n titles?: string | string[];\n datatype?: string | { base: string };\n propertyUrl?: string;\n /** CSVW virtual columns are not backed by a CSV cell. */\n virtual?: boolean;\n /** Default value for missing cells. */\n default?: string;\n /** Whether null is allowed. */\n required?: boolean;\n}\n\n/** The `tableSchema` object from a CSVW metadata document. */\nexport interface CsvwTableSchema {\n columns: CsvwColumn[];\n primaryKey?: string | string[];\n /** Foreign keys are parsed but not enforced. */\n foreignKeys?: unknown[];\n}\n\n/** A trimmed CSVW metadata document (only the fields we use). */\nexport interface CsvwMetadata {\n url?: string;\n \"@context\"?: string | unknown[];\n \"dc:title\"?: string;\n \"dc:source\"?: string;\n \"dcat:modified\"?: string;\n tableSchema: CsvwTableSchema;\n /**\n * Extension object: if present, attached to the dataset as `extension`.\n * CSVW doesn't standardize this; consumers may add `jsonstat:*` keys here.\n */\n \"jsonstat:extension\"?: Record<string, unknown>;\n \"jsonstat:roles\"?: RoleMap;\n \"jsonstat:valueForm\"?: \"auto\" | \"dense\" | \"sparse\";\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface CsvwToCubeOptions {\n /** Explicit measure column name. Overrides schema-based detection. */\n measure?: string;\n /** Explicit dimension column names, in order. */\n dimensions?: string[];\n /** Explicit status column name. */\n status?: string;\n /** Role assignments (merged with, and overriding, metadata hints). */\n roles?: RoleMap;\n /** CSV delimiter (default \",\"). */\n delimiter?: string;\n /** Treat the CSV's first row as a header (default true). */\n header?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction firstTitle(col: CsvwColumn): string | undefined {\n if (!col.titles) return undefined;\n return Array.isArray(col.titles) ? col.titles[0] : col.titles;\n}\n\n/** Is a CSVW datatype numeric? */\nfunction isNumericDatatype(dt: string | { base: string } | undefined): boolean {\n if (!dt) return false;\n const base = typeof dt === \"string\" ? dt : dt.base;\n return (\n base === \"decimal\" ||\n base === \"integer\" ||\n base === \"double\" ||\n base === \"float\" ||\n base === \"number\" ||\n base === \"long\" ||\n base === \"int\" ||\n base === \"short\" ||\n base === \"byte\"\n );\n}\n\n/** Infer a role from a CSVW propertyUrl. */\nfunction roleFromPropertyUrl(url: string | undefined): \"time\" | \"geo\" | \"metric\" | undefined {\n if (!url) return undefined;\n const u = url.toLowerCase();\n if (u.includes(\"time\") || u.includes(\"date\")) return \"time\";\n if (u.includes(\"geo\") || u.includes(\"spatial\") || u.includes(\"place\")) return \"geo\";\n if (u.includes(\"metric\") || u.includes(\"measure\") || u.includes(\"obsvalue\")) return \"metric\";\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Convert CSV text + CSVW metadata into the [`Observations`] IR.\n *\n * @param csvText The raw CSV file content.\n * @param metadata The parsed CSVW metadata document (the `tableSchema` is\n * required).\n * @throws [`CsvwSourceError`] on structural problems.\n */\nexport function csvwToCube(\n csvText: string,\n metadata: CsvwMetadata,\n options: CsvwToCubeOptions = {},\n): Observations {\n if (!metadata.tableSchema?.columns?.length) {\n throw new CsvwSourceError(\"CSVW metadata is missing tableSchema.columns\");\n }\n\n const delimiter = options.delimiter ?? \",\";\n const header = options.header ?? true;\n const schema = metadata.tableSchema;\n\n // The CSVW column `name` is the canonical header. If the CSV has its own\n // header row, we expect it to match the `name`s (or `titles`); if not, we\n // rely on column position. To be robust, we parse by position and label by\n // the schema's `name`.\n const rows = parseCsv(csvText, delimiter);\n if (rows.length === 0) throw new CsvwSourceError(\"CSV has no rows\");\n\n let dataRows: string[][];\n if (header) {\n dataRows = rows.slice(1);\n } else {\n dataRows = rows;\n }\n const cols = schema.columns.filter((c) => !c.virtual);\n const colCount = cols.length;\n if (dataRows.length === 0) throw new CsvwSourceError(\"CSV has no data rows\");\n\n // Read each CSVW column into a string array (by position).\n const columnValues: string[][] = cols.map(() => []);\n for (const r of dataRows) {\n for (let c = 0; c < colCount; c++) {\n const cell = r[c] ?? cols[c].default ?? \"\";\n columnValues[c].push(cell);\n }\n }\n\n // Resolve measure: explicit > first numeric-datatype column.\n let measureIdx = cols.findIndex((c) => c.name === options.measure);\n if (measureIdx === -1) {\n measureIdx = cols.findIndex((c) => isNumericDatatype(c.datatype));\n if (measureIdx === -1) {\n throw new CsvwSourceError(\n \"No measure column: no column has a numeric datatype. \" +\n \"Pass options.measure to name one.\",\n );\n }\n }\n\n // Resolve status: explicit > a column named/propertyUrl'd 'status'.\n let statusIdx = cols.findIndex((c) => c.name === options.status);\n if (statusIdx === -1 && !options.status) {\n statusIdx = cols.findIndex(\n (c) => c.name.toLowerCase() === \"status\" || roleFromPropertyUrl(c.propertyUrl) === undefined && c.name.toLowerCase() === \"status\",\n );\n if (statusIdx === measureIdx) statusIdx = -1;\n }\n\n // Resolve dimensions.\n let dimensionIdxs: number[];\n if (options.dimensions) {\n dimensionIdxs = options.dimensions.map((n) => {\n const idx = cols.findIndex((c) => c.name === n);\n if (idx === -1) throw new CsvwSourceError(`Dimension column \"${n}\" not in schema`);\n return idx;\n });\n } else {\n // Prefer primaryKey columns as dimensions, in declared order; fall back to\n // all non-measure/non-status columns.\n const pk = schema.primaryKey\n ? Array.isArray(schema.primaryKey)\n ? schema.primaryKey\n : [schema.primaryKey]\n : [];\n if (pk.length > 0) {\n dimensionIdxs = pk.map((n) => {\n const idx = cols.findIndex((c) => c.name === n);\n if (idx === -1) throw new CsvwSourceError(`primaryKey column \"${n}\" not in schema`);\n return idx;\n });\n // Append any remaining non-measure/non-status columns not in the PK.\n for (let i = 0; i < cols.length; i++) {\n if (i === measureIdx || i === statusIdx || dimensionIdxs.includes(i)) continue;\n dimensionIdxs.push(i);\n }\n } else {\n dimensionIdxs = cols\n .map((_, i) => i)\n .filter((i) => i !== measureIdx && i !== statusIdx);\n }\n }\n if (dimensionIdxs.length === 0) {\n throw new CsvwSourceError(\"No dimension columns resolved\");\n }\n\n // Build dimension columns, pulling labels from `titles`.\n const dimensions: Record<string, DimensionColumn> = {};\n for (const idx of dimensionIdxs) {\n const col = cols[idx];\n const label = firstTitle(col);\n dimensions[col.name] = {\n id: col.name,\n values: columnValues[idx].map((v) => (v === \"\" ? \"\" : v)),\n label,\n };\n }\n\n // Measure: parse numerically per the datatype.\n const measure: MeasureColumn = {\n name: cols[measureIdx].name,\n values: columnValues[measureIdx].map((v) => {\n if (v === \"\") return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n }),\n };\n\n let status: StatusColumn | undefined;\n if (statusIdx !== -1) {\n status = { values: columnValues[statusIdx].slice() };\n }\n\n // Roles: metadata hints + propertyUrl inference + caller overrides.\n const roles: RoleMap = {};\n const addRole = (role: keyof RoleMap, id: string) => {\n (roles[role] ??= []).push(id);\n };\n if (metadata[\"jsonstat:roles\"]) {\n for (const r of [\"time\", \"geo\", \"metric\"] as const) {\n for (const id of metadata[\"jsonstat:roles\"][r] ?? []) addRole(r, id);\n }\n }\n for (const idx of dimensionIdxs) {\n const role = roleFromPropertyUrl(cols[idx].propertyUrl);\n if (role) addRole(role, cols[idx].name);\n }\n // The measure, if it has a metric propertyUrl, records the metric role on\n // itself (it is not a dimension, so this is informational).\n if (options.roles) {\n for (const r of [\"time\", \"geo\", \"metric\"] as const) {\n for (const id of options.roles[r] ?? []) addRole(r, id);\n }\n }\n\n const observations: Observations = {\n dimensions,\n measure,\n status,\n model: {\n dimensionIds: dimensionIdxs.map((i) => cols[i].name),\n roles: Object.keys(roles).length ? roles : undefined,\n meta: {\n label: metadata[\"dc:title\"],\n source: metadata[\"dc:source\"],\n updated: metadata[\"dcat:modified\"],\n extension: metadata[\"jsonstat:extension\"],\n },\n valueForm: metadata[\"jsonstat:valueForm\"] ?? \"auto\",\n statusForm: status ? \"auto\" : \"none\",\n },\n };\n\n return observations;\n}\n\n/** Convenience: CSVW (CSV + metadata) → JSON-stat [`Dataset`]. */\nexport async function csvwToDataset(\n csvText: string,\n metadata: CsvwMetadata,\n options?: CsvwToCubeOptions,\n): Promise<JsonStatDataset> {\n const { toDataset } = await import(\"../core/cubeBuilder\");\n return toDataset(csvwToCube(csvText, metadata, options));\n}\n\n/** Parse a CSVW metadata JSON document, validating minimally. */\nexport function parseCsvwMetadata(json: unknown): CsvwMetadata {\n const m = json as CsvwMetadata;\n if (!m || !m.tableSchema || !Array.isArray(m.tableSchema.columns)) {\n throw new CsvwSourceError(\"Invalid CSVW metadata: missing tableSchema.columns\");\n }\n return m;\n}\n\n// ---------------------------------------------------------------------------\n// Export: Observations IR → CSVW (CSV text + metadata JSON)\n// ---------------------------------------------------------------------------\n\nexport interface CubeToCsvwOptions {\n /** CSV delimiter (default \",\"). */\n delimiter?: string;\n /** Line terminator: \"\\r\\n\" (default) or \"\\n\". */\n lineTerminator?: string;\n /**\n * The `url` of the CSV file the metadata describes. Required by the CSVW\n * spec for a stand-alone metadata document; falls back to `\"data.csv\"`.\n */\n url?: string;\n}\n\n/** Result of [`cubeToCsvw`]: the CSV body + the CSVW metadata document. */\nexport interface CsvwExport {\n /** The CSV text (header + rows). */\n csv: string;\n /** The CSVW metadata document, ready to `JSON.stringify`. */\n metadata: CsvwMetadata;\n}\n\n/** JSON-stat role → CSVW `propertyUrl` template fragment. */\nfunction propertyUrlForRole(\n role: \"time\" | \"geo\" | \"metric\" | undefined,\n): string | undefined {\n if (role === \"time\") return \"schema:DateTime\";\n if (role === \"geo\") return \"schema:Place\";\n if (role === \"metric\") return \"schema:Measure\";\n return undefined;\n}\n\n/** Look up the role of a dimension id in the IR model. */\nfunction roleOf(\n obs: Observations,\n id: string,\n): \"time\" | \"geo\" | \"metric\" | undefined {\n const roles = obs.model.roles;\n if (!roles) return undefined;\n if (roles.time?.includes(id)) return \"time\";\n if (roles.geo?.includes(id)) return \"geo\";\n if (roles.metric?.includes(id)) return \"metric\";\n return undefined;\n}\n\n/**\n * Write the [`Observations`](../model/ir.ts) IR to a CSVW pair: the CSV text\n * (RFC-4180) and a CSVW metadata document declaring the `tableSchema`.\n *\n * The metadata records, per column:\n * - `name` (the dimension id / measure name / `\"status\"`),\n * - `titles` (the dimension label, when known),\n * - `datatype` (`string` for dimensions/status, `decimal` for the measure),\n * - `propertyUrl` (a role-derived schema.org URL, when a role is assigned).\n *\n * The schema's `primaryKey` is set to all dimension columns — together they\n * uniquely identify an observation. Dataset-level metadata (`label`, `source`,\n * `updated`, `extension`, `roles`, `valueForm`) is emitted as the CSVW\n * `dc:title` / `dc:source` / `dcat:modified` / `jsonstat:*` keys, so the\n * round-trip through [`csvwToCube`] is lossless.\n *\n * @example\n * ```ts\n * import { cubeToCsvw } from \"jsonstat-io/csvw\";\n * const { csv, metadata } = cubeToCsvw(observations);\n * ```\n */\nexport function cubeToCsvw(\n obs: Observations,\n options: CubeToCsvwOptions = {},\n): CsvwExport {\n const { header, rows } = cubeColumns(obs);\n const csv = serializeRows(header, rows, options);\n\n const dimIds = obs.model.dimensionIds;\n const measureName = obs.measure.name ?? \"value\";\n const hasStatus = obs.status !== undefined;\n const meta = obs.model.meta;\n\n const columns: CsvwColumn[] = [];\n\n for (const id of dimIds) {\n const col = obs.dimensions[id];\n const role = roleOf(obs, id);\n const propUrl = propertyUrlForRole(role);\n columns.push({\n name: id,\n titles: col?.label ?? id,\n datatype: \"string\",\n ...(propUrl ? { propertyUrl: propUrl } : {}),\n });\n }\n\n // Measure column: numeric datatype.\n columns.push({\n name: measureName,\n titles: measureName,\n datatype: \"decimal\",\n propertyUrl: \"schema:value\",\n required: false,\n });\n\n if (hasStatus) {\n columns.push({\n name: \"status\",\n titles: \"status\",\n datatype: \"string\",\n });\n }\n\n const metadata: CsvwMetadata = {\n url: options.url ?? \"data.csv\",\n \"@context\": [\"http://www.w3.org/ns/csvw\", { \"@language\": \"en\" }],\n tableSchema: {\n columns,\n primaryKey: dimIds.slice(),\n },\n };\n\n if (meta?.label) metadata[\"dc:title\"] = meta.label;\n if (meta?.source) metadata[\"dc:source\"] = meta.source;\n if (meta?.updated) metadata[\"dcat:modified\"] = meta.updated;\n if (meta?.extension) metadata[\"jsonstat:extension\"] = meta.extension;\n if (obs.model.roles) metadata[\"jsonstat:roles\"] = obs.model.roles;\n if (obs.model.valueForm) metadata[\"jsonstat:valueForm\"] = obs.model.valueForm;\n\n return { csv, metadata };\n}\n"]}
@@ -0,0 +1,143 @@
1
+ import { R as RoleMap, O as Observations } from '../ir-BArfNxo-.cjs';
2
+ import { J as JsonStatDataset } from '../jsonstat-BX4mBLci.cjs';
3
+
4
+ /**
5
+ * CSVW (CSV on the Web) adapter — `jsonstat-io/csvw`.
6
+ *
7
+ * **Import:** CSVW pairs a CSV file with a JSON-LD metadata document that
8
+ * declares a `tableSchema` — column names, types, `titles` (human labels),
9
+ * `primaryKey`, and property URLs. This metadata enables a **lossless** mapping
10
+ * straight to the [`Observations`](../model/ir.ts) IR, without the inference
11
+ * heuristics of plain [`csv`](./csv.ts).
12
+ *
13
+ * **Export:** [`cubeToCsvw`] generates both the CSV text and a CSVW metadata
14
+ * document from the [`Observations`] IR.
15
+ *
16
+ * ## Mapping
17
+ *
18
+ * - Columns whose `datatype` is numeric → measure candidates.
19
+ * - The measure is the first numeric column, or the one flagged via
20
+ * `options.measure`. Other columns are dimensions.
21
+ * - `tableSchema.primaryKey` columns are treated as dimensions (and can pin
22
+ * the dimension order via `options.dimensions`).
23
+ * - `titles` → dimension/category labels; `name` → dimension id.
24
+ * - `primaryKey` → dimension id list (used as `dimensionIds` when
25
+ * `options.dimensions` is not given).
26
+ * - `propertyUrl` containing `time`/`geo`/`metric` → role hints.
27
+ *
28
+ * The adapter does not implement the full CSVW spec (foreign keys, URL
29
+ * templates, cell value transformations); it covers the subset needed to
30
+ * faithfully round-trip statistical cubes. See [docs/formats/csvw.md](../../docs/formats/csvw.md).
31
+ */
32
+
33
+ declare class CsvwSourceError extends Error {
34
+ constructor(message: string);
35
+ }
36
+ /** A column declaration in a CSVW `tableSchema`. */
37
+ interface CsvwColumn {
38
+ name: string;
39
+ titles?: string | string[];
40
+ datatype?: string | {
41
+ base: string;
42
+ };
43
+ propertyUrl?: string;
44
+ /** CSVW virtual columns are not backed by a CSV cell. */
45
+ virtual?: boolean;
46
+ /** Default value for missing cells. */
47
+ default?: string;
48
+ /** Whether null is allowed. */
49
+ required?: boolean;
50
+ }
51
+ /** The `tableSchema` object from a CSVW metadata document. */
52
+ interface CsvwTableSchema {
53
+ columns: CsvwColumn[];
54
+ primaryKey?: string | string[];
55
+ /** Foreign keys are parsed but not enforced. */
56
+ foreignKeys?: unknown[];
57
+ }
58
+ /** A trimmed CSVW metadata document (only the fields we use). */
59
+ interface CsvwMetadata {
60
+ url?: string;
61
+ "@context"?: string | unknown[];
62
+ "dc:title"?: string;
63
+ "dc:source"?: string;
64
+ "dcat:modified"?: string;
65
+ tableSchema: CsvwTableSchema;
66
+ /**
67
+ * Extension object: if present, attached to the dataset as `extension`.
68
+ * CSVW doesn't standardize this; consumers may add `jsonstat:*` keys here.
69
+ */
70
+ "jsonstat:extension"?: Record<string, unknown>;
71
+ "jsonstat:roles"?: RoleMap;
72
+ "jsonstat:valueForm"?: "auto" | "dense" | "sparse";
73
+ }
74
+ interface CsvwToCubeOptions {
75
+ /** Explicit measure column name. Overrides schema-based detection. */
76
+ measure?: string;
77
+ /** Explicit dimension column names, in order. */
78
+ dimensions?: string[];
79
+ /** Explicit status column name. */
80
+ status?: string;
81
+ /** Role assignments (merged with, and overriding, metadata hints). */
82
+ roles?: RoleMap;
83
+ /** CSV delimiter (default ","). */
84
+ delimiter?: string;
85
+ /** Treat the CSV's first row as a header (default true). */
86
+ header?: boolean;
87
+ }
88
+ /**
89
+ * Convert CSV text + CSVW metadata into the [`Observations`] IR.
90
+ *
91
+ * @param csvText The raw CSV file content.
92
+ * @param metadata The parsed CSVW metadata document (the `tableSchema` is
93
+ * required).
94
+ * @throws [`CsvwSourceError`] on structural problems.
95
+ */
96
+ declare function csvwToCube(csvText: string, metadata: CsvwMetadata, options?: CsvwToCubeOptions): Observations;
97
+ /** Convenience: CSVW (CSV + metadata) → JSON-stat [`Dataset`]. */
98
+ declare function csvwToDataset(csvText: string, metadata: CsvwMetadata, options?: CsvwToCubeOptions): Promise<JsonStatDataset>;
99
+ /** Parse a CSVW metadata JSON document, validating minimally. */
100
+ declare function parseCsvwMetadata(json: unknown): CsvwMetadata;
101
+ interface CubeToCsvwOptions {
102
+ /** CSV delimiter (default ","). */
103
+ delimiter?: string;
104
+ /** Line terminator: "\r\n" (default) or "\n". */
105
+ lineTerminator?: string;
106
+ /**
107
+ * The `url` of the CSV file the metadata describes. Required by the CSVW
108
+ * spec for a stand-alone metadata document; falls back to `"data.csv"`.
109
+ */
110
+ url?: string;
111
+ }
112
+ /** Result of [`cubeToCsvw`]: the CSV body + the CSVW metadata document. */
113
+ interface CsvwExport {
114
+ /** The CSV text (header + rows). */
115
+ csv: string;
116
+ /** The CSVW metadata document, ready to `JSON.stringify`. */
117
+ metadata: CsvwMetadata;
118
+ }
119
+ /**
120
+ * Write the [`Observations`](../model/ir.ts) IR to a CSVW pair: the CSV text
121
+ * (RFC-4180) and a CSVW metadata document declaring the `tableSchema`.
122
+ *
123
+ * The metadata records, per column:
124
+ * - `name` (the dimension id / measure name / `"status"`),
125
+ * - `titles` (the dimension label, when known),
126
+ * - `datatype` (`string` for dimensions/status, `decimal` for the measure),
127
+ * - `propertyUrl` (a role-derived schema.org URL, when a role is assigned).
128
+ *
129
+ * The schema's `primaryKey` is set to all dimension columns — together they
130
+ * uniquely identify an observation. Dataset-level metadata (`label`, `source`,
131
+ * `updated`, `extension`, `roles`, `valueForm`) is emitted as the CSVW
132
+ * `dc:title` / `dc:source` / `dcat:modified` / `jsonstat:*` keys, so the
133
+ * round-trip through [`csvwToCube`] is lossless.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * import { cubeToCsvw } from "jsonstat-io/csvw";
138
+ * const { csv, metadata } = cubeToCsvw(observations);
139
+ * ```
140
+ */
141
+ declare function cubeToCsvw(obs: Observations, options?: CubeToCsvwOptions): CsvwExport;
142
+
143
+ export { type CsvwColumn, type CsvwExport, type CsvwMetadata, CsvwSourceError, type CsvwTableSchema, type CsvwToCubeOptions, type CubeToCsvwOptions, csvwToCube, csvwToDataset, cubeToCsvw, parseCsvwMetadata };