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.
- package/LICENSE +201 -0
- package/README.md +200 -0
- package/dist/arrow/index.cjs +60 -0
- package/dist/arrow/index.cjs.map +1 -0
- package/dist/arrow/index.d.cts +125 -0
- package/dist/arrow/index.d.ts +125 -0
- package/dist/arrow/index.js +3 -0
- package/dist/arrow/index.js.map +1 -0
- package/dist/arrowToCube-BdRWZ5oG.d.cts +29 -0
- package/dist/arrowToCube-Mjr79ZHM.d.ts +29 -0
- package/dist/chunk-2HY7NRKX.js +259 -0
- package/dist/chunk-2HY7NRKX.js.map +1 -0
- package/dist/chunk-62NIY7ML.js +191 -0
- package/dist/chunk-62NIY7ML.js.map +1 -0
- package/dist/chunk-AXUZF6O5.cjs +352 -0
- package/dist/chunk-AXUZF6O5.cjs.map +1 -0
- package/dist/chunk-EFRDFQOT.js +335 -0
- package/dist/chunk-EFRDFQOT.js.map +1 -0
- package/dist/chunk-GDKDYLZ4.cjs +263 -0
- package/dist/chunk-GDKDYLZ4.cjs.map +1 -0
- package/dist/chunk-IDTQZG4B.js +432 -0
- package/dist/chunk-IDTQZG4B.js.map +1 -0
- package/dist/chunk-KFIKGBIJ.cjs +200 -0
- package/dist/chunk-KFIKGBIJ.cjs.map +1 -0
- package/dist/chunk-L65CRXTV.js +64 -0
- package/dist/chunk-L65CRXTV.js.map +1 -0
- package/dist/chunk-PHLCWTHB.cjs +446 -0
- package/dist/chunk-PHLCWTHB.cjs.map +1 -0
- package/dist/chunk-VQCS4LVD.cjs +166 -0
- package/dist/chunk-VQCS4LVD.cjs.map +1 -0
- package/dist/chunk-VYS7DPB3.js +162 -0
- package/dist/chunk-VYS7DPB3.js.map +1 -0
- package/dist/chunk-ZEDTWRDN.cjs +70 -0
- package/dist/chunk-ZEDTWRDN.cjs.map +1 -0
- package/dist/cli/index.cjs +337 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +90 -0
- package/dist/cli/index.d.ts +90 -0
- package/dist/cli/index.js +333 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cubeBuilder-BEGA4C5Y.js +4 -0
- package/dist/cubeBuilder-BEGA4C5Y.js.map +1 -0
- package/dist/cubeBuilder-ZU76VVEX.cjs +25 -0
- package/dist/cubeBuilder-ZU76VVEX.cjs.map +1 -0
- package/dist/cubeReader-7QVUF3TI.js +4 -0
- package/dist/cubeReader-7QVUF3TI.js.map +1 -0
- package/dist/cubeReader-UN2BHQWK.cjs +21 -0
- package/dist/cubeReader-UN2BHQWK.cjs.map +1 -0
- package/dist/index.cjs +172 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +445 -0
- package/dist/index.d.ts +445 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/ir-BArfNxo-.d.cts +143 -0
- package/dist/ir-Jae2ymov.d.ts +143 -0
- package/dist/jsonstat-BX4mBLci.d.cts +179 -0
- package/dist/jsonstat-BX4mBLci.d.ts +179 -0
- package/dist/sources/csv.cjs +40 -0
- package/dist/sources/csv.cjs.map +1 -0
- package/dist/sources/csv.d.cts +112 -0
- package/dist/sources/csv.d.ts +112 -0
- package/dist/sources/csv.js +3 -0
- package/dist/sources/csv.js.map +1 -0
- package/dist/sources/csvw.cjs +236 -0
- package/dist/sources/csvw.cjs.map +1 -0
- package/dist/sources/csvw.d.cts +143 -0
- package/dist/sources/csvw.d.ts +143 -0
- package/dist/sources/csvw.js +230 -0
- package/dist/sources/csvw.js.map +1 -0
- package/dist/sources/duckdb.cjs +121 -0
- package/dist/sources/duckdb.cjs.map +1 -0
- package/dist/sources/duckdb.d.cts +111 -0
- package/dist/sources/duckdb.d.ts +111 -0
- package/dist/sources/duckdb.js +115 -0
- package/dist/sources/duckdb.js.map +1 -0
- package/dist/sources/parquet.cjs +117 -0
- package/dist/sources/parquet.cjs.map +1 -0
- package/dist/sources/parquet.d.cts +83 -0
- package/dist/sources/parquet.d.ts +83 -0
- package/dist/sources/parquet.js +111 -0
- package/dist/sources/parquet.js.map +1 -0
- package/dist/sources/polars.cjs +94 -0
- package/dist/sources/polars.cjs.map +1 -0
- package/dist/sources/polars.d.cts +91 -0
- package/dist/sources/polars.d.ts +91 -0
- package/dist/sources/polars.js +87 -0
- package/dist/sources/polars.js.map +1 -0
- package/docs/api.md +322 -0
- package/docs/architecture.md +140 -0
- package/docs/cli.md +159 -0
- package/docs/formats/arrow.md +111 -0
- package/docs/formats/csv.md +115 -0
- package/docs/formats/csvw.md +152 -0
- package/docs/formats/duckdb.md +151 -0
- package/docs/formats/parquet.md +132 -0
- package/docs/formats/polars.md +111 -0
- package/docs/mapping.md +136 -0
- package/package.json +119 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { R as RoleMap, O as Observations } from '../ir-Jae2ymov.js';
|
|
2
|
+
import { J as JsonStatDataset } from '../jsonstat-BX4mBLci.js';
|
|
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 };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { parseCsv, cubeColumns, serializeRows } from '../chunk-62NIY7ML.js';
|
|
2
|
+
|
|
3
|
+
// src/sources/csvw.ts
|
|
4
|
+
var CsvwSourceError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "CsvwSourceError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function firstTitle(col) {
|
|
11
|
+
if (!col.titles) return void 0;
|
|
12
|
+
return Array.isArray(col.titles) ? col.titles[0] : col.titles;
|
|
13
|
+
}
|
|
14
|
+
function isNumericDatatype(dt) {
|
|
15
|
+
if (!dt) return false;
|
|
16
|
+
const base = typeof dt === "string" ? dt : dt.base;
|
|
17
|
+
return base === "decimal" || base === "integer" || base === "double" || base === "float" || base === "number" || base === "long" || base === "int" || base === "short" || base === "byte";
|
|
18
|
+
}
|
|
19
|
+
function roleFromPropertyUrl(url) {
|
|
20
|
+
if (!url) return void 0;
|
|
21
|
+
const u = url.toLowerCase();
|
|
22
|
+
if (u.includes("time") || u.includes("date")) return "time";
|
|
23
|
+
if (u.includes("geo") || u.includes("spatial") || u.includes("place")) return "geo";
|
|
24
|
+
if (u.includes("metric") || u.includes("measure") || u.includes("obsvalue")) return "metric";
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
function csvwToCube(csvText, metadata, options = {}) {
|
|
28
|
+
if (!metadata.tableSchema?.columns?.length) {
|
|
29
|
+
throw new CsvwSourceError("CSVW metadata is missing tableSchema.columns");
|
|
30
|
+
}
|
|
31
|
+
const delimiter = options.delimiter ?? ",";
|
|
32
|
+
const header = options.header ?? true;
|
|
33
|
+
const schema = metadata.tableSchema;
|
|
34
|
+
const rows = parseCsv(csvText, delimiter);
|
|
35
|
+
if (rows.length === 0) throw new CsvwSourceError("CSV has no rows");
|
|
36
|
+
let dataRows;
|
|
37
|
+
if (header) {
|
|
38
|
+
dataRows = rows.slice(1);
|
|
39
|
+
} else {
|
|
40
|
+
dataRows = rows;
|
|
41
|
+
}
|
|
42
|
+
const cols = schema.columns.filter((c) => !c.virtual);
|
|
43
|
+
const colCount = cols.length;
|
|
44
|
+
if (dataRows.length === 0) throw new CsvwSourceError("CSV has no data rows");
|
|
45
|
+
const columnValues = cols.map(() => []);
|
|
46
|
+
for (const r of dataRows) {
|
|
47
|
+
for (let c = 0; c < colCount; c++) {
|
|
48
|
+
const cell = r[c] ?? cols[c].default ?? "";
|
|
49
|
+
columnValues[c].push(cell);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let measureIdx = cols.findIndex((c) => c.name === options.measure);
|
|
53
|
+
if (measureIdx === -1) {
|
|
54
|
+
measureIdx = cols.findIndex((c) => isNumericDatatype(c.datatype));
|
|
55
|
+
if (measureIdx === -1) {
|
|
56
|
+
throw new CsvwSourceError(
|
|
57
|
+
"No measure column: no column has a numeric datatype. Pass options.measure to name one."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let statusIdx = cols.findIndex((c) => c.name === options.status);
|
|
62
|
+
if (statusIdx === -1 && !options.status) {
|
|
63
|
+
statusIdx = cols.findIndex(
|
|
64
|
+
(c) => c.name.toLowerCase() === "status" || roleFromPropertyUrl(c.propertyUrl) === void 0 && c.name.toLowerCase() === "status"
|
|
65
|
+
);
|
|
66
|
+
if (statusIdx === measureIdx) statusIdx = -1;
|
|
67
|
+
}
|
|
68
|
+
let dimensionIdxs;
|
|
69
|
+
if (options.dimensions) {
|
|
70
|
+
dimensionIdxs = options.dimensions.map((n) => {
|
|
71
|
+
const idx = cols.findIndex((c) => c.name === n);
|
|
72
|
+
if (idx === -1) throw new CsvwSourceError(`Dimension column "${n}" not in schema`);
|
|
73
|
+
return idx;
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
const pk = schema.primaryKey ? Array.isArray(schema.primaryKey) ? schema.primaryKey : [schema.primaryKey] : [];
|
|
77
|
+
if (pk.length > 0) {
|
|
78
|
+
dimensionIdxs = pk.map((n) => {
|
|
79
|
+
const idx = cols.findIndex((c) => c.name === n);
|
|
80
|
+
if (idx === -1) throw new CsvwSourceError(`primaryKey column "${n}" not in schema`);
|
|
81
|
+
return idx;
|
|
82
|
+
});
|
|
83
|
+
for (let i = 0; i < cols.length; i++) {
|
|
84
|
+
if (i === measureIdx || i === statusIdx || dimensionIdxs.includes(i)) continue;
|
|
85
|
+
dimensionIdxs.push(i);
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
dimensionIdxs = cols.map((_, i) => i).filter((i) => i !== measureIdx && i !== statusIdx);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (dimensionIdxs.length === 0) {
|
|
92
|
+
throw new CsvwSourceError("No dimension columns resolved");
|
|
93
|
+
}
|
|
94
|
+
const dimensions = {};
|
|
95
|
+
for (const idx of dimensionIdxs) {
|
|
96
|
+
const col = cols[idx];
|
|
97
|
+
const label = firstTitle(col);
|
|
98
|
+
dimensions[col.name] = {
|
|
99
|
+
id: col.name,
|
|
100
|
+
values: columnValues[idx].map((v) => v === "" ? "" : v),
|
|
101
|
+
label
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const measure = {
|
|
105
|
+
name: cols[measureIdx].name,
|
|
106
|
+
values: columnValues[measureIdx].map((v) => {
|
|
107
|
+
if (v === "") return null;
|
|
108
|
+
const n = Number(v);
|
|
109
|
+
return Number.isFinite(n) ? n : null;
|
|
110
|
+
})
|
|
111
|
+
};
|
|
112
|
+
let status;
|
|
113
|
+
if (statusIdx !== -1) {
|
|
114
|
+
status = { values: columnValues[statusIdx].slice() };
|
|
115
|
+
}
|
|
116
|
+
const roles = {};
|
|
117
|
+
const addRole = (role, id) => {
|
|
118
|
+
(roles[role] ?? (roles[role] = [])).push(id);
|
|
119
|
+
};
|
|
120
|
+
if (metadata["jsonstat:roles"]) {
|
|
121
|
+
for (const r of ["time", "geo", "metric"]) {
|
|
122
|
+
for (const id of metadata["jsonstat:roles"][r] ?? []) addRole(r, id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const idx of dimensionIdxs) {
|
|
126
|
+
const role = roleFromPropertyUrl(cols[idx].propertyUrl);
|
|
127
|
+
if (role) addRole(role, cols[idx].name);
|
|
128
|
+
}
|
|
129
|
+
if (options.roles) {
|
|
130
|
+
for (const r of ["time", "geo", "metric"]) {
|
|
131
|
+
for (const id of options.roles[r] ?? []) addRole(r, id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const observations = {
|
|
135
|
+
dimensions,
|
|
136
|
+
measure,
|
|
137
|
+
status,
|
|
138
|
+
model: {
|
|
139
|
+
dimensionIds: dimensionIdxs.map((i) => cols[i].name),
|
|
140
|
+
roles: Object.keys(roles).length ? roles : void 0,
|
|
141
|
+
meta: {
|
|
142
|
+
label: metadata["dc:title"],
|
|
143
|
+
source: metadata["dc:source"],
|
|
144
|
+
updated: metadata["dcat:modified"],
|
|
145
|
+
extension: metadata["jsonstat:extension"]
|
|
146
|
+
},
|
|
147
|
+
valueForm: metadata["jsonstat:valueForm"] ?? "auto",
|
|
148
|
+
statusForm: status ? "auto" : "none"
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
return observations;
|
|
152
|
+
}
|
|
153
|
+
async function csvwToDataset(csvText, metadata, options) {
|
|
154
|
+
const { toDataset } = await import('../cubeBuilder-BEGA4C5Y.js');
|
|
155
|
+
return toDataset(csvwToCube(csvText, metadata, options));
|
|
156
|
+
}
|
|
157
|
+
function parseCsvwMetadata(json) {
|
|
158
|
+
const m = json;
|
|
159
|
+
if (!m || !m.tableSchema || !Array.isArray(m.tableSchema.columns)) {
|
|
160
|
+
throw new CsvwSourceError("Invalid CSVW metadata: missing tableSchema.columns");
|
|
161
|
+
}
|
|
162
|
+
return m;
|
|
163
|
+
}
|
|
164
|
+
function propertyUrlForRole(role) {
|
|
165
|
+
if (role === "time") return "schema:DateTime";
|
|
166
|
+
if (role === "geo") return "schema:Place";
|
|
167
|
+
if (role === "metric") return "schema:Measure";
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
function roleOf(obs, id) {
|
|
171
|
+
const roles = obs.model.roles;
|
|
172
|
+
if (!roles) return void 0;
|
|
173
|
+
if (roles.time?.includes(id)) return "time";
|
|
174
|
+
if (roles.geo?.includes(id)) return "geo";
|
|
175
|
+
if (roles.metric?.includes(id)) return "metric";
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
function cubeToCsvw(obs, options = {}) {
|
|
179
|
+
const { header, rows } = cubeColumns(obs);
|
|
180
|
+
const csv = serializeRows(header, rows, options);
|
|
181
|
+
const dimIds = obs.model.dimensionIds;
|
|
182
|
+
const measureName = obs.measure.name ?? "value";
|
|
183
|
+
const hasStatus = obs.status !== void 0;
|
|
184
|
+
const meta = obs.model.meta;
|
|
185
|
+
const columns = [];
|
|
186
|
+
for (const id of dimIds) {
|
|
187
|
+
const col = obs.dimensions[id];
|
|
188
|
+
const role = roleOf(obs, id);
|
|
189
|
+
const propUrl = propertyUrlForRole(role);
|
|
190
|
+
columns.push({
|
|
191
|
+
name: id,
|
|
192
|
+
titles: col?.label ?? id,
|
|
193
|
+
datatype: "string",
|
|
194
|
+
...propUrl ? { propertyUrl: propUrl } : {}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
columns.push({
|
|
198
|
+
name: measureName,
|
|
199
|
+
titles: measureName,
|
|
200
|
+
datatype: "decimal",
|
|
201
|
+
propertyUrl: "schema:value",
|
|
202
|
+
required: false
|
|
203
|
+
});
|
|
204
|
+
if (hasStatus) {
|
|
205
|
+
columns.push({
|
|
206
|
+
name: "status",
|
|
207
|
+
titles: "status",
|
|
208
|
+
datatype: "string"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const metadata = {
|
|
212
|
+
url: options.url ?? "data.csv",
|
|
213
|
+
"@context": ["http://www.w3.org/ns/csvw", { "@language": "en" }],
|
|
214
|
+
tableSchema: {
|
|
215
|
+
columns,
|
|
216
|
+
primaryKey: dimIds.slice()
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
if (meta?.label) metadata["dc:title"] = meta.label;
|
|
220
|
+
if (meta?.source) metadata["dc:source"] = meta.source;
|
|
221
|
+
if (meta?.updated) metadata["dcat:modified"] = meta.updated;
|
|
222
|
+
if (meta?.extension) metadata["jsonstat:extension"] = meta.extension;
|
|
223
|
+
if (obs.model.roles) metadata["jsonstat:roles"] = obs.model.roles;
|
|
224
|
+
if (obs.model.valueForm) metadata["jsonstat:valueForm"] = obs.model.valueForm;
|
|
225
|
+
return { csv, metadata };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { CsvwSourceError, csvwToCube, csvwToDataset, cubeToCsvw, parseCsvwMetadata };
|
|
229
|
+
//# sourceMappingURL=csvw.js.map
|
|
230
|
+
//# sourceMappingURL=csvw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/sources/csvw.ts"],"names":[],"mappings":"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","file":"csvw.js","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,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkPHLCWTHB_cjs = require('../chunk-PHLCWTHB.cjs');
|
|
4
|
+
|
|
5
|
+
// src/sources/duckdb.ts
|
|
6
|
+
var DuckdbSourceError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "DuckdbSourceError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
async function runQuery(conn, query) {
|
|
13
|
+
if (typeof conn.arrow === "function") {
|
|
14
|
+
return await conn.arrow(query);
|
|
15
|
+
}
|
|
16
|
+
if (typeof conn.arrowResult === "function") {
|
|
17
|
+
const res = await conn.arrowResult(query);
|
|
18
|
+
if (res && typeof res.arrow === "function") return await res.arrow();
|
|
19
|
+
return res;
|
|
20
|
+
}
|
|
21
|
+
throw new DuckdbSourceError(
|
|
22
|
+
"DuckDB connection has no arrow()/arrowResult() method; cannot fetch Arrow result"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
async function duckdbToCube(connection, query, options = {}) {
|
|
26
|
+
let conn = connection;
|
|
27
|
+
let ownsConn = false;
|
|
28
|
+
if (options.connect) {
|
|
29
|
+
conn = await options.connect();
|
|
30
|
+
ownsConn = true;
|
|
31
|
+
}
|
|
32
|
+
let table;
|
|
33
|
+
try {
|
|
34
|
+
table = await runQuery(conn, query);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (ownsConn && conn.close) await conn.close().catch(() => {
|
|
37
|
+
});
|
|
38
|
+
throw new DuckdbSourceError(`DuckDB query failed: ${e?.message ?? e}`);
|
|
39
|
+
}
|
|
40
|
+
if (ownsConn && conn.close) await conn.close().catch(() => {
|
|
41
|
+
});
|
|
42
|
+
if (!table) throw new DuckdbSourceError("DuckDB returned no result");
|
|
43
|
+
return chunkPHLCWTHB_cjs.arrowToCube(table, options);
|
|
44
|
+
}
|
|
45
|
+
async function duckdbToDataset(connection, query, options) {
|
|
46
|
+
const { toDataset } = await import('../cubeBuilder-ZU76VVEX.cjs');
|
|
47
|
+
return toDataset(await duckdbToCube(connection, query, options));
|
|
48
|
+
}
|
|
49
|
+
async function openDuckdbNode(path = ":memory:") {
|
|
50
|
+
try {
|
|
51
|
+
const mod = await import('duckdb-async');
|
|
52
|
+
const db = await mod.createDb(path);
|
|
53
|
+
return await db.connect();
|
|
54
|
+
} catch {
|
|
55
|
+
throw new DuckdbSourceError(
|
|
56
|
+
"duckdb-async is not installed. Install it with `npm i duckdb-async` for Node usage."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function quoteIdent(name) {
|
|
61
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
62
|
+
}
|
|
63
|
+
async function registerArrowTable(conn, table, name, mode) {
|
|
64
|
+
if (mode === "view" && typeof conn.register === "function") {
|
|
65
|
+
await conn.register(name, table);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (typeof conn.insert_arrow_table === "function") {
|
|
69
|
+
await conn.insert_arrow_table(table, { name });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (mode === "view" && typeof conn.register === "function") {
|
|
73
|
+
await conn.register(name, table);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (typeof conn.run === "function") {
|
|
77
|
+
throw new DuckdbSourceError(
|
|
78
|
+
"DuckDB connection cannot ingest an Arrow Table: no register()/insert_arrow_table() method. Pass a duckdb-async or @duckdb/duckdb-wasm connection."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
throw new DuckdbSourceError(
|
|
82
|
+
"DuckDB connection cannot ingest an Arrow Table: no register()/insert_arrow_table() method. Pass a duckdb-async or @duckdb/duckdb-wasm connection."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
async function cubeToDuckdb(conn, obs, options = {}) {
|
|
86
|
+
const tableName = options.tableName ?? "observations";
|
|
87
|
+
const mode = options.mode ?? "view";
|
|
88
|
+
const table = chunkPHLCWTHB_cjs.cubeToArrow(obs);
|
|
89
|
+
try {
|
|
90
|
+
if (mode === "table") {
|
|
91
|
+
if (typeof conn.run === "function") {
|
|
92
|
+
const ident = quoteIdent(tableName);
|
|
93
|
+
const tempView = `__jsonstat_io_${tableName}_${Date.now()}`;
|
|
94
|
+
await registerArrowTable(conn, table, tempView, "view");
|
|
95
|
+
await conn.run(
|
|
96
|
+
`CREATE TABLE ${ident} AS SELECT * FROM ${quoteIdent(tempView)};`
|
|
97
|
+
);
|
|
98
|
+
if (typeof conn.run === "function") {
|
|
99
|
+
await conn.run(`DROP VIEW IF EXISTS ${quoteIdent(tempView)};`).catch(
|
|
100
|
+
() => {
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return tableName;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await registerArrowTable(conn, table, tableName, mode);
|
|
108
|
+
return tableName;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
if (e instanceof DuckdbSourceError) throw e;
|
|
111
|
+
throw new DuckdbSourceError(`DuckDB export failed: ${e?.message ?? e}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
exports.DuckdbSourceError = DuckdbSourceError;
|
|
116
|
+
exports.cubeToDuckdb = cubeToDuckdb;
|
|
117
|
+
exports.duckdbToCube = duckdbToCube;
|
|
118
|
+
exports.duckdbToDataset = duckdbToDataset;
|
|
119
|
+
exports.openDuckdbNode = openDuckdbNode;
|
|
120
|
+
//# sourceMappingURL=duckdb.cjs.map
|
|
121
|
+
//# sourceMappingURL=duckdb.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/sources/duckdb.ts"],"names":["arrowToCube","cubeToArrow"],"mappings":"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","file":"duckdb.cjs","sourcesContent":["/**\n * DuckDB adapter — `jsonstat-io/duckdb`.\n *\n * **Import:** runs a SQL query against DuckDB and funnels the result (as an\n * Arrow [`Table`]) through the Arrow hub\n * ([`arrowToCube`](../arrow/arrowToCube.ts)).\n *\n * **Export:** [`cubeToDuckdb`] registers an Arrow [`Table`] built from the\n * [`Observations`] IR into a DuckDB connection as a named table.\n *\n * Two optional peer backends are supported, picked automatically:\n *\n * - **Browser**: `@duckdb/duckdb-wasm` — instantiate via `@duckdb/duckdb-wasm`\n * and pass the connection (or a factory) to [`duckdbToCube`].\n * - **Node**: `duckdb-async` — a native binding; pass the connection.\n *\n * Both expose an `.arrowResult()` / `.arrow()` style API that yields an Arrow\n * Table. The adapter normalizes the two shapes.\n *\n * `@duckdb/duckdb-wasm` and `duckdb-async` are **optional peer dependencies**.\n */\n\nimport type { Observations } from \"../model/ir\";\nimport type { JsonStatDataset } from \"../model/jsonstat\";\nimport { arrowToCube, type ArrowToCubeOptions } from \"../arrow/arrowToCube\";\nimport { cubeToArrow } from \"../arrow/arrowFromCube\";\nimport type { Table } from \"apache-arrow\";\n\n// ---------------------------------------------------------------------------\n// Errors & types\n// ---------------------------------------------------------------------------\n\nexport class DuckdbSourceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DuckdbSourceError\";\n }\n}\n\n/**\n * A minimal connection interface covering both duckdb-wasm and duckdb-async.\n * The adapter only requires a way to run a query and obtain an Arrow Table.\n */\nexport interface DuckdbConnection {\n /** Run a SQL query and return an Apache Arrow Table. */\n arrow(query: string): Promise<any>;\n /**\n * Register an Arrow Table under a view name (duckdb-async style). Optional:\n * the export path falls back to `insert_arrow_table` / `conn.register` if\n * this is absent.\n */\n register?(viewName: string, table: any): Promise<void>;\n /** DuckDB-wasm style: insert an Arrow Table into a named table. */\n insert_arrow_table?(table: any, options?: { name?: string }): Promise<void>;\n /** Run an arbitrary SQL statement (no Arrow result). */\n run?(query: string): Promise<unknown>;\n close?(): Promise<void>;\n}\n\nexport interface DuckdbToCubeOptions extends ArrowToCubeOptions {\n /**\n * A factory that returns a fresh connection. Useful for browser setups where\n * the wasm module must be instantiated lazily. If provided, takes precedence\n * over `connection` and is closed after use.\n */\n connect?: () => Promise<DuckdbConnection>;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: normalize Arrow result extraction\n// ---------------------------------------------------------------------------\n\n/**\n * DuckDB bindings return Arrow tables via differently-named methods\n * (`arrowResult`, `arrow`, `all`+manual). Try the common ones in order.\n */\nasync function runQuery(conn: DuckdbConnection, query: string): Promise<any> {\n if (typeof (conn as any).arrow === \"function\") {\n return await (conn as any).arrow(query);\n }\n if (typeof (conn as any).arrowResult === \"function\") {\n const res = await (conn as any).arrowResult(query);\n // duckdb-wasm: result is either a Table or has .getAll() / .arrow()\n if (res && typeof res.arrow === \"function\") return await res.arrow();\n return res;\n }\n throw new DuckdbSourceError(\n \"DuckDB connection has no arrow()/arrowResult() method; cannot fetch Arrow result\",\n );\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Run a SQL query against DuckDB and convert the result to the\n * [`Observations`] IR via the Arrow hub.\n *\n * @param connectionOrFactory A [`DuckdbConnection`] or (via `options.connect`)\n * a factory creating one.\n * @param query SQL to execute.\n * @throws [`DuckdbSourceError`] on query or conversion failure.\n */\nexport async function duckdbToCube(\n connection: DuckdbConnection,\n query: string,\n options: DuckdbToCubeOptions = {},\n): Promise<Observations> {\n let conn: DuckdbConnection = connection;\n let ownsConn = false;\n if (options.connect) {\n conn = await options.connect();\n ownsConn = true;\n }\n\n let table: any;\n try {\n table = await runQuery(conn, query);\n } catch (e: any) {\n if (ownsConn && conn.close) await conn.close().catch(() => {});\n throw new DuckdbSourceError(`DuckDB query failed: ${e?.message ?? e}`);\n }\n\n if (ownsConn && conn.close) await conn.close().catch(() => {});\n if (!table) throw new DuckdbSourceError(\"DuckDB returned no result\");\n\n return arrowToCube(table, options);\n}\n\n/** Convenience: DuckDB query → JSON-stat [`Dataset`]. */\nexport async function duckdbToDataset(\n connection: DuckdbConnection,\n query: string,\n options?: DuckdbToCubeOptions,\n): Promise<JsonStatDataset> {\n const { toDataset } = await import(\"../core/cubeBuilder\");\n return toDataset(await duckdbToCube(connection, query, options));\n}\n\n/**\n * Helper for Node users of `duckdb-async`: create a connection from a\n * Database instance. Importing `duckdb-async` is lazy so browser bundles stay\n * lean.\n */\nexport async function openDuckdbNode(path = \":memory:\"): Promise<DuckdbConnection> {\n try {\n const mod: any = await import(\"duckdb-async\");\n const db = await mod.createDb(path);\n return (await db.connect()) as DuckdbConnection;\n } catch {\n throw new DuckdbSourceError(\n \"duckdb-async is not installed. Install it with `npm i duckdb-async` for Node usage.\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: Observations IR → DuckDB table\n// ---------------------------------------------------------------------------\n\nexport interface CubeToDuckdbOptions {\n /** Name of the target table/view to create. Defaults to `\"observations\"`. */\n tableName?: string;\n /**\n * `\"view\"` (default): registers the Arrow Table as a queryable view via\n * `conn.register` (cheap; the table is not copied). `\"table\"`: creates and\n * populates a physical table via DuckDB's `insert_arrow_table` / `CREATE\n * TABLE AS`.\n */\n mode?: \"view\" | \"table\";\n}\n\n/** Sanitize a table name into a valid DuckDB identifier (quoted). */\nfunction quoteIdent(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Register an Arrow [`Table`] into a DuckDB connection under a view/table name.\n *\n * Tries, in order: `conn.register(viewName, table)` (duckdb-async), then\n * `conn.insert_arrow_table(table, { name })` (duckdb-wasm), then a manual\n * `CREATE TABLE AS SELECT * FROM arrow_scan` fallback. Throws if none of the\n * registration paths are available on the connection.\n *\n * @internal\n */\nasync function registerArrowTable(\n conn: DuckdbConnection,\n table: Table,\n name: string,\n mode: \"view\" | \"table\",\n): Promise<void> {\n if (mode === \"view\" && typeof conn.register === \"function\") {\n await conn.register(name, table);\n return;\n }\n if (typeof conn.insert_arrow_table === \"function\") {\n // duckdb-wasm writes a physical table.\n await conn.insert_arrow_table(table, { name });\n return;\n }\n if (mode === \"view\" && typeof conn.register === \"function\") {\n await conn.register(name, table);\n return;\n }\n if (typeof conn.run === \"function\") {\n // Last-resort: register via the duckdb-async Connection.register, which is\n // already covered above; here we only reach if register is absent.\n throw new DuckdbSourceError(\n \"DuckDB connection cannot ingest an Arrow Table: no register()/insert_arrow_table() method. \" +\n \"Pass a duckdb-async or @duckdb/duckdb-wasm connection.\",\n );\n }\n throw new DuckdbSourceError(\n \"DuckDB connection cannot ingest an Arrow Table: no register()/insert_arrow_table() method. \" +\n \"Pass a duckdb-async or @duckdb/duckdb-wasm connection.\",\n );\n}\n\n/**\n * Write the [`Observations`](../model/ir.ts) IR into a DuckDB connection as a\n * named table (or view).\n *\n * The IR is first converted to an Arrow [`Table`] via [`cubeToArrow`], then\n * registered into the connection. All `jsonstat.*` schema/field metadata is\n * preserved on the Arrow schema, so a subsequent `SELECT * FROM <table>` →\n * [`arrowToCube`] round-trips the model.\n *\n * @returns The name of the created table/view.\n * @throws [`DuckdbSourceError`] if the connection cannot ingest Arrow tables.\n *\n * @example\n * ```ts\n * import { openDuckdbNode, cubeToDuckdb } from \"jsonstat-io/duckdb\";\n * const conn = await openDuckdbNode();\n * const name = await cubeToDuckdb(conn, observations, { tableName: \"sales\" });\n * const rows = await conn.arrow(`SELECT * FROM ${name} LIMIT 5`);\n * ```\n */\nexport async function cubeToDuckdb(\n conn: DuckdbConnection,\n obs: Observations,\n options: CubeToDuckdbOptions = {},\n): Promise<string> {\n const tableName = options.tableName ?? \"observations\";\n const mode = options.mode ?? \"view\";\n const table = cubeToArrow(obs);\n\n try {\n if (mode === \"table\") {\n // Materialize into a physical table: create empty then insert.\n if (typeof conn.run === \"function\") {\n const ident = quoteIdent(tableName);\n // DuckDB can register the Arrow table temporarily, then CTAS from it.\n const tempView = `__jsonstat_io_${tableName}_${Date.now()}`;\n await registerArrowTable(conn, table, tempView, \"view\");\n await conn.run(\n `CREATE TABLE ${ident} AS SELECT * FROM ${quoteIdent(tempView)};`,\n );\n // Best-effort drop of the temp view; ignore errors.\n if (typeof conn.run === \"function\") {\n await conn.run(`DROP VIEW IF EXISTS ${quoteIdent(tempView)};`).catch(\n () => {},\n );\n }\n return tableName;\n }\n }\n await registerArrowTable(conn, table, tableName, mode);\n return tableName;\n } catch (e: any) {\n if (e instanceof DuckdbSourceError) throw e;\n throw new DuckdbSourceError(`DuckDB export failed: ${e?.message ?? e}`);\n }\n}\n"]}
|