@malloydata/malloyyo 0.2.12 → 0.2.14
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/dist/index.js +106 -64
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -945,7 +945,9 @@ async function run(runtime, entry, opts = {}) {
|
|
|
945
945
|
return { ok: false, problems: [errorProblem(e, entry.href)] };
|
|
946
946
|
}
|
|
947
947
|
let query;
|
|
948
|
-
if (opts.
|
|
948
|
+
if (opts.runExpr !== void 0) {
|
|
949
|
+
query = materializer.loadQuery(`run: ${opts.runExpr}`);
|
|
950
|
+
} else if (opts.name !== void 0) {
|
|
949
951
|
if (!modelQueries.named.includes(opts.name)) {
|
|
950
952
|
return {
|
|
951
953
|
ok: false,
|
|
@@ -1129,18 +1131,10 @@ function describeGivenSpec(name, g) {
|
|
|
1129
1131
|
}
|
|
1130
1132
|
return spec;
|
|
1131
1133
|
}
|
|
1132
|
-
async function dashboardGivenSpecs(runtime, entry,
|
|
1134
|
+
async function dashboardGivenSpecs(runtime, entry, runExpr) {
|
|
1133
1135
|
try {
|
|
1134
1136
|
const mm = runtime.loadModel(entry);
|
|
1135
|
-
const
|
|
1136
|
-
const named = [...model.queries().named];
|
|
1137
|
-
if (!named.includes(queryName)) {
|
|
1138
|
-
return {
|
|
1139
|
-
ok: false,
|
|
1140
|
-
error: `no query named '${queryName}' (model has: ${named.join(", ") || "none"})`
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
const pq = await mm.loadQueryByName(queryName).getPreparedQuery();
|
|
1137
|
+
const pq = await mm.loadQuery(`run: ${runExpr}`).getPreparedQuery();
|
|
1144
1138
|
const specs = [];
|
|
1145
1139
|
for (const [name, g] of pq.givens) {
|
|
1146
1140
|
specs.push(describeGivenSpec(name, g));
|
|
@@ -1158,7 +1152,7 @@ function docText(t) {
|
|
|
1158
1152
|
return void 0;
|
|
1159
1153
|
}
|
|
1160
1154
|
}
|
|
1161
|
-
function readArtifactTag(
|
|
1155
|
+
function readArtifactTag(ident, q) {
|
|
1162
1156
|
let tag;
|
|
1163
1157
|
try {
|
|
1164
1158
|
tag = q.annotations.parseAsTag().tag;
|
|
@@ -1168,9 +1162,11 @@ function readArtifactTag(queryName, q) {
|
|
|
1168
1162
|
if (!tag.has("artifact")) return void 0;
|
|
1169
1163
|
const nested = tag.tag("artifact");
|
|
1170
1164
|
const description = docText(q);
|
|
1171
|
-
const name = nested?.text("name") ?? tag.text("name") ??
|
|
1172
|
-
const title = nested?.text("title") ?? tag.text("title") ?? description?.split("\n")[0] ??
|
|
1173
|
-
const info = { name, query:
|
|
1165
|
+
const name = nested?.text("name") ?? tag.text("name") ?? ident.defaultName;
|
|
1166
|
+
const title = nested?.text("title") ?? tag.text("title") ?? description?.split("\n")[0] ?? ident.defaultName;
|
|
1167
|
+
const info = { name, query: ident.runExpr, title };
|
|
1168
|
+
if (ident.source) info.source = ident.source;
|
|
1169
|
+
if (ident.view) info.view = ident.view;
|
|
1174
1170
|
if (description) info.description = description;
|
|
1175
1171
|
const givensTag = tag.tag("artifact", "givens");
|
|
1176
1172
|
if (givensTag) {
|
|
@@ -1191,9 +1187,25 @@ async function artifactQueries(runtime, entry) {
|
|
|
1191
1187
|
const artifacts = [];
|
|
1192
1188
|
for (const queryName of model.queries().named) {
|
|
1193
1189
|
const pq = model.getPreparedQueryByName(queryName);
|
|
1194
|
-
const info = readArtifactTag(queryName, pq);
|
|
1190
|
+
const info = readArtifactTag({ runExpr: queryName, defaultName: queryName }, pq);
|
|
1195
1191
|
if (info) artifacts.push(info);
|
|
1196
1192
|
}
|
|
1193
|
+
for (const src of model.explores) {
|
|
1194
|
+
for (const field of src.allFields) {
|
|
1195
|
+
if (!field.isQueryField()) continue;
|
|
1196
|
+
const view = field;
|
|
1197
|
+
const info = readArtifactTag(
|
|
1198
|
+
{
|
|
1199
|
+
runExpr: `${src.name} -> ${view.name}`,
|
|
1200
|
+
defaultName: view.name,
|
|
1201
|
+
source: src.name,
|
|
1202
|
+
view: view.name
|
|
1203
|
+
},
|
|
1204
|
+
view
|
|
1205
|
+
);
|
|
1206
|
+
if (info) artifacts.push(info);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1197
1209
|
return { ok: true, artifacts };
|
|
1198
1210
|
} catch (e) {
|
|
1199
1211
|
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
@@ -1680,9 +1692,9 @@ async function makeRunner(root) {
|
|
|
1680
1692
|
return {
|
|
1681
1693
|
root: abs,
|
|
1682
1694
|
entryExists: () => fs.existsSync(path2.join(abs, ENTRY)),
|
|
1683
|
-
run(
|
|
1695
|
+
run(runExpr, givens) {
|
|
1684
1696
|
return lease(
|
|
1685
|
-
(runtime, entry) => run(runtime, entry, {
|
|
1697
|
+
(runtime, entry) => run(runtime, entry, { runExpr, givens, stableResult: true, rowLimit: 5e3 })
|
|
1686
1698
|
);
|
|
1687
1699
|
},
|
|
1688
1700
|
runText(malloy, givens) {
|
|
@@ -1698,22 +1710,16 @@ async function makeRunner(root) {
|
|
|
1698
1710
|
return { ok: false, error: msg || "restricted query failed to compile" };
|
|
1699
1711
|
});
|
|
1700
1712
|
},
|
|
1701
|
-
givensForQuery(
|
|
1702
|
-
return lease((runtime, entry) => dashboardGivenSpecs(runtime, entry,
|
|
1713
|
+
givensForQuery(runExpr) {
|
|
1714
|
+
return lease((runtime, entry) => dashboardGivenSpecs(runtime, entry, runExpr));
|
|
1703
1715
|
},
|
|
1704
1716
|
artifacts() {
|
|
1705
1717
|
return lease((runtime, entry) => artifactQueries(runtime, entry));
|
|
1706
1718
|
},
|
|
1707
|
-
validate(
|
|
1719
|
+
validate(runExpr, givens) {
|
|
1708
1720
|
return lease(async (runtime, entry) => {
|
|
1709
1721
|
try {
|
|
1710
|
-
const
|
|
1711
|
-
const model = await mm.getModel();
|
|
1712
|
-
const named = [...model.queries().named];
|
|
1713
|
-
if (!named.includes(queryName)) {
|
|
1714
|
-
return { ok: false, error: `no query named '${queryName}' (model has: ${named.join(", ") || "none"})` };
|
|
1715
|
-
}
|
|
1716
|
-
const q = mm.loadQueryByName(queryName);
|
|
1722
|
+
const q = runtime.loadModel(entry).loadQuery(`run: ${runExpr}`);
|
|
1717
1723
|
const has = givens && Object.keys(givens).length > 0;
|
|
1718
1724
|
await q.getSQL(has ? { givens } : void 0);
|
|
1719
1725
|
return { ok: true };
|
|
@@ -1760,6 +1766,8 @@ async function gatherDashboards(dir) {
|
|
|
1760
1766
|
return res.artifacts.map((a) => {
|
|
1761
1767
|
const tsxPath = join2(dir, "dashboards", a.name, "Dashboard.tsx");
|
|
1762
1768
|
const manifest = { title: a.title, query: a.query };
|
|
1769
|
+
if (a.source) manifest.source = a.source;
|
|
1770
|
+
if (a.view) manifest.view = a.view;
|
|
1763
1771
|
if (a.description) manifest.description = a.description;
|
|
1764
1772
|
if (a.givens) manifest.givens = a.givens;
|
|
1765
1773
|
return {
|
|
@@ -2174,9 +2182,11 @@ with \`malloyyo lint\`.
|
|
|
2174
2182
|
server only see what that file EXPORTS. Three things must all be surfaced
|
|
2175
2183
|
(imported AND exported) through it, or the feature looks broken:
|
|
2176
2184
|
|
|
2177
|
-
1. **
|
|
2178
|
-
|
|
2179
|
-
|
|
2185
|
+
1. **Whatever holds each \`# artifact\` tag.** A tag on a \`view:\` rides along
|
|
2186
|
+
with its SOURCE (export the source \u2014 you can't export a view on its own); a
|
|
2187
|
+
tag on a top-level \`query:\` needs that query exported. Not surfaced \u2192
|
|
2188
|
+
\`dashboard dev\` says "No dashboards declared" and \`lint\` says "no dashboards
|
|
2189
|
+
to lint", even though the model compiles clean.
|
|
2180
2190
|
2. **Every filter given the dashboards reference.** An unexported given
|
|
2181
2191
|
silently resolves to its declaration default \u2014 the control still renders
|
|
2182
2192
|
but CAN'T CHANGE THE QUERY (the filter looks inert).
|
|
@@ -2187,49 +2197,61 @@ server only see what that file EXPORTS. Three things must all be surfaced
|
|
|
2187
2197
|
\`\`\`malloy
|
|
2188
2198
|
##! experimental.givens
|
|
2189
2199
|
import {
|
|
2190
|
-
order_items,
|
|
2200
|
+
order_items, // the source \u2014 carries its # artifact views
|
|
2191
2201
|
BRAND, CATEGORY, PERIOD, // the filter givens
|
|
2192
|
-
brand_suggest
|
|
2193
|
-
overview_dashboard // the # artifact query
|
|
2202
|
+
brand_suggest // backs a suggest {query=\u2026}
|
|
2194
2203
|
} from 'ecommerce.malloy'
|
|
2195
|
-
export { order_items, BRAND, CATEGORY, PERIOD, brand_suggest
|
|
2204
|
+
export { order_items, BRAND, CATEGORY, PERIOD, brand_suggest }
|
|
2196
2205
|
\`\`\`
|
|
2197
2206
|
|
|
2207
|
+
Exporting the source is often the whole job: its \`# artifact\` views, its
|
|
2208
|
+
dimensions (for \`suggest {source=\u2026}\`), and its measures all travel with it.
|
|
2209
|
+
|
|
2198
2210
|
Prefer \`suggest { query=<named-query> \u2026 }\` over \`suggest { source=\u2026 }\` for
|
|
2199
2211
|
anything beyond a throwaway: you export one small governed query instead of a
|
|
2200
2212
|
whole base source.
|
|
2201
2213
|
|
|
2202
2214
|
## The model is the whole contract
|
|
2203
2215
|
|
|
2204
|
-
**1. Tag a
|
|
2205
|
-
the
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2216
|
+
**1. Tag a \`view:\` inside a source** with \`# artifact\` to declare a dashboard
|
|
2217
|
+
(the idiomatic form \u2014 a view is reusable, nestable, and explorable through the
|
|
2218
|
+
normal \`query\`/\`describe_source\` surface). For the common overview shape
|
|
2219
|
+
(top-level aggregates + nests), ALSO tag it \`# dashboard\` so the result
|
|
2220
|
+
renders as KPI tiles + a card grid instead of one flat table \u2014 they're
|
|
2221
|
+
partners: \`# artifact\` declares the dashboard, \`# dashboard\` is the renderer
|
|
2222
|
+
tag that draws it like one:
|
|
2209
2223
|
|
|
2210
2224
|
\`\`\`malloy
|
|
2211
|
-
|
|
2212
|
-
#
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
+
source: order_items is \u2026 extend {
|
|
2226
|
+
#" Business health at a glance \u2014 sales, margin, orders.
|
|
2227
|
+
# artifact { title="Business Overview" } dashboard
|
|
2228
|
+
view: overview_dashboard is {
|
|
2229
|
+
where:
|
|
2230
|
+
inventory_items.product_brand ~ $BRAND, // multi-filter where: is
|
|
2231
|
+
inventory_items.product_category ~ $CATEGORY, // COMMA separated
|
|
2232
|
+
created_at ~ $PERIOD
|
|
2233
|
+
aggregate: total_sales, total_gross_margin, order_count
|
|
2234
|
+
nest:
|
|
2235
|
+
# line_chart
|
|
2236
|
+
sales_trend is by_month
|
|
2237
|
+
top_brands
|
|
2238
|
+
# shape_map
|
|
2239
|
+
sales_by_state
|
|
2240
|
+
}
|
|
2225
2241
|
}
|
|
2226
2242
|
\`\`\`
|
|
2227
2243
|
|
|
2228
2244
|
That's a complete dashboard: the runtime auto-renders a title (the tag's
|
|
2229
|
-
\`title\`, else the \`#"\` doc comment), a control for every given the
|
|
2230
|
-
references, and the result panel. \`
|
|
2231
|
-
|
|
2232
|
-
|
|
2245
|
+
\`title\`, else the \`#"\` doc comment), a control for every given the view
|
|
2246
|
+
references, and the result panel. It runs as \`run: <source> -> <view>\` (here
|
|
2247
|
+
\`order_items -> overview_dashboard\`). \`name="slug"\` overrides the
|
|
2248
|
+
URL/directory slug (default: the view name). Note the \`where:\` clauses
|
|
2249
|
+
applying givens are COMMA-separated \u2014 newline-separated conditions do not
|
|
2250
|
+
parse.
|
|
2251
|
+
|
|
2252
|
+
Tagging a **top-level \`query:\`** still works and behaves identically (it runs
|
|
2253
|
+
as \`run: <name>\`) \u2014 reach for it only when the dashboard query doesn't belong
|
|
2254
|
+
to any one source.
|
|
2233
2255
|
|
|
2234
2256
|
Two dashboards can share a given but start on different values \u2014 a \`givens\`
|
|
2235
2257
|
block in the tag sets PER-DASHBOARD defaults (given values, i.e. filter
|
|
@@ -2282,6 +2304,25 @@ equals, not colon):
|
|
|
2282
2304
|
(\`\u2026 + { where: lower(field) ~ f'll%'; limit: 50 }\`, case-insensitive,
|
|
2283
2305
|
escaped). Without a dimension the fetched list is filtered client-side.
|
|
2284
2306
|
Runs as a restricted query; lint checks the declaration compiles.
|
|
2307
|
+
|
|
2308
|
+
**RELATED (faceted) filters** \u2014 query-form only: a suggest query may
|
|
2309
|
+
reference the OTHER givens, and the runtime runs it with the dashboard's
|
|
2310
|
+
current values (the suggested given itself is excluded, so the list never
|
|
2311
|
+
collapses to the current pick). Brand suggestions narrow when Category is
|
|
2312
|
+
set:
|
|
2313
|
+
|
|
2314
|
+
\`\`\`malloy
|
|
2315
|
+
query: brand_suggest is inventory_items -> product_brand + {
|
|
2316
|
+
where:
|
|
2317
|
+
product_category ~ $CATEGORY, // NOT product_brand ~ $BRAND
|
|
2318
|
+
product_department ~ $DEPARTMENT
|
|
2319
|
+
limit: 500
|
|
2320
|
+
}
|
|
2321
|
+
\`\`\`
|
|
2322
|
+
|
|
2323
|
+
Declare one \`*_suggest\` per filter, each referencing the others; \`f''\`
|
|
2324
|
+
defaults mean unset filters don't constrain. \`source=\` suggests can't do
|
|
2325
|
+
this (no place for a \`where:\`) \u2014 another reason to prefer \`query=\`.
|
|
2285
2326
|
- \`control=select\` \u2014 a fixed dropdown instead of a typeahead search box
|
|
2286
2327
|
- \`range_min\` / \`range_max\` \u2014 bounds; makes a filter<number> given a
|
|
2287
2328
|
dual-thumb range slider
|
|
@@ -2381,11 +2422,12 @@ usually means the \`# artifact\` queries aren't exported through
|
|
|
2381
2422
|
|
|
2382
2423
|
Validation loop that works well: the local \`malloyyo mcp\` server hot-reloads
|
|
2383
2424
|
working-directory edits \u2014 \`query(execute:false)\` to compile-check,
|
|
2384
|
-
\`execute:true\` to run. A
|
|
2385
|
-
\`run: <
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2425
|
+
\`execute:true\` to run. A \`# artifact\` view runs as
|
|
2426
|
+
\`run: <source> -> <view>\`; a top-level \`# artifact\` query runs as
|
|
2427
|
+
\`run: <name>\`. Either is only visible once surfaced through the entry (export
|
|
2428
|
+
the source for a view, the query for a top-level query). Don't validate local
|
|
2429
|
+
edits against a hosted/claude.ai connector \u2014 that serves the PUBLISHED model,
|
|
2430
|
+
which is stale until \`malloyyo publish\`.
|
|
2389
2431
|
`;
|
|
2390
2432
|
|
|
2391
2433
|
// src/mcp.ts
|
|
@@ -2771,7 +2813,7 @@ async function serveDashboard(opts) {
|
|
|
2771
2813
|
}
|
|
2772
2814
|
|
|
2773
2815
|
// package.json
|
|
2774
|
-
var version = "0.2.
|
|
2816
|
+
var version = "0.2.14";
|
|
2775
2817
|
|
|
2776
2818
|
// src/index.ts
|
|
2777
2819
|
function shortSha(sha) {
|