@matthieumordrel/chart-studio 0.3.0 → 0.4.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 (48) hide show
  1. package/README.md +402 -12
  2. package/dist/core/chart-builder-controls.mjs +141 -0
  3. package/dist/core/dashboard.types.d.mts +220 -0
  4. package/dist/core/data-label-defaults.mjs +74 -0
  5. package/dist/core/data-model.types.d.mts +196 -0
  6. package/dist/core/dataset-builder.types.d.mts +51 -0
  7. package/dist/core/dataset-chart-metadata.d.mts +8 -0
  8. package/dist/core/dataset-chart-metadata.mjs +4 -0
  9. package/dist/core/date-range-presets.mjs +1 -1
  10. package/dist/core/define-dashboard.d.mts +8 -0
  11. package/dist/core/define-dashboard.mjs +156 -0
  12. package/dist/core/define-data-model.d.mts +11 -0
  13. package/dist/core/define-data-model.mjs +327 -0
  14. package/dist/core/define-dataset.d.mts +13 -0
  15. package/dist/core/define-dataset.mjs +111 -0
  16. package/dist/core/index.d.mts +17 -0
  17. package/dist/core/infer-columns.mjs +28 -2
  18. package/dist/core/materialized-view.mjs +580 -0
  19. package/dist/core/materialized-view.types.d.mts +223 -0
  20. package/dist/core/model-chart.mjs +242 -0
  21. package/dist/core/model-chart.types.d.mts +199 -0
  22. package/dist/core/model-inference.mjs +169 -0
  23. package/dist/core/model-inference.types.d.mts +71 -0
  24. package/dist/core/pipeline.mjs +32 -1
  25. package/dist/core/schema-builder.mjs +28 -158
  26. package/dist/core/schema-builder.types.d.mts +2 -49
  27. package/dist/core/types.d.mts +59 -8
  28. package/dist/core/use-chart-options.d.mts +35 -8
  29. package/dist/core/use-chart-resolvers.mjs +13 -3
  30. package/dist/core/use-chart.d.mts +16 -12
  31. package/dist/core/use-chart.mjs +136 -34
  32. package/dist/core/use-dashboard.d.mts +190 -0
  33. package/dist/core/use-dashboard.mjs +551 -0
  34. package/dist/index.d.mts +10 -3
  35. package/dist/index.mjs +5 -2
  36. package/dist/ui/chart-canvas.d.mts +11 -4
  37. package/dist/ui/chart-canvas.mjs +45 -34
  38. package/dist/ui/chart-context.d.mts +2 -0
  39. package/dist/ui/chart-context.mjs +2 -0
  40. package/dist/ui/chart-filters-panel.d.mts +1 -1
  41. package/dist/ui/chart-filters-panel.mjs +163 -37
  42. package/dist/ui/chart-group-by-selector.mjs +4 -4
  43. package/dist/ui/chart-time-bucket-selector.mjs +1 -1
  44. package/dist/ui/chart-toolbar-overflow.mjs +5 -13
  45. package/dist/ui/chart-toolbar.mjs +1 -1
  46. package/package.json +1 -1
  47. package/dist/core/define-chart-schema.d.mts +0 -38
  48. package/dist/core/define-chart-schema.mjs +0 -39
@@ -0,0 +1,156 @@
1
+ import { DATASET_CHART_METADATA } from "./dataset-chart-metadata.mjs";
2
+ //#region src/core/define-dashboard.ts
3
+ function humanizeId(id) {
4
+ return id.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().split(/\s+/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
5
+ }
6
+ function assertUniqueId(collection, kind, id) {
7
+ if (id in collection) throw new Error(`Duplicate ${kind} id: "${id}"`);
8
+ }
9
+ function getDatasetChartMetadata(chart) {
10
+ if (chart && typeof chart === "object" && DATASET_CHART_METADATA in chart) return chart[DATASET_CHART_METADATA];
11
+ if (chart && typeof chart === "object" && "build" in chart && typeof chart.build === "function") {
12
+ const built = chart.build();
13
+ if (built && typeof built === "object" && DATASET_CHART_METADATA in built) return built[DATASET_CHART_METADATA];
14
+ }
15
+ }
16
+ function findModelDatasetId(model, dataset) {
17
+ return Object.entries(model.datasets).find(([, candidate]) => candidate === dataset)?.[0];
18
+ }
19
+ function isMaterializedView(source) {
20
+ return !!source && typeof source === "object" && "__materializedViewBrand" in source && "materialization" in source && "build" in source && typeof source.build === "function";
21
+ }
22
+ function resolveChartDataSource(model, datasetOrView) {
23
+ const datasetId = findModelDatasetId(model, datasetOrView);
24
+ if (datasetId) return {
25
+ kind: "dataset",
26
+ datasetId
27
+ };
28
+ if (!isMaterializedView(datasetOrView)) return;
29
+ const view = datasetOrView;
30
+ const baseDatasetId = view.materialization.baseDataset;
31
+ if (!(baseDatasetId in model.datasets)) return;
32
+ return {
33
+ kind: "materialized-view",
34
+ datasetId: baseDatasetId,
35
+ view
36
+ };
37
+ }
38
+ function createDefinedDashboard(state) {
39
+ let cachedDashboard;
40
+ const build = () => {
41
+ if (cachedDashboard) return cachedDashboard;
42
+ const definedDashboard = {
43
+ model: state.model,
44
+ charts: state.charts,
45
+ sharedFilters: state.sharedFilters,
46
+ build() {
47
+ return definedDashboard;
48
+ },
49
+ __dashboardBrand: "dashboard-definition"
50
+ };
51
+ cachedDashboard = definedDashboard;
52
+ return definedDashboard;
53
+ };
54
+ return build();
55
+ }
56
+ function createDashboardBuilder(state) {
57
+ let cachedDashboard;
58
+ return {
59
+ chart(id, chart) {
60
+ assertUniqueId(state.charts, "dashboard chart", id);
61
+ const metadata = getDatasetChartMetadata(chart);
62
+ if (!metadata) throw new Error(`Dashboard chart "${id}" must come from defineDataset(...).chart(...).`);
63
+ if (metadata.chartId && metadata.chartId !== id) throw new Error(`Dashboard chart "${id}" does not match the chart authoring id "${metadata.chartId}".`);
64
+ const dataSource = resolveChartDataSource(state.model, metadata.dataset);
65
+ if (!dataSource) throw new Error(`Dashboard chart "${id}" references a dataset or materialized view that is not registered in this data model.`);
66
+ return createDashboardBuilder({
67
+ ...state,
68
+ charts: {
69
+ ...state.charts,
70
+ [id]: {
71
+ id,
72
+ datasetId: dataSource.datasetId,
73
+ schema: chart,
74
+ dataSource
75
+ }
76
+ }
77
+ });
78
+ },
79
+ sharedFilter(id, config) {
80
+ assertUniqueId(state.sharedFilters, "shared filter", id);
81
+ if (config === void 0) {
82
+ const attribute = state.model.attributes[id];
83
+ if (!attribute) throw new Error(`Unknown model attribute id: "${id}"`);
84
+ return createDashboardBuilder({
85
+ ...state,
86
+ sharedFilters: {
87
+ ...state.sharedFilters,
88
+ [id]: {
89
+ id,
90
+ kind: "select",
91
+ label: humanizeId(id),
92
+ source: {
93
+ kind: "attribute",
94
+ dataset: attribute.source.dataset,
95
+ key: attribute.source.key,
96
+ label: attribute.source.label
97
+ },
98
+ targets: attribute.targets,
99
+ attribute: id
100
+ }
101
+ }
102
+ });
103
+ }
104
+ if (config.kind === "select") return createDashboardBuilder({
105
+ ...state,
106
+ sharedFilters: {
107
+ ...state.sharedFilters,
108
+ [id]: {
109
+ id,
110
+ kind: "select",
111
+ label: config.label ?? humanizeId(id),
112
+ source: {
113
+ kind: "column",
114
+ dataset: config.source.dataset,
115
+ column: config.source.column
116
+ },
117
+ targets: config.targets ?? [{
118
+ dataset: config.source.dataset,
119
+ column: config.source.column
120
+ }]
121
+ }
122
+ }
123
+ });
124
+ if (!Array.isArray(config.targets) || config.targets.length === 0) throw new Error(`Dashboard shared date-range filter "${id}" requires at least one target.`);
125
+ return createDashboardBuilder({
126
+ ...state,
127
+ sharedFilters: {
128
+ ...state.sharedFilters,
129
+ [id]: {
130
+ id,
131
+ kind: "date-range",
132
+ label: config.label ?? humanizeId(id),
133
+ targets: config.targets
134
+ }
135
+ }
136
+ });
137
+ },
138
+ build() {
139
+ if (cachedDashboard) return cachedDashboard;
140
+ cachedDashboard = createDefinedDashboard(state);
141
+ return cachedDashboard;
142
+ }
143
+ };
144
+ }
145
+ function resolveDashboardDefinition(dashboard) {
146
+ return dashboard.build();
147
+ }
148
+ function defineDashboard(model) {
149
+ return createDashboardBuilder({
150
+ model: model.build(),
151
+ charts: {},
152
+ sharedFilters: {}
153
+ });
154
+ }
155
+ //#endregion
156
+ export { defineDashboard, resolveDashboardDefinition };
@@ -0,0 +1,11 @@
1
+ import { DataModelBuilder } from "./data-model.types.mjs";
2
+
3
+ //#region src/core/define-data-model.d.ts
4
+ /**
5
+ * Define one linked data model for dataset registration, relationships,
6
+ * associations, reusable model-level filter attributes, and explicit
7
+ * Phase 7 materialized views.
8
+ */
9
+ declare function defineDataModel(): DataModelBuilder<{}, {}, {}, {}>;
10
+ //#endregion
11
+ export { defineDataModel };
@@ -0,0 +1,327 @@
1
+ import { resolveDatasetDefinition, validateDatasetData } from "./define-dataset.mjs";
2
+ import { createMaterializationStartBuilder } from "./materialized-view.mjs";
3
+ import { attachModelRuntimeMetadata, getModelRuntimeMetadata, inferModelAttributes, inferModelRelationships, rewriteInferredRelationshipError } from "./model-inference.mjs";
4
+ import { compileModelChart } from "./model-chart.mjs";
5
+ //#region src/core/define-data-model.ts
6
+ function formatValue(value) {
7
+ if (value instanceof Date) return value.toISOString();
8
+ return String(value);
9
+ }
10
+ function buildFingerprint(value) {
11
+ if (value instanceof Date) return `date:${value.toISOString()}`;
12
+ return `${typeof value}:${String(value)}`;
13
+ }
14
+ function getSingleDatasetKey(dataset, datasetId) {
15
+ if (!dataset.key || dataset.key.length !== 1) throw new Error(`Dataset "${datasetId}" must declare exactly one key to participate in relationships or associations.`);
16
+ return dataset.key[0];
17
+ }
18
+ function assertUniqueId(collection, kind, id) {
19
+ if (id in collection) throw new Error(`Duplicate ${kind} id: "${id}"`);
20
+ }
21
+ function getDatasetOrThrow(datasets, datasetId) {
22
+ const dataset = datasets[datasetId];
23
+ if (!dataset) throw new Error(`Unknown dataset id: "${datasetId}"`);
24
+ return dataset;
25
+ }
26
+ function createKeyLookup(datasetId, keyId, rows) {
27
+ const values = /* @__PURE__ */ new Set();
28
+ rows.forEach((row, index) => {
29
+ const value = row[keyId];
30
+ if (value == null) throw new Error(`Dataset "${datasetId}" key "${keyId}" is missing a value at row ${index}.`);
31
+ values.add(buildFingerprint(value));
32
+ });
33
+ return {
34
+ keyId,
35
+ values
36
+ };
37
+ }
38
+ function validateRelationshipData(relationship, data, keyLookups) {
39
+ const toRows = data[relationship.to.dataset];
40
+ const sourceLookup = keyLookups[relationship.from.dataset]?.[relationship.from.key];
41
+ if (!sourceLookup) throw new Error(`Relationship "${relationship.id}" requires dataset "${relationship.from.dataset}" data.`);
42
+ if (!Array.isArray(toRows)) throw new Error(`Relationship "${relationship.id}" requires dataset "${relationship.to.dataset}" data.`);
43
+ toRows.forEach((row, index) => {
44
+ const value = row[relationship.to.column];
45
+ if (value == null) return;
46
+ if (!sourceLookup.values.has(buildFingerprint(value))) throw new Error(`Relationship "${relationship.id}" has an orphan foreign key "${formatValue(value)}" in dataset "${relationship.to.dataset}" column "${relationship.to.column}" at row ${index}.`);
47
+ });
48
+ }
49
+ function validateExplicitAssociationData(association, associationId, fromDatasetId, toDatasetId, fromLookup, toLookup) {
50
+ association.data.forEach((row, index) => {
51
+ const fromValue = row[association.columns.from];
52
+ const toValue = row[association.columns.to];
53
+ if (fromValue == null) throw new Error(`Association "${associationId}" is missing "${association.columns.from}" at edge row ${index}.`);
54
+ if (toValue == null) throw new Error(`Association "${associationId}" is missing "${association.columns.to}" at edge row ${index}.`);
55
+ if (!fromLookup.values.has(buildFingerprint(fromValue))) throw new Error(`Association "${associationId}" has an orphan "${association.columns.from}" value "${formatValue(fromValue)}" for dataset "${fromDatasetId}".`);
56
+ if (!toLookup.values.has(buildFingerprint(toValue))) throw new Error(`Association "${associationId}" has an orphan "${association.columns.to}" value "${formatValue(toValue)}" for dataset "${toDatasetId}".`);
57
+ });
58
+ }
59
+ function validateDerivedAssociationData(association, state, data, keyLookups) {
60
+ const derivedEdge = association.edge;
61
+ if (derivedEdge.kind !== "derived") return;
62
+ const deriveDatasetId = derivedEdge.deriveFrom.dataset;
63
+ const deriveRows = data[deriveDatasetId];
64
+ const deriveKeyId = getSingleDatasetKey(getDatasetOrThrow(state.datasets, deriveDatasetId), deriveDatasetId);
65
+ const oppositeDatasetId = deriveDatasetId === association.from.dataset ? association.to.dataset : association.from.dataset;
66
+ const oppositeKeyId = oppositeDatasetId === association.from.dataset ? association.from.key : association.to.key;
67
+ const oppositeLookup = keyLookups[oppositeDatasetId]?.[oppositeKeyId];
68
+ if (!oppositeLookup) throw new Error(`Association "${association.id}" requires dataset "${oppositeDatasetId}" data.`);
69
+ if (!Array.isArray(deriveRows)) throw new Error(`Association "${association.id}" requires dataset "${deriveDatasetId}" data.`);
70
+ deriveRows.forEach((row, index) => {
71
+ if (row[deriveKeyId] == null) throw new Error(`Association "${association.id}" source dataset "${deriveDatasetId}" is missing key "${deriveKeyId}" at row ${index}.`);
72
+ const rawValues = derivedEdge.deriveFrom.values(row) ?? [];
73
+ if (!Array.isArray(rawValues)) throw new Error(`Association "${association.id}" deriveFrom.values(...) must return an array.`);
74
+ rawValues.forEach((value) => {
75
+ if (value == null) throw new Error(`Association "${association.id}" deriveFrom.values(...) returned an empty key value.`);
76
+ if (!oppositeLookup.values.has(buildFingerprint(value))) throw new Error(`Association "${association.id}" has an orphan derived key "${formatValue(value)}" targeting dataset "${oppositeDatasetId}".`);
77
+ });
78
+ });
79
+ }
80
+ function validateAssociationData(association, state, data, keyLookups) {
81
+ const fromLookup = keyLookups[association.from.dataset]?.[association.from.key];
82
+ const toLookup = keyLookups[association.to.dataset]?.[association.to.key];
83
+ if (!fromLookup || !toLookup) throw new Error(`Association "${association.id}" requires both endpoint datasets to be present.`);
84
+ if (association.edge.kind === "explicit") {
85
+ validateExplicitAssociationData(association.edge, association.id, association.from.dataset, association.to.dataset, fromLookup, toLookup);
86
+ return;
87
+ }
88
+ validateDerivedAssociationData(association, state, data, keyLookups);
89
+ }
90
+ function validateModelRuntimeData(state, data) {
91
+ const keyLookups = {};
92
+ const requiredKeysByDataset = /* @__PURE__ */ new Map();
93
+ const addRequiredKey = (datasetId, keyId) => {
94
+ if (!keyId) return;
95
+ const existing = requiredKeysByDataset.get(datasetId);
96
+ if (existing) {
97
+ existing.add(keyId);
98
+ return;
99
+ }
100
+ requiredKeysByDataset.set(datasetId, new Set([keyId]));
101
+ };
102
+ Object.entries(state.datasets).forEach(([datasetId, dataset]) => {
103
+ const rows = data[datasetId];
104
+ if (!Array.isArray(rows)) throw new Error(`Missing dataset data for "${datasetId}".`);
105
+ validateDatasetData(dataset, rows, datasetId);
106
+ });
107
+ Object.values(state.relationships).forEach((relationship) => {
108
+ addRequiredKey(relationship.from.dataset, relationship.from.key);
109
+ });
110
+ Object.values(state.associations).forEach((association) => {
111
+ addRequiredKey(association.from.dataset, association.from.key);
112
+ addRequiredKey(association.to.dataset, association.to.key);
113
+ });
114
+ Object.values(state.attributes).forEach((attribute) => {
115
+ addRequiredKey(attribute.source.dataset, attribute.source.key);
116
+ });
117
+ Object.entries(state.datasets).forEach(([datasetId, dataset]) => {
118
+ if (dataset.key && dataset.key.length === 1) addRequiredKey(datasetId, dataset.key[0]);
119
+ });
120
+ requiredKeysByDataset.forEach((keyIds, datasetId) => {
121
+ const rows = data[datasetId];
122
+ if (!Array.isArray(rows)) throw new Error(`Missing dataset data for "${datasetId}".`);
123
+ keyLookups[datasetId] = {};
124
+ keyIds.forEach((keyId) => {
125
+ keyLookups[datasetId][keyId] = createKeyLookup(datasetId, keyId, rows);
126
+ });
127
+ });
128
+ Object.values(state.relationships).forEach((relationship) => {
129
+ validateRelationshipData(relationship, data, keyLookups);
130
+ });
131
+ Object.values(state.associations).forEach((association) => {
132
+ validateAssociationData(association, state, data, keyLookups);
133
+ });
134
+ }
135
+ function createDefinedDataModel(state) {
136
+ let cachedModel;
137
+ const build = () => {
138
+ if (cachedModel) return cachedModel;
139
+ const definedModel = {
140
+ datasets: state.datasets,
141
+ relationships: state.relationships,
142
+ associations: state.associations,
143
+ attributes: state.attributes,
144
+ materialize(id, defineView) {
145
+ return defineView(createMaterializationStartBuilder(id, definedModel));
146
+ },
147
+ chart(id, defineChart) {
148
+ return compileModelChart(definedModel, id, defineChart);
149
+ },
150
+ validateData(data) {
151
+ try {
152
+ validateModelRuntimeData(state, data);
153
+ } catch (error) {
154
+ rewriteInferredRelationshipError(getModelRuntimeMetadata(definedModel), error);
155
+ }
156
+ },
157
+ build() {
158
+ return definedModel;
159
+ },
160
+ __dataModelBrand: "data-model-definition"
161
+ };
162
+ attachModelRuntimeMetadata(definedModel, { inferredRelationships: new Map([...state.inferredRelationshipIds].map((relationshipId) => {
163
+ const relationship = state.relationships[relationshipId];
164
+ return [relationshipId, {
165
+ id: relationshipId,
166
+ fromDataset: relationship.from.dataset,
167
+ fromKey: relationship.from.key,
168
+ toDataset: relationship.to.dataset,
169
+ toColumn: relationship.to.column
170
+ }];
171
+ })) });
172
+ cachedModel = definedModel;
173
+ return definedModel;
174
+ };
175
+ return build();
176
+ }
177
+ function createDataModelBuilder(state = {
178
+ datasets: {},
179
+ relationships: {},
180
+ associations: {},
181
+ attributes: {},
182
+ inferredRelationshipIds: /* @__PURE__ */ new Set()
183
+ }) {
184
+ let cachedModel;
185
+ return {
186
+ dataset(id, dataset) {
187
+ assertUniqueId(state.datasets, "dataset", id);
188
+ const resolvedDataset = resolveDatasetDefinition(dataset);
189
+ if (!resolvedDataset.key || resolvedDataset.key.length === 0) throw new Error(`Dataset "${id}" must declare a .key() before being added to a data model.`);
190
+ return createDataModelBuilder({
191
+ ...state,
192
+ datasets: {
193
+ ...state.datasets,
194
+ [id]: resolvedDataset
195
+ }
196
+ });
197
+ },
198
+ relationship(id, config) {
199
+ assertUniqueId(state.relationships, "relationship", id);
200
+ const fromKeyId = getSingleDatasetKey(getDatasetOrThrow(state.datasets, config.from.dataset), config.from.dataset);
201
+ getDatasetOrThrow(state.datasets, config.to.dataset);
202
+ if (config.from.key !== fromKeyId) throw new Error(`Relationship "${id}" must use declared key "${fromKeyId}" from dataset "${config.from.dataset}".`);
203
+ return createDataModelBuilder({
204
+ ...state,
205
+ relationships: {
206
+ ...state.relationships,
207
+ [id]: {
208
+ kind: "relationship",
209
+ id,
210
+ from: config.from,
211
+ to: config.to,
212
+ reverse: {
213
+ dataset: config.to.dataset,
214
+ column: config.to.column,
215
+ to: {
216
+ dataset: config.from.dataset,
217
+ key: config.from.key
218
+ }
219
+ }
220
+ }
221
+ }
222
+ });
223
+ },
224
+ association(id, config) {
225
+ assertUniqueId(state.associations, "association", id);
226
+ const fromDataset = getDatasetOrThrow(state.datasets, config.from.dataset);
227
+ const toDataset = getDatasetOrThrow(state.datasets, config.to.dataset);
228
+ const fromKeyId = getSingleDatasetKey(fromDataset, config.from.dataset);
229
+ const toKeyId = getSingleDatasetKey(toDataset, config.to.dataset);
230
+ if (config.from.key !== fromKeyId) throw new Error(`Association "${id}" must use declared key "${fromKeyId}" from dataset "${config.from.dataset}".`);
231
+ if (config.to.key !== toKeyId) throw new Error(`Association "${id}" must use declared key "${toKeyId}" from dataset "${config.to.dataset}".`);
232
+ if ("deriveFrom" in config) {
233
+ if (config.deriveFrom.dataset !== config.from.dataset && config.deriveFrom.dataset !== config.to.dataset) throw new Error(`Association "${id}" deriveFrom.dataset must match either "${config.from.dataset}" or "${config.to.dataset}".`);
234
+ }
235
+ return createDataModelBuilder({
236
+ ...state,
237
+ associations: {
238
+ ...state.associations,
239
+ [id]: {
240
+ kind: "association",
241
+ id,
242
+ from: config.from,
243
+ to: config.to,
244
+ reverse: {
245
+ dataset: config.to.dataset,
246
+ key: config.to.key,
247
+ to: {
248
+ dataset: config.from.dataset,
249
+ key: config.from.key
250
+ }
251
+ },
252
+ edge: "deriveFrom" in config ? {
253
+ kind: "derived",
254
+ deriveFrom: config.deriveFrom
255
+ } : {
256
+ kind: "explicit",
257
+ data: config.data,
258
+ columns: config.columns
259
+ }
260
+ }
261
+ }
262
+ });
263
+ },
264
+ attribute(id, config) {
265
+ assertUniqueId(state.attributes, "attribute", id);
266
+ return createDataModelBuilder({
267
+ ...state,
268
+ attributes: {
269
+ ...state.attributes,
270
+ [id]: {
271
+ id,
272
+ kind: config.kind,
273
+ source: config.source,
274
+ targets: config.targets
275
+ }
276
+ }
277
+ });
278
+ },
279
+ infer(options) {
280
+ const nextRelationships = options.relationships === true ? inferModelRelationships(state.datasets, state.relationships, new Set(options.exclude ?? [])) : {
281
+ relationships: state.relationships,
282
+ metadata: { inferredRelationships: /* @__PURE__ */ new Map() }
283
+ };
284
+ const nextAttributes = options.attributes === true ? inferModelAttributes(state.datasets, nextRelationships.relationships, state.attributes) : state.attributes;
285
+ return createDataModelBuilder({
286
+ ...state,
287
+ relationships: nextRelationships.relationships,
288
+ attributes: nextAttributes,
289
+ inferredRelationshipIds: new Set([...state.inferredRelationshipIds, ...nextRelationships.metadata.inferredRelationships.keys()])
290
+ });
291
+ },
292
+ chart(id, defineChart) {
293
+ const model = cachedModel ?? createDefinedDataModel(state);
294
+ cachedModel = model;
295
+ return compileModelChart(model, id, defineChart);
296
+ },
297
+ materialize(id, defineView) {
298
+ const model = cachedModel ?? createDefinedDataModel(state);
299
+ cachedModel = model;
300
+ return defineView(createMaterializationStartBuilder(id, model));
301
+ },
302
+ validateData(data) {
303
+ try {
304
+ validateModelRuntimeData(state, data);
305
+ } catch (error) {
306
+ const model = cachedModel ?? createDefinedDataModel(state);
307
+ cachedModel = model;
308
+ rewriteInferredRelationshipError(getModelRuntimeMetadata(model), error);
309
+ }
310
+ },
311
+ build() {
312
+ if (cachedModel) return cachedModel;
313
+ cachedModel = createDefinedDataModel(state);
314
+ return cachedModel;
315
+ }
316
+ };
317
+ }
318
+ /**
319
+ * Define one linked data model for dataset registration, relationships,
320
+ * associations, reusable model-level filter attributes, and explicit
321
+ * Phase 7 materialized views.
322
+ */
323
+ function defineDataModel() {
324
+ return createDataModelBuilder();
325
+ }
326
+ //#endregion
327
+ export { defineDataModel };
@@ -0,0 +1,13 @@
1
+ import { DatasetBuilder, DatasetDefinition, ResolvedDatasetFromDefinition } from "./dataset-builder.types.mjs";
2
+
3
+ //#region src/core/define-dataset.d.ts
4
+ declare function validateDatasetData<TDataset extends DatasetDefinition<any, any, any>>(dataset: TDataset, rows: Parameters<ResolvedDatasetFromDefinition<TDataset>['validateData']>[0], datasetLabel?: string): void;
5
+ /**
6
+ * Define one reusable dataset contract for columns, derived fields, and row identity.
7
+ *
8
+ * Dataset-owned `.columns(...)` is the canonical reusable meaning.
9
+ * Use `.chart(...)` to derive chart definitions from that shared contract.
10
+ */
11
+ declare function defineDataset<TRow>(): DatasetBuilder<TRow, undefined, undefined>;
12
+ //#endregion
13
+ export { defineDataset, validateDatasetData };
@@ -0,0 +1,111 @@
1
+ import { COLUMN_HELPER, assertColumnEntries, buildColumnsMap, createDatasetChartBuilder } from "./schema-builder.mjs";
2
+ //#region src/core/define-dataset.ts
3
+ function normalizeDatasetKey(key) {
4
+ return Array.isArray(key) ? key : [key];
5
+ }
6
+ function formatKeyValue(value) {
7
+ return value.length === 1 ? String(value[0]) : `[${value.map((part) => String(part)).join(", ")}]`;
8
+ }
9
+ function buildKeyFingerprint(parts) {
10
+ return JSON.stringify(parts.map((part) => {
11
+ if (part instanceof Date) return {
12
+ type: "date",
13
+ value: part.toISOString()
14
+ };
15
+ return {
16
+ type: typeof part,
17
+ value: part
18
+ };
19
+ }));
20
+ }
21
+ function validateDatasetRows(key, rows, datasetLabel) {
22
+ if (!key || key.length === 0) return;
23
+ const seen = /* @__PURE__ */ new Map();
24
+ rows.forEach((row, index) => {
25
+ const parts = key.map((keyId) => row[keyId]);
26
+ const missingKeyId = parts.findIndex((part) => part == null);
27
+ if (missingKeyId >= 0) throw new Error(`Dataset "${datasetLabel}" key "${key.join(", ")}" is missing a value at row ${index} for "${key[missingKeyId]}".`);
28
+ const fingerprint = buildKeyFingerprint(parts);
29
+ if (seen.get(fingerprint)) throw new Error(`Dataset "${datasetLabel}" key "${key.join(", ")}" must be unique. Duplicate value: ${formatKeyValue(parts)}.`);
30
+ seen.set(fingerprint, parts);
31
+ });
32
+ }
33
+ function resolveDatasetDefinition(dataset) {
34
+ return dataset.build();
35
+ }
36
+ function validateDatasetData(dataset, rows, datasetLabel = "dataset") {
37
+ validateDatasetRows(resolveDatasetDefinition(dataset).key, rows, datasetLabel);
38
+ }
39
+ function createDefinedDataset(state) {
40
+ let cachedDataset;
41
+ const build = () => {
42
+ if (cachedDataset) return cachedDataset;
43
+ const definedDataset = {
44
+ ...state.key !== void 0 ? { key: state.key } : {},
45
+ ...state.columns !== void 0 ? { columns: state.columns } : {},
46
+ chart(id) {
47
+ return createDatasetChartBuilder({ ...state.columns !== void 0 ? { columns: state.columns } : {} }, {
48
+ dataset: definedDataset,
49
+ chartId: id
50
+ });
51
+ },
52
+ validateData(data) {
53
+ validateDatasetRows(state.key, data, "dataset");
54
+ },
55
+ build() {
56
+ return definedDataset;
57
+ },
58
+ __datasetBrand: "dataset-definition"
59
+ };
60
+ cachedDataset = definedDataset;
61
+ return definedDataset;
62
+ };
63
+ return build();
64
+ }
65
+ function createDatasetBuilder(state = {}) {
66
+ let cachedDataset;
67
+ const getOrBuildDataset = () => {
68
+ if (cachedDataset) return cachedDataset;
69
+ cachedDataset = createDefinedDataset(state);
70
+ return cachedDataset;
71
+ };
72
+ return {
73
+ key(keyOrKeys) {
74
+ return createDatasetBuilder({
75
+ ...state,
76
+ key: normalizeDatasetKey(keyOrKeys)
77
+ });
78
+ },
79
+ columns(defineColumns) {
80
+ const entries = defineColumns(COLUMN_HELPER);
81
+ assertColumnEntries(entries, "defineDataset");
82
+ return createDatasetBuilder({
83
+ ...state,
84
+ columns: buildColumnsMap(entries)
85
+ });
86
+ },
87
+ chart(id) {
88
+ return createDatasetChartBuilder({ ...state.columns !== void 0 ? { columns: state.columns } : {} }, {
89
+ dataset: getOrBuildDataset(),
90
+ chartId: id
91
+ });
92
+ },
93
+ validateData(data) {
94
+ validateDatasetRows(state.key, data, "dataset");
95
+ },
96
+ build() {
97
+ return getOrBuildDataset();
98
+ }
99
+ };
100
+ }
101
+ /**
102
+ * Define one reusable dataset contract for columns, derived fields, and row identity.
103
+ *
104
+ * Dataset-owned `.columns(...)` is the canonical reusable meaning.
105
+ * Use `.chart(...)` to derive chart definitions from that shared contract.
106
+ */
107
+ function defineDataset() {
108
+ return createDatasetBuilder();
109
+ }
110
+ //#endregion
111
+ export { defineDataset, resolveDatasetDefinition, validateDatasetData };
@@ -0,0 +1,17 @@
1
+ import { DateRangePresetId } from "./date-range-presets.mjs";
2
+ import { AggregateFunction, AggregateMetric, AggregateMetricAllowance, AvailableFilter, BooleanColumn, CategoricalChartType, CategoryColumn, ChartColumn, ChartColumnType, ChartControlMode, ChartDataScopeControlState, ChartDataScopeInputs, ChartDataScopeInputsFromSchemaDefinition, ChartDateRangeSelection, ChartInstance, ChartInstanceFromSchema, ChartInstanceFromSchemaDefinition, ChartSchema, ChartSchemaDefinition, ChartSeries, ChartSourceOptions, ChartType, ChartTypeConfig, ColumnFormat, ColumnFormatPreset, CountMetric, DateColumn, DateColumnFormat, DateRange, DateRangeFilter, DefinedChartSchema, DerivedBooleanColumnSchema, DerivedCategoryColumnSchema, DerivedColumnSchema, DerivedDateColumnSchema, DerivedNumberColumnSchema, DurationColumnFormat, DurationInputUnit, FilterState, FiltersConfig, GroupByConfig, InferableFieldKey, Metric, MetricAllowance, MetricConfig, MultiSourceChartDataScopeInputs, MultiSourceChartInstance, NumberColumn, NumberColumnFormat, NumericAggregateFunction, NumericAggregateSelection, RawColumnSchemaFor, RawColumnSchemaMap, ResolvedColumnIdFromSchema, ResolvedDateColumnIdFromSchema, ResolvedFilterColumnIdFromSchema, ResolvedGroupByColumnIdFromSchema, ResolvedMetricColumnIdFromSchema, ResolvedXAxisColumnIdFromSchema, RestrictedChartTypeFromSchema, RestrictedFilterColumnIdFromSchema, RestrictedGroupByColumnIdFromSchema, RestrictedMetricFromSchema, RestrictedTimeBucketFromSchema, RestrictedXAxisColumnIdFromSchema, SelectableControlConfig, SortConfig, SortDirection, TimeBucket, TimeBucketConfig, TimeSeriesChartType, TransformedDataPoint, ValidatedChartSchema, XAxisConfig } from "./types.mjs";
3
+ import { CHART_TYPE_CONFIG, ChartAxisType, ChartTypeCapabilities } from "./chart-capabilities.mjs";
4
+ import { buildColorMap, getSeriesColor } from "./colors.mjs";
5
+ import { DatasetBuilder, DatasetChartBuilder, DatasetChartDefinition, DatasetDefinition, DatasetKey, DatasetRow, DefinedDataset, DefinedDatasetChartSchema, SingleDatasetKeyId } from "./dataset-builder.types.mjs";
6
+ import { DefinedMaterializedView, MaterializedProjectableColumnId, MaterializedViewDefinition, MaterializedViewMetadata, MaterializedViewStepMetadata, ModelMaterializationBuilder, ModelMaterializationStartBuilder } from "./materialized-view.types.mjs";
7
+ import { DataModelBuilder, DataModelDefinition, DefinedDataModel, KeyedDatasetDefinition, ModelAssociationDefinition, ModelAttributeDefinition, ModelDataInput, ModelDatasetId, ModelRelationshipDefinition, ResolvedDataModelFromDefinition, SelectAttributeConfig } from "./data-model.types.mjs";
8
+ import { defineDataModel } from "./define-data-model.mjs";
9
+ import { DashboardBuilder, DashboardChartIdFromDefinition, DashboardChartInstanceFromDefinition, DashboardDataInputFromDefinition, DashboardDatasetIdFromDefinition, DashboardDatasetRowsFromDefinition, DashboardDateRangeSelection, DashboardDefinition, DashboardLocalSharedSelectFilterConfig, DashboardResolvedChart, DashboardResolvedChartOwnership, DashboardRuntime, DashboardSharedDateRangeFilterConfig, DashboardSharedDateRangeFilterDefinition, DashboardSharedDateRangeFilterRuntime, DashboardSharedDateRangePresetId, DashboardSharedDateRangeTarget, DashboardSharedFilterDefinition, DashboardSharedFilterIdFromDefinition, DashboardSharedFilterRuntime, DashboardSharedFilterRuntimeFromDefinition, DashboardSharedFilters, DashboardSharedSelectFilterRuntime, DashboardSharedSelectTarget, DefinedDashboard, ResolvedDashboardFromDefinition } from "./dashboard.types.mjs";
10
+ import { defineDashboard, resolveDashboardDefinition } from "./define-dashboard.mjs";
11
+ import { defineDataset, validateDatasetData } from "./define-dataset.mjs";
12
+ import { inferColumnsFromData } from "./infer-columns.mjs";
13
+ import { buildAvailableMetrics, getMetricLabel } from "./metric-utils.mjs";
14
+ import { PipelineInput, PipelineOutput, applyFilters, extractAvailableFilters, runPipeline } from "./pipeline.mjs";
15
+ import { DashboardProvider, useDashboard, useDashboardChart, useDashboardContext, useDashboardDataset, useDashboardSharedFilter } from "./use-dashboard.mjs";
16
+ import { UseChartOptions } from "./use-chart-options.mjs";
17
+ import { useChart } from "./use-chart.mjs";