@data360/chart-payload-normalize 0.0.1

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.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Chart URLs may return either a Charts API envelope `{ spec, title, ... }`
3
+ * or a raw Vega-Lite spec object (e.g. Data360 MCP static JSON).
4
+ */
5
+ export type NormalizedChartPayload = {
6
+ spec: Record<string, unknown>;
7
+ /** Main headline (matches Vega-Lite `title` string or `title.text`). */
8
+ title: string;
9
+ /**
10
+ * Vega-Lite compound title subtitle (geography · years · unit), when present.
11
+ * Prefer this for UI over tool `strategy`/`reason` metadata.
12
+ */
13
+ subtitle?: string;
14
+ };
15
+ /** Vega-Lite `{ title: { text, subtitle } }` → second line for chart cards. */
16
+ export declare function subtitleFromSpec(spec: Record<string, unknown>): string | undefined;
17
+ /**
18
+ * @throws Error if the JSON is not a supported chart payload
19
+ */
20
+ export declare function normalizeChartPayloadFromJson(data: unknown): NormalizedChartPayload;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAyCF,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAUlF;AAMD;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,OAAO,GACZ,sBAAsB,CAyDxB"}
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Chart URLs may return either a Charts API envelope `{ spec, title, ... }`
3
+ * or a raw Vega-Lite spec object (e.g. Data360 MCP static JSON).
4
+ */
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function looksLikeVegaLiteSpec(obj) {
9
+ if (typeof obj.$schema === "string" && /\bvega\b/i.test(obj.$schema)) {
10
+ return true;
11
+ }
12
+ if ("mark" in obj) {
13
+ return true;
14
+ }
15
+ if ("encoding" in obj && isRecord(obj.encoding)) {
16
+ return true;
17
+ }
18
+ // Composite / faceted specs (no top-level mark)
19
+ if ("layer" in obj) {
20
+ return true;
21
+ }
22
+ if ("concat" in obj || "hconcat" in obj || "vconcat" in obj) {
23
+ return true;
24
+ }
25
+ if ("facet" in obj || "repeat" in obj) {
26
+ return true;
27
+ }
28
+ // Note: do not treat bare `{ spec: ... }` as VL — that pattern is also Charts API envelopes.
29
+ return false;
30
+ }
31
+ function titleFromSpec(spec) {
32
+ const raw = spec.title;
33
+ if (typeof raw === "string") {
34
+ return raw;
35
+ }
36
+ if (isRecord(raw) && typeof raw.text === "string") {
37
+ return raw.text;
38
+ }
39
+ return "Chart";
40
+ }
41
+ /** Vega-Lite `{ title: { text, subtitle } }` → second line for chart cards. */
42
+ export function subtitleFromSpec(spec) {
43
+ const raw = spec.title;
44
+ if (!isRecord(raw)) {
45
+ return undefined;
46
+ }
47
+ const sub = raw.subtitle;
48
+ if (typeof sub === "string" && sub.trim()) {
49
+ return sub.trim();
50
+ }
51
+ return undefined;
52
+ }
53
+ function isLikelyChartsApiEnvelope(data) {
54
+ return typeof data.id === "string" || typeof data.createdAt === "string";
55
+ }
56
+ /**
57
+ * @throws Error if the JSON is not a supported chart payload
58
+ */
59
+ export function normalizeChartPayloadFromJson(data) {
60
+ if (!isRecord(data)) {
61
+ throw new Error("Chart JSON must be an object");
62
+ }
63
+ const nested = data.spec;
64
+ // Charts API: explicit metadata — unwrap nested `spec` only.
65
+ if (isRecord(nested) && isLikelyChartsApiEnvelope(data)) {
66
+ const title = typeof data.title === "string" && data.title.trim()
67
+ ? data.title
68
+ : titleFromSpec(nested);
69
+ return {
70
+ spec: nested,
71
+ title,
72
+ subtitle: subtitleFromSpec(nested),
73
+ };
74
+ }
75
+ // Raw Vega-Lite document at root (MCP static JSON, facet, layer, concat, …).
76
+ if (looksLikeVegaLiteSpec(data)) {
77
+ return {
78
+ spec: data,
79
+ title: titleFromSpec(data),
80
+ subtitle: subtitleFromSpec(data),
81
+ };
82
+ }
83
+ // Charts-style `{ title, spec }` without id/createdAt (unwrap nested VL only).
84
+ if (isRecord(nested) &&
85
+ typeof data.title === "string" &&
86
+ data.title.trim() &&
87
+ looksLikeVegaLiteSpec(nested)) {
88
+ return {
89
+ spec: nested,
90
+ title: data.title.trim(),
91
+ subtitle: subtitleFromSpec(nested),
92
+ };
93
+ }
94
+ // Nested `spec` that is clearly VL (legacy / loose envelopes).
95
+ if (isRecord(nested) && looksLikeVegaLiteSpec(nested)) {
96
+ const title = typeof data.title === "string" && data.title.trim()
97
+ ? data.title.trim()
98
+ : titleFromSpec(nested);
99
+ return {
100
+ spec: nested,
101
+ title,
102
+ subtitle: subtitleFromSpec(nested),
103
+ };
104
+ }
105
+ throw new Error("Chart JSON did not contain a Vega-Lite spec");
106
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@data360/chart-payload-normalize",
3
+ "version": "0.0.1",
4
+ "description": "Normalize Charts API envelopes and raw Vega-Lite JSON into spec + title for Data360 charts.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.0.0",
22
+ "typescript": "^5.5.0"
23
+ },
24
+ "keywords": [
25
+ "data360",
26
+ "vega-lite",
27
+ "world-bank"
28
+ ],
29
+ "license": "MIT",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/worldbank/data360-mcp"
36
+ }
37
+ }