@malloydata/malloyyo 0.2.23 → 0.2.25

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,117 @@
1
+ ---
2
+ name: malloyyo-auto-update
3
+ description: Keep a malloyyo GitHub-Pages data site current automatically with a weekly GitHub Actions job — download from the source URL, transform, export parquet, commit. Use after a site is built (see malloyyo-data-site) when the data comes from a public URL that refreshes over time and you want the published site to track it with no manual steps. Worked example: malloydata/malloyyo-imdb.
4
+ ---
5
+
6
+ # Auto-update a data site weekly
7
+
8
+ Add a scheduled GitHub Actions job that rebuilds the data and commits it, so the
9
+ published Pages site stays current on its own. Use this **after** the site works
10
+ (built with `malloyyo-data-site`). If the data never changes, skip this.
11
+
12
+ The worked example is `malloydata/malloyyo-imdb` — fetch and read its
13
+ `scripts/build_data.sh` and `.github/workflows/refresh-data.yml`; adapt, don't
14
+ copy blindly.
15
+
16
+ ## Precondition: one command that rebuilds the data
17
+
18
+ The whole thing rests on a single script that turns the source URL(s) into the
19
+ committed parquet — the same script you'd run by hand. Keep it linear so it
20
+ reads as a recipe:
21
+
22
+ ```bash
23
+ # scripts/build_data.sh
24
+ set -euo pipefail
25
+ cd "$(dirname "$0")/.."
26
+ bash data/get.sh # 1. download source URLs -> data/
27
+ rm -f data/build.duckdb
28
+ malloy-cli -c malloy-build.json build transform.malloy # 2. transform
29
+ echo "ATTACH 'data/build.duckdb' AS b (READ_ONLY); -- 3. export -> docs/*.parquet
30
+ COPY b.thing TO 'docs/thing.parquet' (FORMAT parquet)" | malloyyo sql
31
+ ```
32
+
33
+ (If you used Path A — direct — the whole script is just the one
34
+ `echo "COPY (FROM read_..('https://…')) TO 'docs/…'" | malloyyo sql` command.
35
+ No `data/get.sh`, `malloy-build.json`, `malloy-cli`, or transform needed — and
36
+ no standalone `duckdb` either, since `malloyyo sql` uses the embedded one.)
37
+
38
+ Gitignore the working files: `data/*.gz`, `data/*.duckdb`, `MANIFESTS/`.
39
+
40
+ ## The workflow
41
+
42
+ `.github/workflows/refresh-data.yml`:
43
+
44
+ ```yaml
45
+ name: refresh-data
46
+ on:
47
+ schedule:
48
+ - cron: "0 6 * * 0" # weekly, Sunday 06:00 UTC
49
+ workflow_dispatch: # ...and on demand from the Actions tab
50
+ jobs:
51
+ refresh:
52
+ runs-on: ubuntu-latest
53
+ permissions:
54
+ contents: write # so the job can commit + push
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ - uses: actions/setup-node@v4
58
+ with: { node-version: "20" }
59
+ # malloyyo carries its own DuckDB (`malloyyo sql`), so no duckdb CLI to install.
60
+ # Add `@malloydata/cli` only for a `#@ persist` transform (malloy-cli build).
61
+ - run: npm install -g @malloydata/malloyyo @malloydata/cli
62
+ - uses: actions/setup-python@v5 # only if an enrichment step needs it
63
+ with: { python-version: "3.12" }
64
+ - name: Build data
65
+ run: bash scripts/build_data.sh
66
+ - name: Commit refreshed data
67
+ run: |
68
+ git config user.name github-actions
69
+ git config user.email github-actions@github.com
70
+ git add docs/*.parquet
71
+ git commit -m "refresh data $(date -u +%F)" || exit 0 # unchanged = clean no-op
72
+ git push
73
+ ```
74
+
75
+ ## Hard-won details (these came from a real first run)
76
+
77
+ - **`git commit … || exit 0`** — an unchanged week means nothing new upstream.
78
+ That is success, not a failed job.
79
+ - **Secondary/enrichment steps must be best-effort.** If the build has a step
80
+ that can fail independently (an external API lookup, an optional metadata
81
+ enrichment) and it isn't essential to the data, mark it
82
+ `continue-on-error: true`. Otherwise one flaky API call throws away an
83
+ otherwise-good data refresh — the commit step never runs and the fresh parquet
84
+ is lost. (In `malloyyo-imdb` this bit us: a poster-image lookup with a missing
85
+ secret failed the whole job until it was made non-blocking.)
86
+ - **Secrets** go in repo settings (Settings → Secrets and variables → Actions,
87
+ or `gh secret set NAME`), referenced as `${{ secrets.NAME }}`. A step reading
88
+ a secret that isn't set gets an empty string — pair that with best-effort.
89
+ - **No re-bundle needed for data changes.** The dashboards fetch parquet at
90
+ runtime, so committing fresh parquet updates the live site. Only install
91
+ `@malloydata/malloyyo` and run `malloyyo dashboard bundle` in CI if the job
92
+ also needs to regenerate the HTML (i.e. dashboard code changed — rare for a
93
+ data refresh).
94
+ - **Test before trusting the schedule.** Push, then Actions tab → the workflow →
95
+ **Run workflow** (that's `workflow_dispatch`). Or `gh workflow run
96
+ refresh-data.yml`, then `gh run watch <id> --exit-status`.
97
+
98
+ ## Git growth, and the flatten
99
+
100
+ Each committed refresh adds the full parquet to history (~its file size per
101
+ run). That is intentional simplicity, not a leak. When history gets too big,
102
+ flatten it to a single commit by hand — **not** as part of the weekly job:
103
+
104
+ ```bash
105
+ git checkout --orphan flat && git add -A && git commit -m "flatten history"
106
+ git branch -M flat main && git push -f origin main
107
+ ```
108
+
109
+ Only do this on a repo you solely own (it force-pushes; other clones must
110
+ `git reset --hard origin/main`). Run it as often or as rarely as you like.
111
+ Document this in the repo's README so future-you remembers it's available.
112
+
113
+ ## Done when
114
+
115
+ - The workflow file is on the default branch (so `workflow_dispatch` shows up)
116
+ - A manual run goes green end-to-end and commits fresh `docs/*.parquet`
117
+ - The Pages site shows the new data
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: malloyyo-data-site
3
+ description: Turn a public data URL into a browsable, interactive dashboard site hosted on GitHub Pages, using Malloy (malloyyo). Use when someone points at data on the web (CSV/TSV/Parquet/JSON at an https URL) and wants a public web interface to explore it — scaffold the repo with `malloyyo init`, transform the data into parquet under docs/, write the Malloy model + dashboards, preview with `malloyyo dashboard dev`, build with `malloyyo dashboard bundle`, and publish on GitHub Pages. Worked examples: malloydata/malloyyo-babynames (base) and malloydata/malloyyo-imdb (adds a transform + weekly auto-update).
4
+ ---
5
+
6
+ # Build a public data site from a web data pointer
7
+
8
+ Goal: given one or more **public data URLs**, produce a **GitHub Pages site** of
9
+ interactive Malloy dashboards that anyone can open in a browser. The data ends
10
+ up as parquet in `docs/`; the dashboards are static HTML that query it
11
+ client-side with DuckDB-WASM — so there is no server, no database to run.
12
+
13
+ Two reference repos — read them, they are the ground truth for structure:
14
+
15
+ - **`malloydata/malloyyo-babynames`** — the clean base pattern (one model file,
16
+ a few dashboards, `docs/`). Data is static.
17
+ - **`malloydata/malloyyo-imdb`** — the same, plus a `transform.malloy` that
18
+ cleans raw source files into parquet, plus a **weekly auto-refresh** (that
19
+ part is the separate `malloyyo-auto-update` skill).
20
+
21
+ Fetch either with `gh api repos/<repo>/git/trees/HEAD?recursive=1` and read the
22
+ files you need. Match their layout rather than inventing your own.
23
+
24
+ ## The shape of a finished repo
25
+
26
+ ```
27
+ malloy-config.json connection(s): duckdb (local), optionally md/gs mirrors
28
+ index.malloy the EXPORT SURFACE — only what this file exports is live
29
+ <model>.malloy sources, measures, joins, givens (parameters)
30
+ storage.malloy sources point at the parquet — docs-local OR an https URL
31
+ (or gs.malloy / md.malloy) same source names, swappable hosting (step 3)
32
+ dashboards/*.malloy the query behind each dashboard
33
+ dashboards/*.jsx the dashboard layout (grid of charts/tables)
34
+ docs/ PUBLISHED SITE — bundled HTML (+ the *.parquet if docs-local)
35
+ *.parquet the data, when hosted docs-local (committed; served by Pages)
36
+ *.html .nojekyll written by `malloyyo dashboard bundle`
37
+ .mcp.json written by `malloyyo init` (author-mode Claude)
38
+ ```
39
+
40
+ ## Prerequisites (install once)
41
+
42
+ ```bash
43
+ npm install -g @malloydata/malloyyo # the `malloyyo` command
44
+ ```
45
+
46
+ That's the whole toolchain for the common case. `malloyyo` has **DuckDB built
47
+ in** — `malloyyo sql` runs SQL (read a URL, `COPY` to parquet) through it, so you
48
+ do **not** need a standalone `duckdb` CLI.
49
+
50
+ Add `@malloydata/cli` (`malloy-cli`) **only** if you do a heavier transform with
51
+ Malloy `#@ persist` (see Path B in `reference/data-to-parquet.md`):
52
+
53
+ ```bash
54
+ npm install -g @malloydata/cli # only for #@ persist transforms
55
+ ```
56
+
57
+ ## Recipe
58
+
59
+ Work top to bottom. After each step, prove it before moving on.
60
+
61
+ ### 1. Scaffold
62
+
63
+ ```bash
64
+ mkdir my-site && cd my-site && git init
65
+ malloyyo init # writes .mcp.json (author-mode Claude) + index.malloy stub
66
+ ```
67
+
68
+ Create `malloy-config.json` with a local DuckDB connection (this is what reads
69
+ the parquet):
70
+
71
+ ```json
72
+ { "connections": { "duckdb": { "is": "duckdb" } } }
73
+ ```
74
+
75
+ ### 2. Get the web data into `docs/*.parquet`
76
+
77
+ This is the one dataset-specific step. Two paths — pick the simpler one that
78
+ works. Full detail and copy-paste commands: **`reference/data-to-parquet.md`**.
79
+
80
+ - **Direct** (default): `malloyyo sql` reads the URL and writes parquet in one
81
+ shot, through the embedded DuckDB:
82
+ ```bash
83
+ echo "COPY (FROM read_csv_auto('https://…/thing.csv')) TO 'docs/thing.parquet'" | malloyyo sql
84
+ ```
85
+ Use when the web data is already close to what you want to show.
86
+ - **Transform** (when you need to clean / join / rank / reshape): write a
87
+ `transform.malloy` with `#@ persist` sources and build it with `malloy-cli`,
88
+ then export the tables to `docs/*.parquet`. This is the `malloyyo-imdb`
89
+ pattern.
90
+
91
+ Verify: `malloyyo sql -e "DESCRIBE SELECT * FROM 'docs/thing.parquet'"` and a
92
+ `SELECT count(*)`.
93
+
94
+ ### 3. Write the model
95
+
96
+ A storage file — one source per parquet file — that the rest of the model builds
97
+ on. **Where the parquet lives is a choice** (the two examples differ here):
98
+
99
+ - **docs-local** (the `malloyyo-imdb` way) — parquet committed in `docs/`,
100
+ served same-origin by Pages. Self-contained; git carries the data. Address it
101
+ by project-relative path so the same spelling works locally and published:
102
+ ```malloy
103
+ // storage.malloy
104
+ source: thing_table is duckdb.table('docs/thing.parquet') extend {}
105
+ ```
106
+ - **External URL** (the `malloyyo-babynames` way — it uses `import "gs.malloy"`)
107
+ — parquet hosted on GCS / a CDN / any https URL; `docs/` holds only the HTML,
108
+ so git stays small. DuckDB-WASM fetches the URL at runtime:
109
+ ```malloy
110
+ // gs.malloy
111
+ source: thing_table is duckdb.table('https://storage.googleapis.com/…/thing.parquet') extend {}
112
+ ```
113
+
114
+ Keep the **source names identical** across storage files (as babynames does with
115
+ `gs.malloy` / `md.malloy`) so switching hosting is a one-line import change in
116
+ the model. Start docs-local (simplest); move to a URL if git growth bites.
117
+
118
+ `<model>.malloy` — build real sources on top: primary keys, measures, joins,
119
+ and **givens** (the parameters that become the dashboard's filter controls).
120
+ `index.malloy` — re-export exactly the sources/queries/givens the dashboards
121
+ use. **Only what `index.malloy` exports is visible** to dashboards, `dashboard
122
+ dev`, and the hosted app.
123
+
124
+ For Malloy modeling and givens specifics, lean on the author MCP rather than
125
+ guessing: the repo's `.mcp.json` wires `mcp__malloyyo_author__*`. Call
126
+ `mcp__malloyyo_author__compile` to check files and
127
+ `mcp__malloyyo_author__yo_help` for topics (`develop/working-with-models`).
128
+
129
+ ### 4. Author dashboards and preview live
130
+
131
+ Each dashboard is a `.malloy` (the query/view) + a `.jsx` (the layout) under
132
+ `dashboards/`. Author them with the `malloyyo_author` MCP and its `yo_help`
133
+ topics — **read these, don't guess the JSX/grid API**:
134
+ `dashboards/authoring`, `dashboards/grid-layout`, `dashboards/vega-charts`.
135
+
136
+ Preview in a browser with live reload:
137
+
138
+ ```bash
139
+ malloyyo dashboard dev # serves at http://localhost:4173
140
+ ```
141
+
142
+ Iterate here until the dashboards look right. `malloyyo lint` validates the
143
+ dashboards against the model; `malloyyo test` previews what the hosted claude.ai
144
+ app would see.
145
+
146
+ ### 5. Build the static site
147
+
148
+ ```bash
149
+ malloyyo dashboard bundle --out docs
150
+ # optional: add analytics + a title
151
+ malloyyo dashboard bundle --out docs --title "My Site" --analytics G-XXXXXXXXXX
152
+ ```
153
+
154
+ `bundle` writes the HTML, `.nojekyll`, and assets into `docs/`, next to the
155
+ parquet from step 2. The pages fetch `./*.parquet` relative to the site root, so
156
+ the data must already be in `docs/` — always run the data step before bundling.
157
+
158
+ ### 6. Publish on GitHub Pages
159
+
160
+ Commit `docs/` and turn on Pages (Settings → Pages → Deploy from a branch →
161
+ `main` / `docs`). Full steps + gotchas: **`reference/publish-to-github-pages.md`**.
162
+
163
+ ### 7. (Optional) Keep it fresh automatically
164
+
165
+ If the data comes from a URL that updates over time and you want the site to
166
+ track it, add a weekly GitHub Actions refresh — see the **`malloyyo-auto-update`**
167
+ skill (worked example: `malloydata/malloyyo-imdb`).
168
+
169
+ ## Done when
170
+
171
+ - `malloyyo sql -e "SELECT count(*) FROM 'docs/<file>.parquet'"` returns real rows
172
+ - `malloyyo dashboard dev` renders every dashboard with no errors
173
+ - `docs/` has the bundled `*.html` + `.nojekyll` alongside the parquet
174
+ - the Pages URL loads and the dashboards populate (data fetch succeeds)
@@ -0,0 +1,109 @@
1
+ # Getting web data into `docs/*.parquet`
2
+
3
+ The site queries parquet files (in `docs/`, or at an https URL — see the model
4
+ step). This is how you turn a public data URL into that parquet. Pick the
5
+ simplest path that produces clean data.
6
+
7
+ `malloyyo sql` runs SQL through malloyyo's **embedded DuckDB** — no standalone
8
+ `duckdb` binary. It takes SQL from `-e`, `-f <file>`, or stdin, and runs against
9
+ a connection from `malloy-config.json` (default `duckdb`).
10
+
11
+ ## Path A — Direct (default)
12
+
13
+ One command reads the URL and writes parquet. Best when the web data is already
14
+ close to what you want to display.
15
+
16
+ ```bash
17
+ # CSV / TSV
18
+ echo "COPY (SELECT * FROM read_csv_auto('https://example.com/data.csv'))
19
+ TO 'docs/data.parquet' (FORMAT parquet)" | malloyyo sql
20
+
21
+ # TSV, gzipped, tab-delimited, header row (IMDb-style)
22
+ echo "COPY (SELECT * FROM read_csv_auto('https://example.com/data.tsv.gz',
23
+ delim='\t', header=true, all_varchar=true))
24
+ TO 'docs/data.parquet' (FORMAT parquet)" | malloyyo sql
25
+
26
+ # JSON / NDJSON
27
+ echo "COPY (SELECT * FROM read_json_auto('https://example.com/data.json'))
28
+ TO 'docs/data.parquet' (FORMAT parquet)" | malloyyo sql
29
+
30
+ # Already parquet on the web — just fetch it
31
+ curl -fsSL -o docs/data.parquet 'https://example.com/data.parquet'
32
+ ```
33
+
34
+ For anything longer than a line or two, put the SQL in a file and run it with
35
+ `-f`, so it's re-runnable and diffable:
36
+
37
+ ```bash
38
+ malloyyo sql -f scripts/build.sql # a file of ;-separated statements
39
+ ```
40
+
41
+ Notes:
42
+ - DuckDB autoloads `httpfs` for `https://` reads.
43
+ - Keep only the columns/rows the dashboards need — `SELECT` the columns and add a
44
+ `WHERE` to drop noise. Smaller parquet = faster page loads.
45
+ - Cast types here if the source is all-strings: `col::INT`, `col::DOUBLE`, etc.
46
+
47
+ ## Path B — Transform with Malloy (when you need to reshape)
48
+
49
+ Use when the data needs cleaning, joining across files, ranking, or nesting —
50
+ the `malloydata/malloyyo-imdb` case. You write the transform once in Malloy;
51
+ `malloy-cli build` materializes it; you export the tables to parquet. This path
52
+ also needs `@malloydata/cli` (`npm install -g @malloydata/cli`).
53
+
54
+ **1. A build connection** — `malloy-build.json` (a DuckDB db just for building):
55
+
56
+ ```json
57
+ { "connections": { "build": { "is": "duckdb", "databasePath": "data/build.duckdb" } } }
58
+ ```
59
+
60
+ **2. `transform.malloy`** — read the raw source(s) through the `build`
61
+ connection and mark each output source with `#@ persist name="…"`:
62
+
63
+ ```malloy
64
+ ##! experimental.persistence experimental.virtual_source
65
+
66
+ source: raw is build.sql("""
67
+ SELECT * FROM read_csv_auto('data/raw.csv.gz', delim='\t', all_varchar=true, header=true)
68
+ """)
69
+
70
+ #@ persist name="thing"
71
+ source: thing_base is raw -> {
72
+ where: some_count::number > 100
73
+ select: id, name, value is value::number
74
+ calculate: rank is rank() { order_by: value::number desc }
75
+ }
76
+ ```
77
+
78
+ Download the raw files first (a `data/get.sh` that `wget`s the URLs into `data/`,
79
+ gitignored). Gitignore `data/`, `data/*.duckdb`, and `MANIFESTS/`.
80
+
81
+ **3. Build, then export the persisted tables to `docs/`** — the export still
82
+ goes through `malloyyo sql` (no standalone duckdb needed):
83
+
84
+ ```bash
85
+ rm -f data/build.duckdb
86
+ malloy-cli -c malloy-build.json build transform.malloy # -> tables in build.duckdb
87
+ echo "ATTACH 'data/build.duckdb' AS b (READ_ONLY);
88
+ COPY b.thing TO 'docs/thing.parquet' (FORMAT parquet)" | malloyyo sql
89
+ ```
90
+
91
+ The persist `name=` is the table name inside `build.duckdb`; the `COPY` names the
92
+ served file (they can differ — e.g. `thing` → `docs/mysite_thing.parquet`).
93
+
94
+ Wrap steps 1–3 in a single `scripts/build_data.sh` so it is one command, by hand
95
+ and in CI. That's exactly what `malloyyo-auto-update` automates weekly.
96
+
97
+ ## Either way, verify
98
+
99
+ ```bash
100
+ malloyyo sql -e "DESCRIBE SELECT * FROM 'docs/thing.parquet'"
101
+ malloyyo sql -e "SELECT count(*) FROM 'docs/thing.parquet'"
102
+ ```
103
+
104
+ Then point a storage source at it (docs-local shown; an https URL works too —
105
+ see the model step in SKILL.md):
106
+
107
+ ```malloy
108
+ source: thing_table is duckdb.table('docs/thing.parquet') extend {}
109
+ ```
@@ -0,0 +1,61 @@
1
+ # Publishing the site on GitHub Pages
2
+
3
+ The published site is just the `docs/` directory: bundled HTML plus the parquet
4
+ it queries. GitHub Pages serves it directly — no build step on GitHub's side.
5
+
6
+ ## One-time setup
7
+
8
+ ```bash
9
+ # from the repo root, after `malloyyo dashboard bundle --out docs`
10
+ gh repo create <owner>/<name> --public --source=. --remote=origin # or an existing repo
11
+ git add -A
12
+ git commit -m "initial data site"
13
+ git push -u origin main
14
+ ```
15
+
16
+ Turn on Pages, pointing at the `docs/` folder on the default branch:
17
+
18
+ ```bash
19
+ gh api -X POST repos/<owner>/<name>/pages \
20
+ -f 'source[branch]=main' -f 'source[path]=/docs'
21
+ ```
22
+
23
+ (Or in the UI: Settings → Pages → Source: **Deploy from a branch** → Branch
24
+ `main`, folder `/docs`.)
25
+
26
+ The site appears at `https://<owner>.github.io/<name>/` within a minute or two.
27
+
28
+ ## Why `docs/` and why `.nojekyll`
29
+
30
+ - Serving from `/docs` on the main branch keeps the data committed **once**
31
+ (it's the same directory you build into), not duplicated on a separate branch.
32
+ - `malloyyo dashboard bundle` writes a `.nojekyll` file so Pages serves the
33
+ bundled assets as-is instead of running them through Jekyll (which would drop
34
+ files beginning with `_`). Keep it committed.
35
+
36
+ ## Updating the site
37
+
38
+ Re-run the data step and the bundle, then commit `docs/`:
39
+
40
+ ```bash
41
+ bash scripts/build_data.sh # or your Path-A duckdb command → docs/*.parquet
42
+ malloyyo dashboard bundle --out docs
43
+ git add docs && git commit -m "update" && git push
44
+ ```
45
+
46
+ Because the dashboards fetch the parquet client-side, **committing fresh parquet
47
+ is enough to update the live data** — you only need to re-`bundle` when you
48
+ change dashboard code. To automate the data refresh weekly, see the
49
+ `malloyyo-auto-update` skill.
50
+
51
+ ## Gotchas
52
+
53
+ - **Data must be in `docs/` before you bundle** — the pages fetch `./*.parquet`
54
+ relative to the site root; `bundle` also reads the parquet to get schemas.
55
+ - **Big parquet grows git history.** Each committed refresh adds the full file.
56
+ Fine for occasional updates; for frequent auto-refresh, see the flatten note
57
+ in `malloyyo-auto-update`.
58
+ - **Git LFS does not work with Pages "deploy from a branch"** — Pages serves the
59
+ LFS pointer text, not the file. Commit parquet as normal git objects.
60
+ - **Private repos**: Pages on private repos needs a paid plan. Use a public repo
61
+ for a public site.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloyyo",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "Publish Malloy models to a Malloyyo instance",
5
5
  "license": "MIT",
6
6
  "repository": {