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,166 @@
1
+ 'use strict';
2
+
3
+ var chunkZEDTWRDN_cjs = require('./chunk-ZEDTWRDN.cjs');
4
+
5
+ // src/core/cubeReader.ts
6
+ var CubeReaderError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "CubeReaderError";
10
+ }
11
+ };
12
+ function resolveCategories(dataset, dimId) {
13
+ const dim = dataset.dimension[dimId];
14
+ if (!dim) throw new CubeReaderError(`Missing dimension "${dimId}"`);
15
+ const cat = dim.category;
16
+ if (!cat) {
17
+ return {
18
+ categories: [dimId],
19
+ position: /* @__PURE__ */ new Map([[dimId, 0]])
20
+ };
21
+ }
22
+ const idx = cat.index;
23
+ let categories;
24
+ if (Array.isArray(idx)) {
25
+ categories = idx.slice();
26
+ } else if (idx && typeof idx === "object") {
27
+ categories = Object.entries(idx).sort((a, b) => a[1] - b[1]).map(([id]) => id);
28
+ } else {
29
+ if (cat.label && Object.keys(cat.label).length > 0) {
30
+ categories = Object.keys(cat.label);
31
+ } else {
32
+ categories = [dimId];
33
+ }
34
+ }
35
+ const position = /* @__PURE__ */ new Map();
36
+ categories.forEach((c, i) => position.set(c, i));
37
+ return { categories, position };
38
+ }
39
+ function materializeValues(dataset) {
40
+ const total = chunkZEDTWRDN_cjs.totalCells(dataset.size);
41
+ const value = dataset.value;
42
+ if (Array.isArray(value)) {
43
+ if (value.length !== total) {
44
+ throw new CubeReaderError(
45
+ `value array length ${value.length} \u2260 product(size) ${total}`
46
+ );
47
+ }
48
+ return value.slice();
49
+ }
50
+ const dense = new Array(total).fill(null);
51
+ for (const [k, v] of Object.entries(value)) {
52
+ const pos = Number(k);
53
+ if (!Number.isInteger(pos) || pos < 0 || pos >= total) {
54
+ throw new CubeReaderError(`Sparse value key "${k}" is out of range`);
55
+ }
56
+ dense[pos] = v;
57
+ }
58
+ return dense;
59
+ }
60
+ function normalizeStatus(status, total) {
61
+ if (status === void 0) return void 0;
62
+ if (typeof status === "string") {
63
+ return new Array(total).fill(status);
64
+ }
65
+ if (Array.isArray(status)) {
66
+ if (status.length !== total) {
67
+ throw new CubeReaderError(
68
+ `status array length ${status.length} \u2260 product(size) ${total}`
69
+ );
70
+ }
71
+ return status.slice();
72
+ }
73
+ const out = new Array(total).fill("");
74
+ for (const [k, v] of Object.entries(status)) {
75
+ const pos = Number(k);
76
+ if (!Number.isInteger(pos) || pos < 0 || pos >= total) {
77
+ throw new CubeReaderError(`Sparse status key "${k}" is out of range`);
78
+ }
79
+ out[pos] = v;
80
+ }
81
+ return out;
82
+ }
83
+ function readDataset(dataset, options = {}) {
84
+ if (dataset.class !== "dataset") {
85
+ throw new CubeReaderError(`Expected class "dataset", got "${dataset.class}"`);
86
+ }
87
+ const dropNulls = options.dropNulls ?? true;
88
+ const dimIds = dataset.id;
89
+ const resolvedDims = dimIds.map((id) => ({
90
+ id,
91
+ ...resolveCategories(dataset, id)
92
+ }));
93
+ const total = chunkZEDTWRDN_cjs.totalCells(dataset.size);
94
+ const denseValues = materializeValues(dataset);
95
+ const denseStatus = normalizeStatus(dataset.status, total);
96
+ const dimValues = dimIds.map(() => []);
97
+ const measureValues = [];
98
+ const statusValues = [];
99
+ for (let pos = 0; pos < total; pos++) {
100
+ const v = denseValues[pos];
101
+ if (dropNulls && v === null) continue;
102
+ const idx = chunkZEDTWRDN_cjs.multiIndex(pos, dataset.size);
103
+ for (let d = 0; d < dimIds.length; d++) {
104
+ dimValues[d].push(resolvedDims[d].categories[idx[d]]);
105
+ }
106
+ measureValues.push(v);
107
+ if (denseStatus) statusValues.push(denseStatus[pos]);
108
+ }
109
+ const dimensions = {};
110
+ for (let d = 0; d < dimIds.length; d++) {
111
+ const id = dimIds[d];
112
+ const dim = dataset.dimension[id];
113
+ const cat = dim?.category;
114
+ dimensions[id] = {
115
+ id,
116
+ values: dimValues[d],
117
+ categoryOrder: resolvedDims[d].categories.slice(),
118
+ categoryLabels: cat?.label ? { ...cat.label } : void 0,
119
+ categoryUnits: cat?.unit ? { ...cat.unit } : void 0,
120
+ categoryCoordinates: cat?.coordinates ? { ...cat.coordinates } : void 0,
121
+ categoryChild: cat?.child ? { ...cat.child } : void 0,
122
+ label: dim?.label,
123
+ href: dim?.href
124
+ };
125
+ }
126
+ const measure = { values: measureValues };
127
+ let status;
128
+ if (denseStatus) {
129
+ status = { values: statusValues };
130
+ }
131
+ const obs = {
132
+ dimensions,
133
+ measure,
134
+ status,
135
+ model: {
136
+ dimensionIds: dimIds.slice(),
137
+ roles: dataset.role ? { ...dataset.role } : void 0,
138
+ meta: {
139
+ label: dataset.label,
140
+ source: dataset.source,
141
+ updated: dataset.updated,
142
+ extension: dataset.extension ? { ...dataset.extension } : void 0
143
+ },
144
+ // Preserve the original value form so a round-trip is byte-stable when
145
+ // the builder is run with defaults.
146
+ valueForm: Array.isArray(dataset.value) ? "dense" : "sparse",
147
+ statusForm: dataset.status ? typeof dataset.status === "string" ? "string" : Array.isArray(dataset.status) ? "array" : "object" : "none"
148
+ }
149
+ };
150
+ return obs;
151
+ }
152
+ function readResponse(response, options) {
153
+ const ds = response;
154
+ if (!ds || ds.version !== "2.0" || ds.class !== "dataset") {
155
+ throw new CubeReaderError(
156
+ "Input is not a JSON-stat 2.0 dataset response (missing version/class)"
157
+ );
158
+ }
159
+ return readDataset(ds, options);
160
+ }
161
+
162
+ exports.CubeReaderError = CubeReaderError;
163
+ exports.readDataset = readDataset;
164
+ exports.readResponse = readResponse;
165
+ //# sourceMappingURL=chunk-VQCS4LVD.cjs.map
166
+ //# sourceMappingURL=chunk-VQCS4LVD.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/cubeReader.ts"],"names":["totalCells","multiIndex"],"mappings":"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","file":"chunk-VQCS4LVD.cjs","sourcesContent":["/**\n * CubeReader — the inverse of [`CubeBuilder`](./cubeBuilder.ts): converts a\n * JSON-stat [`Dataset`](../model/jsonstat.ts) back into the\n * [`Observations`](../model/ir.ts) IR (a tidy long table).\n *\n * ## Why this exists now (Phase-1 import project)\n *\n * Although the primary deliverable is *import* (columnar → JSON-stat), the\n * reader is shipped in v0.1 for two reasons:\n *\n * 1. **Round-trip fidelity tests.** The canonical JSON-stat samples\n * (oecd, canada, galicia, order, hierarchy, us-gsp, us-unr — see\n * wiki/sample-files.md) are read into the IR, written back through the\n * builder, and asserted equal. This is the strongest correctness proof we\n * have that the importer faithfully preserves the JSON-stat model.\n *\n * 2. **The Phase-2 export seam.** Export (JSON-stat → columnar) is\n * `cubeReader` → IR → [`arrowFromCube`](../arrow/arrowFromCube.ts) →\n * Arrow/Parquet/DuckDB/Polars/CSV writers. Building the reader now means\n * the IR is verified bidirectional, and Phase 2 is \"just\" the writers.\n * See [docs/architecture.md](../../docs/architecture.md) §\"The two seams\".\n *\n * ## Semantics\n *\n * The reader *materializes the dense cube* from `value` (handling both the\n * array form and the sparse object form — see wiki/sparse-cubes.md), then walks\n * every cell in row-major order ([`multiIndex`](./strides.ts)) to emit one\n * observation per cell. Missing cells (`null`) are emitted as rows with a\n * `null` measure, preserving the full Cartesian product of categories — which\n * is what round-trip equality requires.\n *\n * `status` is normalized back to a per-row `StatusColumn` regardless of whether\n * the source used the string, array, or object form.\n */\n\nimport type {\n DimensionColumn,\n MeasureColumn,\n Observations,\n StatusColumn,\n} from \"../model/ir\";\nimport type {\n JsonStatDataset,\n JsonStatStatus,\n JsonStatValue,\n} from \"../model/jsonstat\";\nimport { multiIndex, totalCells } from \"./strides\";\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** Thrown when a dataset cannot be read (malformed or unsupported). */\nexport class CubeReaderError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CubeReaderError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Category index resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a dimension's categories into an ordered array + position map,\n * mirroring [`resolveDimension`](./cubeBuilder.ts) but reading from the\n * JSON-stat `category.index` (array or object form).\n */\nfunction resolveCategories(\n dataset: JsonStatDataset,\n dimId: string,\n): { categories: string[]; position: Map<string, number> } {\n const dim = dataset.dimension[dimId];\n if (!dim) throw new CubeReaderError(`Missing dimension \"${dimId}\"`);\n const cat = dim.category;\n if (!cat) {\n // A dimension with no category block: treat as a single implicit category.\n return {\n categories: [dimId],\n position: new Map([[dimId, 0]]),\n };\n }\n const idx = cat.index;\n let categories: string[];\n if (Array.isArray(idx)) {\n categories = idx.slice();\n } else if (idx && typeof idx === \"object\") {\n // Object form: ID → position. Sort by position ascending.\n categories = Object.entries(idx)\n .sort((a, b) => a[1] - b[1])\n .map(([id]) => id);\n } else {\n // No index: if there's a label object, derive categories from its keys;\n // otherwise it's a constant dimension with a single implicit category.\n if (cat.label && Object.keys(cat.label).length > 0) {\n categories = Object.keys(cat.label);\n } else {\n categories = [dimId];\n }\n }\n const position = new Map<string, number>();\n categories.forEach((c, i) => position.set(c, i));\n return { categories, position };\n}\n\n// ---------------------------------------------------------------------------\n// Value materialization (dense cube from dense or sparse `value`)\n// ---------------------------------------------------------------------------\n\n/**\n * Materialize the full dense value array (length = product of `size`),\n * regardless of whether `value` is the dense array form or the sparse object\n * form. Missing cells become `null`.\n */\nfunction materializeValues(dataset: JsonStatDataset): (number | null)[] {\n const total = totalCells(dataset.size);\n const value = dataset.value as JsonStatValue;\n if (Array.isArray(value)) {\n if (value.length !== total) {\n throw new CubeReaderError(\n `value array length ${value.length} ≠ product(size) ${total}`,\n );\n }\n return value.slice();\n }\n // Sparse object form: keys are stringified flat positions.\n const dense: (number | null)[] = new Array(total).fill(null);\n for (const [k, v] of Object.entries(value)) {\n const pos = Number(k);\n if (!Number.isInteger(pos) || pos < 0 || pos >= total) {\n throw new CubeReaderError(`Sparse value key \"${k}\" is out of range`);\n }\n dense[pos] = v;\n }\n return dense;\n}\n\n// ---------------------------------------------------------------------------\n// Status normalization\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize any `status` form (string / array / object) into a per-cell array\n * of length `total`. Empty string means \"no status for this cell\".\n */\nfunction normalizeStatus(\n status: JsonStatStatus | undefined,\n total: number,\n): string[] | undefined {\n if (status === undefined) return undefined;\n if (typeof status === \"string\") {\n return new Array(total).fill(status);\n }\n if (Array.isArray(status)) {\n if (status.length !== total) {\n throw new CubeReaderError(\n `status array length ${status.length} ≠ product(size) ${total}`,\n );\n }\n return status.slice();\n }\n // Object form: position → status. Default \"\" for cells without an entry.\n const out = new Array<string>(total).fill(\"\");\n for (const [k, v] of Object.entries(status)) {\n const pos = Number(k);\n if (!Number.isInteger(pos) || pos < 0 || pos >= total) {\n throw new CubeReaderError(`Sparse status key \"${k}\" is out of range`);\n }\n out[pos] = v;\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Reader\n// ---------------------------------------------------------------------------\n\nexport interface ReadOptions {\n /**\n * When true (default), cells whose measure is `null` are *omitted* from the\n * emitted long table (a truly sparse/tidy representation). When false, every\n * cell — including nulls — is emitted as a row (required for exact round-trip\n * equality against a dense `value` array).\n */\n dropNulls?: boolean;\n}\n\n/**\n * Read a JSON-stat [`Dataset`](../model/jsonstat.ts) into the\n * [`Observations`](../model/ir.ts) IR.\n *\n * @throws [`CubeReaderError`](#cubereadererror) on structural problems.\n */\nexport function readDataset(\n dataset: JsonStatDataset,\n options: ReadOptions = {},\n): Observations {\n if (dataset.class !== \"dataset\") {\n throw new CubeReaderError(`Expected class \"dataset\", got \"${dataset.class}\"`);\n }\n const dropNulls = options.dropNulls ?? true;\n\n const dimIds = dataset.id;\n const resolvedDims = dimIds.map((id) => ({\n id,\n ...resolveCategories(dataset, id),\n }));\n\n const total = totalCells(dataset.size);\n const denseValues = materializeValues(dataset);\n const denseStatus = normalizeStatus(dataset.status, total);\n\n // Walk every cell in row-major order, emitting one observation per cell.\n const dimValues: string[][] = dimIds.map(() => []);\n const measureValues: (number | null)[] = [];\n const statusValues: string[] = [];\n\n for (let pos = 0; pos < total; pos++) {\n const v = denseValues[pos];\n if (dropNulls && v === null) continue;\n const idx = multiIndex(pos, dataset.size);\n for (let d = 0; d < dimIds.length; d++) {\n dimValues[d].push(resolvedDims[d].categories[idx[d]]);\n }\n measureValues.push(v);\n if (denseStatus) statusValues.push(denseStatus[pos]);\n }\n\n // Build dimension columns from the resolved metadata + the per-row values.\n const dimensions: Record<string, DimensionColumn> = {};\n for (let d = 0; d < dimIds.length; d++) {\n const id = dimIds[d];\n const dim = dataset.dimension[id];\n const cat = dim?.category;\n dimensions[id] = {\n id,\n values: dimValues[d],\n categoryOrder: resolvedDims[d].categories.slice(),\n categoryLabels: cat?.label ? { ...cat.label } : undefined,\n categoryUnits: cat?.unit ? { ...cat.unit } : undefined,\n categoryCoordinates: cat?.coordinates ? { ...cat.coordinates } : undefined,\n categoryChild: cat?.child ? { ...cat.child } : undefined,\n label: dim?.label,\n href: dim?.href,\n };\n }\n\n const measure: MeasureColumn = { values: measureValues };\n\n let status: StatusColumn | undefined;\n if (denseStatus) {\n status = { values: statusValues };\n }\n\n // Reconstruct the cube model so the result can be fed straight back to the\n // builder and round-trip.\n const obs: Observations = {\n dimensions,\n measure,\n status,\n model: {\n dimensionIds: dimIds.slice(),\n roles: dataset.role ? { ...dataset.role } : undefined,\n meta: {\n label: dataset.label,\n source: dataset.source,\n updated: dataset.updated,\n extension: dataset.extension ? { ...dataset.extension } : undefined,\n },\n // Preserve the original value form so a round-trip is byte-stable when\n // the builder is run with defaults.\n valueForm: Array.isArray(dataset.value) ? \"dense\" : \"sparse\",\n statusForm: dataset.status\n ? typeof dataset.status === \"string\"\n ? \"string\"\n : Array.isArray(dataset.status)\n ? \"array\"\n : \"object\"\n : \"none\",\n },\n };\n\n return obs;\n}\n\n/** Convenience: read a JSON-stat response, rejecting non-dataset classes. */\nexport function readResponse(\n response: unknown,\n options?: ReadOptions,\n): Observations {\n const ds = response as JsonStatDataset;\n if (!ds || ds.version !== \"2.0\" || ds.class !== \"dataset\") {\n throw new CubeReaderError(\n \"Input is not a JSON-stat 2.0 dataset response (missing version/class)\",\n );\n }\n return readDataset(ds, options);\n}\n"]}
@@ -0,0 +1,162 @@
1
+ import { totalCells, multiIndex } from './chunk-L65CRXTV.js';
2
+
3
+ // src/core/cubeReader.ts
4
+ var CubeReaderError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "CubeReaderError";
8
+ }
9
+ };
10
+ function resolveCategories(dataset, dimId) {
11
+ const dim = dataset.dimension[dimId];
12
+ if (!dim) throw new CubeReaderError(`Missing dimension "${dimId}"`);
13
+ const cat = dim.category;
14
+ if (!cat) {
15
+ return {
16
+ categories: [dimId],
17
+ position: /* @__PURE__ */ new Map([[dimId, 0]])
18
+ };
19
+ }
20
+ const idx = cat.index;
21
+ let categories;
22
+ if (Array.isArray(idx)) {
23
+ categories = idx.slice();
24
+ } else if (idx && typeof idx === "object") {
25
+ categories = Object.entries(idx).sort((a, b) => a[1] - b[1]).map(([id]) => id);
26
+ } else {
27
+ if (cat.label && Object.keys(cat.label).length > 0) {
28
+ categories = Object.keys(cat.label);
29
+ } else {
30
+ categories = [dimId];
31
+ }
32
+ }
33
+ const position = /* @__PURE__ */ new Map();
34
+ categories.forEach((c, i) => position.set(c, i));
35
+ return { categories, position };
36
+ }
37
+ function materializeValues(dataset) {
38
+ const total = totalCells(dataset.size);
39
+ const value = dataset.value;
40
+ if (Array.isArray(value)) {
41
+ if (value.length !== total) {
42
+ throw new CubeReaderError(
43
+ `value array length ${value.length} \u2260 product(size) ${total}`
44
+ );
45
+ }
46
+ return value.slice();
47
+ }
48
+ const dense = new Array(total).fill(null);
49
+ for (const [k, v] of Object.entries(value)) {
50
+ const pos = Number(k);
51
+ if (!Number.isInteger(pos) || pos < 0 || pos >= total) {
52
+ throw new CubeReaderError(`Sparse value key "${k}" is out of range`);
53
+ }
54
+ dense[pos] = v;
55
+ }
56
+ return dense;
57
+ }
58
+ function normalizeStatus(status, total) {
59
+ if (status === void 0) return void 0;
60
+ if (typeof status === "string") {
61
+ return new Array(total).fill(status);
62
+ }
63
+ if (Array.isArray(status)) {
64
+ if (status.length !== total) {
65
+ throw new CubeReaderError(
66
+ `status array length ${status.length} \u2260 product(size) ${total}`
67
+ );
68
+ }
69
+ return status.slice();
70
+ }
71
+ const out = new Array(total).fill("");
72
+ for (const [k, v] of Object.entries(status)) {
73
+ const pos = Number(k);
74
+ if (!Number.isInteger(pos) || pos < 0 || pos >= total) {
75
+ throw new CubeReaderError(`Sparse status key "${k}" is out of range`);
76
+ }
77
+ out[pos] = v;
78
+ }
79
+ return out;
80
+ }
81
+ function readDataset(dataset, options = {}) {
82
+ if (dataset.class !== "dataset") {
83
+ throw new CubeReaderError(`Expected class "dataset", got "${dataset.class}"`);
84
+ }
85
+ const dropNulls = options.dropNulls ?? true;
86
+ const dimIds = dataset.id;
87
+ const resolvedDims = dimIds.map((id) => ({
88
+ id,
89
+ ...resolveCategories(dataset, id)
90
+ }));
91
+ const total = totalCells(dataset.size);
92
+ const denseValues = materializeValues(dataset);
93
+ const denseStatus = normalizeStatus(dataset.status, total);
94
+ const dimValues = dimIds.map(() => []);
95
+ const measureValues = [];
96
+ const statusValues = [];
97
+ for (let pos = 0; pos < total; pos++) {
98
+ const v = denseValues[pos];
99
+ if (dropNulls && v === null) continue;
100
+ const idx = multiIndex(pos, dataset.size);
101
+ for (let d = 0; d < dimIds.length; d++) {
102
+ dimValues[d].push(resolvedDims[d].categories[idx[d]]);
103
+ }
104
+ measureValues.push(v);
105
+ if (denseStatus) statusValues.push(denseStatus[pos]);
106
+ }
107
+ const dimensions = {};
108
+ for (let d = 0; d < dimIds.length; d++) {
109
+ const id = dimIds[d];
110
+ const dim = dataset.dimension[id];
111
+ const cat = dim?.category;
112
+ dimensions[id] = {
113
+ id,
114
+ values: dimValues[d],
115
+ categoryOrder: resolvedDims[d].categories.slice(),
116
+ categoryLabels: cat?.label ? { ...cat.label } : void 0,
117
+ categoryUnits: cat?.unit ? { ...cat.unit } : void 0,
118
+ categoryCoordinates: cat?.coordinates ? { ...cat.coordinates } : void 0,
119
+ categoryChild: cat?.child ? { ...cat.child } : void 0,
120
+ label: dim?.label,
121
+ href: dim?.href
122
+ };
123
+ }
124
+ const measure = { values: measureValues };
125
+ let status;
126
+ if (denseStatus) {
127
+ status = { values: statusValues };
128
+ }
129
+ const obs = {
130
+ dimensions,
131
+ measure,
132
+ status,
133
+ model: {
134
+ dimensionIds: dimIds.slice(),
135
+ roles: dataset.role ? { ...dataset.role } : void 0,
136
+ meta: {
137
+ label: dataset.label,
138
+ source: dataset.source,
139
+ updated: dataset.updated,
140
+ extension: dataset.extension ? { ...dataset.extension } : void 0
141
+ },
142
+ // Preserve the original value form so a round-trip is byte-stable when
143
+ // the builder is run with defaults.
144
+ valueForm: Array.isArray(dataset.value) ? "dense" : "sparse",
145
+ statusForm: dataset.status ? typeof dataset.status === "string" ? "string" : Array.isArray(dataset.status) ? "array" : "object" : "none"
146
+ }
147
+ };
148
+ return obs;
149
+ }
150
+ function readResponse(response, options) {
151
+ const ds = response;
152
+ if (!ds || ds.version !== "2.0" || ds.class !== "dataset") {
153
+ throw new CubeReaderError(
154
+ "Input is not a JSON-stat 2.0 dataset response (missing version/class)"
155
+ );
156
+ }
157
+ return readDataset(ds, options);
158
+ }
159
+
160
+ export { CubeReaderError, readDataset, readResponse };
161
+ //# sourceMappingURL=chunk-VYS7DPB3.js.map
162
+ //# sourceMappingURL=chunk-VYS7DPB3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/cubeReader.ts"],"names":[],"mappings":"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","file":"chunk-VYS7DPB3.js","sourcesContent":["/**\n * CubeReader — the inverse of [`CubeBuilder`](./cubeBuilder.ts): converts a\n * JSON-stat [`Dataset`](../model/jsonstat.ts) back into the\n * [`Observations`](../model/ir.ts) IR (a tidy long table).\n *\n * ## Why this exists now (Phase-1 import project)\n *\n * Although the primary deliverable is *import* (columnar → JSON-stat), the\n * reader is shipped in v0.1 for two reasons:\n *\n * 1. **Round-trip fidelity tests.** The canonical JSON-stat samples\n * (oecd, canada, galicia, order, hierarchy, us-gsp, us-unr — see\n * wiki/sample-files.md) are read into the IR, written back through the\n * builder, and asserted equal. This is the strongest correctness proof we\n * have that the importer faithfully preserves the JSON-stat model.\n *\n * 2. **The Phase-2 export seam.** Export (JSON-stat → columnar) is\n * `cubeReader` → IR → [`arrowFromCube`](../arrow/arrowFromCube.ts) →\n * Arrow/Parquet/DuckDB/Polars/CSV writers. Building the reader now means\n * the IR is verified bidirectional, and Phase 2 is \"just\" the writers.\n * See [docs/architecture.md](../../docs/architecture.md) §\"The two seams\".\n *\n * ## Semantics\n *\n * The reader *materializes the dense cube* from `value` (handling both the\n * array form and the sparse object form — see wiki/sparse-cubes.md), then walks\n * every cell in row-major order ([`multiIndex`](./strides.ts)) to emit one\n * observation per cell. Missing cells (`null`) are emitted as rows with a\n * `null` measure, preserving the full Cartesian product of categories — which\n * is what round-trip equality requires.\n *\n * `status` is normalized back to a per-row `StatusColumn` regardless of whether\n * the source used the string, array, or object form.\n */\n\nimport type {\n DimensionColumn,\n MeasureColumn,\n Observations,\n StatusColumn,\n} from \"../model/ir\";\nimport type {\n JsonStatDataset,\n JsonStatStatus,\n JsonStatValue,\n} from \"../model/jsonstat\";\nimport { multiIndex, totalCells } from \"./strides\";\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** Thrown when a dataset cannot be read (malformed or unsupported). */\nexport class CubeReaderError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CubeReaderError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Category index resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a dimension's categories into an ordered array + position map,\n * mirroring [`resolveDimension`](./cubeBuilder.ts) but reading from the\n * JSON-stat `category.index` (array or object form).\n */\nfunction resolveCategories(\n dataset: JsonStatDataset,\n dimId: string,\n): { categories: string[]; position: Map<string, number> } {\n const dim = dataset.dimension[dimId];\n if (!dim) throw new CubeReaderError(`Missing dimension \"${dimId}\"`);\n const cat = dim.category;\n if (!cat) {\n // A dimension with no category block: treat as a single implicit category.\n return {\n categories: [dimId],\n position: new Map([[dimId, 0]]),\n };\n }\n const idx = cat.index;\n let categories: string[];\n if (Array.isArray(idx)) {\n categories = idx.slice();\n } else if (idx && typeof idx === \"object\") {\n // Object form: ID → position. Sort by position ascending.\n categories = Object.entries(idx)\n .sort((a, b) => a[1] - b[1])\n .map(([id]) => id);\n } else {\n // No index: if there's a label object, derive categories from its keys;\n // otherwise it's a constant dimension with a single implicit category.\n if (cat.label && Object.keys(cat.label).length > 0) {\n categories = Object.keys(cat.label);\n } else {\n categories = [dimId];\n }\n }\n const position = new Map<string, number>();\n categories.forEach((c, i) => position.set(c, i));\n return { categories, position };\n}\n\n// ---------------------------------------------------------------------------\n// Value materialization (dense cube from dense or sparse `value`)\n// ---------------------------------------------------------------------------\n\n/**\n * Materialize the full dense value array (length = product of `size`),\n * regardless of whether `value` is the dense array form or the sparse object\n * form. Missing cells become `null`.\n */\nfunction materializeValues(dataset: JsonStatDataset): (number | null)[] {\n const total = totalCells(dataset.size);\n const value = dataset.value as JsonStatValue;\n if (Array.isArray(value)) {\n if (value.length !== total) {\n throw new CubeReaderError(\n `value array length ${value.length} ≠ product(size) ${total}`,\n );\n }\n return value.slice();\n }\n // Sparse object form: keys are stringified flat positions.\n const dense: (number | null)[] = new Array(total).fill(null);\n for (const [k, v] of Object.entries(value)) {\n const pos = Number(k);\n if (!Number.isInteger(pos) || pos < 0 || pos >= total) {\n throw new CubeReaderError(`Sparse value key \"${k}\" is out of range`);\n }\n dense[pos] = v;\n }\n return dense;\n}\n\n// ---------------------------------------------------------------------------\n// Status normalization\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize any `status` form (string / array / object) into a per-cell array\n * of length `total`. Empty string means \"no status for this cell\".\n */\nfunction normalizeStatus(\n status: JsonStatStatus | undefined,\n total: number,\n): string[] | undefined {\n if (status === undefined) return undefined;\n if (typeof status === \"string\") {\n return new Array(total).fill(status);\n }\n if (Array.isArray(status)) {\n if (status.length !== total) {\n throw new CubeReaderError(\n `status array length ${status.length} ≠ product(size) ${total}`,\n );\n }\n return status.slice();\n }\n // Object form: position → status. Default \"\" for cells without an entry.\n const out = new Array<string>(total).fill(\"\");\n for (const [k, v] of Object.entries(status)) {\n const pos = Number(k);\n if (!Number.isInteger(pos) || pos < 0 || pos >= total) {\n throw new CubeReaderError(`Sparse status key \"${k}\" is out of range`);\n }\n out[pos] = v;\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Reader\n// ---------------------------------------------------------------------------\n\nexport interface ReadOptions {\n /**\n * When true (default), cells whose measure is `null` are *omitted* from the\n * emitted long table (a truly sparse/tidy representation). When false, every\n * cell — including nulls — is emitted as a row (required for exact round-trip\n * equality against a dense `value` array).\n */\n dropNulls?: boolean;\n}\n\n/**\n * Read a JSON-stat [`Dataset`](../model/jsonstat.ts) into the\n * [`Observations`](../model/ir.ts) IR.\n *\n * @throws [`CubeReaderError`](#cubereadererror) on structural problems.\n */\nexport function readDataset(\n dataset: JsonStatDataset,\n options: ReadOptions = {},\n): Observations {\n if (dataset.class !== \"dataset\") {\n throw new CubeReaderError(`Expected class \"dataset\", got \"${dataset.class}\"`);\n }\n const dropNulls = options.dropNulls ?? true;\n\n const dimIds = dataset.id;\n const resolvedDims = dimIds.map((id) => ({\n id,\n ...resolveCategories(dataset, id),\n }));\n\n const total = totalCells(dataset.size);\n const denseValues = materializeValues(dataset);\n const denseStatus = normalizeStatus(dataset.status, total);\n\n // Walk every cell in row-major order, emitting one observation per cell.\n const dimValues: string[][] = dimIds.map(() => []);\n const measureValues: (number | null)[] = [];\n const statusValues: string[] = [];\n\n for (let pos = 0; pos < total; pos++) {\n const v = denseValues[pos];\n if (dropNulls && v === null) continue;\n const idx = multiIndex(pos, dataset.size);\n for (let d = 0; d < dimIds.length; d++) {\n dimValues[d].push(resolvedDims[d].categories[idx[d]]);\n }\n measureValues.push(v);\n if (denseStatus) statusValues.push(denseStatus[pos]);\n }\n\n // Build dimension columns from the resolved metadata + the per-row values.\n const dimensions: Record<string, DimensionColumn> = {};\n for (let d = 0; d < dimIds.length; d++) {\n const id = dimIds[d];\n const dim = dataset.dimension[id];\n const cat = dim?.category;\n dimensions[id] = {\n id,\n values: dimValues[d],\n categoryOrder: resolvedDims[d].categories.slice(),\n categoryLabels: cat?.label ? { ...cat.label } : undefined,\n categoryUnits: cat?.unit ? { ...cat.unit } : undefined,\n categoryCoordinates: cat?.coordinates ? { ...cat.coordinates } : undefined,\n categoryChild: cat?.child ? { ...cat.child } : undefined,\n label: dim?.label,\n href: dim?.href,\n };\n }\n\n const measure: MeasureColumn = { values: measureValues };\n\n let status: StatusColumn | undefined;\n if (denseStatus) {\n status = { values: statusValues };\n }\n\n // Reconstruct the cube model so the result can be fed straight back to the\n // builder and round-trip.\n const obs: Observations = {\n dimensions,\n measure,\n status,\n model: {\n dimensionIds: dimIds.slice(),\n roles: dataset.role ? { ...dataset.role } : undefined,\n meta: {\n label: dataset.label,\n source: dataset.source,\n updated: dataset.updated,\n extension: dataset.extension ? { ...dataset.extension } : undefined,\n },\n // Preserve the original value form so a round-trip is byte-stable when\n // the builder is run with defaults.\n valueForm: Array.isArray(dataset.value) ? \"dense\" : \"sparse\",\n statusForm: dataset.status\n ? typeof dataset.status === \"string\"\n ? \"string\"\n : Array.isArray(dataset.status)\n ? \"array\"\n : \"object\"\n : \"none\",\n },\n };\n\n return obs;\n}\n\n/** Convenience: read a JSON-stat response, rejecting non-dataset classes. */\nexport function readResponse(\n response: unknown,\n options?: ReadOptions,\n): Observations {\n const ds = response as JsonStatDataset;\n if (!ds || ds.version !== \"2.0\" || ds.class !== \"dataset\") {\n throw new CubeReaderError(\n \"Input is not a JSON-stat 2.0 dataset response (missing version/class)\",\n );\n }\n return readDataset(ds, options);\n}\n"]}
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ // src/core/strides.ts
4
+ function strides(size) {
5
+ const out = new Array(size.length);
6
+ let acc = 1;
7
+ for (let d = size.length - 1; d >= 0; d--) {
8
+ out[d] = acc;
9
+ acc *= size[d];
10
+ }
11
+ return out;
12
+ }
13
+ function totalCells(size) {
14
+ return size.reduce((a, b) => a * b, 1);
15
+ }
16
+ function flatPosition(indices, size) {
17
+ if (indices.length !== size.length) {
18
+ throw new Error(
19
+ `flatPosition: indices length ${indices.length} !== size length ${size.length}`
20
+ );
21
+ }
22
+ const s = strides(size);
23
+ let pos = 0;
24
+ for (let d = 0; d < indices.length; d++) {
25
+ const i = indices[d];
26
+ if (i < 0 || i >= size[d]) {
27
+ throw new Error(
28
+ `flatPosition: index ${i} out of range [0,${size[d]}) for dimension ${d}`
29
+ );
30
+ }
31
+ pos += i * s[d];
32
+ }
33
+ return pos;
34
+ }
35
+ function multiIndex(pos, size) {
36
+ const s = strides(size);
37
+ const total = totalCells(size);
38
+ if (pos < 0 || pos >= total) {
39
+ throw new Error(`multiIndex: position ${pos} out of range [0,${total})`);
40
+ }
41
+ const out = new Array(size.length);
42
+ let rem = pos;
43
+ for (let d = 0; d < size.length; d++) {
44
+ out[d] = Math.floor(rem / s[d]);
45
+ rem = rem % s[d];
46
+ }
47
+ return out;
48
+ }
49
+ function enumerateCells(size, visit) {
50
+ const k = size.length;
51
+ const total = totalCells(size);
52
+ if (total === 0) return;
53
+ const idx = new Array(k).fill(0);
54
+ for (let pos = 0; pos < total; pos++) {
55
+ visit(idx.slice(), pos);
56
+ for (let d = k - 1; d >= 0; d--) {
57
+ idx[d]++;
58
+ if (idx[d] < size[d]) break;
59
+ idx[d] = 0;
60
+ }
61
+ }
62
+ }
63
+
64
+ exports.enumerateCells = enumerateCells;
65
+ exports.flatPosition = flatPosition;
66
+ exports.multiIndex = multiIndex;
67
+ exports.strides = strides;
68
+ exports.totalCells = totalCells;
69
+ //# sourceMappingURL=chunk-ZEDTWRDN.cjs.map
70
+ //# sourceMappingURL=chunk-ZEDTWRDN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/strides.ts"],"names":[],"mappings":"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","file":"chunk-ZEDTWRDN.cjs","sourcesContent":["/**\n * Row-major index math — the correctness backbone of the importer.\n *\n * JSON-stat stores cell values in a flat array laid out in **row-major order**,\n * described by the spec as \"what does not change, first\": the *last* dimension\n * in `id` changes fastest (see wiki/format-specification.md and\n * wiki/sparse-cubes.md).\n *\n * Concretely, for dimensions with sizes `s₀, s₁, …, sₖ₋₁`, the flat position of\n * a cell at multi-index `(i₀, i₁, …, iₖ₋₁)` is:\n *\n * pos = i₀·(s₁·…·sₖ₋₁) + i₁·(s₂·…·sₖ₋₁) + … + iₖ₋₂·sₖ₋₁ + iₖ₋₁\n *\n * i.e. the stride of dimension `d` is the product of the sizes of all\n * dimensions that come *after* it. This module centralizes that arithmetic so\n * every converter and the round-trip tests share one source of truth.\n */\n\n/** Compute row-major strides from a `size` array. strides[d] = ∏ size[d+1..]. */\nexport function strides(size: number[]): number[] {\n const out = new Array<number>(size.length);\n let acc = 1;\n // Walk right-to-left so each stride is the running product of later sizes.\n for (let d = size.length - 1; d >= 0; d--) {\n out[d] = acc;\n acc *= size[d];\n }\n return out;\n}\n\n/** Total number of cells = product of all sizes (the dense cell count). */\nexport function totalCells(size: number[]): number {\n return size.reduce((a, b) => a * b, 1);\n}\n\n/**\n * Convert a multi-index to a flat row-major position.\n *\n * `indices` length must equal `size` length; each `indices[d]` must satisfy\n * `0 ≤ indices[d] < size[d]`.\n */\nexport function flatPosition(indices: number[], size: number[]): number {\n if (indices.length !== size.length) {\n throw new Error(\n `flatPosition: indices length ${indices.length} !== size length ${size.length}`,\n );\n }\n const s = strides(size);\n let pos = 0;\n for (let d = 0; d < indices.length; d++) {\n const i = indices[d];\n if (i < 0 || i >= size[d]) {\n throw new Error(\n `flatPosition: index ${i} out of range [0,${size[d]}) for dimension ${d}`,\n );\n }\n pos += i * s[d];\n }\n return pos;\n}\n\n/**\n * Inverse of [`flatPosition`](#flatposition): decompose a flat position back\n * into a multi-index. Used by the cube reader (Phase-2 seam) and by tests.\n */\nexport function multiIndex(pos: number, size: number[]): number[] {\n const s = strides(size);\n const total = totalCells(size);\n if (pos < 0 || pos >= total) {\n throw new Error(`multiIndex: position ${pos} out of range [0,${total})`);\n }\n const out = new Array<number>(size.length);\n let rem = pos;\n for (let d = 0; d < size.length; d++) {\n out[d] = Math.floor(rem / s[d]);\n rem = rem % s[d];\n }\n return out;\n}\n\n/**\n * Enumerate every multi-index in row-major order, yielding flat positions\n * 0..(total-1). Useful for materializing a dense value array from a sparse map.\n *\n * Calls `visit(indices, flatPos)` for each cell. Iterative (no recursion) to\n * avoid stack overflow on large cubes.\n */\nexport function enumerateCells(\n size: number[],\n visit: (indices: number[], flatPos: number) => void,\n): void {\n const k = size.length;\n const total = totalCells(size);\n if (total === 0) return;\n const idx = new Array<number>(k).fill(0);\n for (let pos = 0; pos < total; pos++) {\n visit(idx.slice(), pos);\n // Increment the last dimension first (it changes fastest), carrying over.\n for (let d = k - 1; d >= 0; d--) {\n idx[d]++;\n if (idx[d] < size[d]) break;\n idx[d] = 0; // carry\n }\n }\n}\n"]}