Repository navigation
WQL Domain Query Composition
title: "WQL Evolution & Query Composition Guide" date: 2026-09-27 status: architectural-guide tags:
- wql
- domain-model
- query-composition
- typed-tags
- block-efforts
- architecture
This guide details how WQL (Wod Query Language) operates under the unified domain structure:
-
Dynamic Typed Tags as Filter Drivers & Typeahead Engines: How
domain,format,equipment,quality, andintentdrive declarative query filtering and interactive typeahead. -
Multi-Level Query Joins: How searches across
Page,Note,Block, andSession(events) execute their relational intersections. -
Block
$\leftrightarrow$ Effort Cross Table (block_efforts): An inverted index connecting parsed workout movements directly to notes and blocks for$O(1)$ exercise containment queries without scanning raw text or relying on execution history.
The unified domain cleanly separates placement, authored containers, structural script blocks, exercise containment, and telemetry:
[Page] (placement & routing anchor: date or slug)
│
▲ (N:M via `page_notes`)
│
[Note] (canonical authored document)
├── [BlockRevision] (immutable saved actionable-block states)
├── [BlockIndexRow] (searchable parsed blocks: wod, headings)
│ │
│ ▲ (N:M via `block_efforts`)
│ │
│ [Effort] (exercise definition: thruster, pull-up, snatch)
├── [Session] (workout execution metadata)
└── [EventRecord] (output telemetry facts)
│
▲ (N:M via `note_tags`)
│
[Tag] ─── (N:1) ───► [TagType] (`domain`, `format`, `equipment`, `quality`, `intent`)
-
Dynamic Typed Tags: Frontmatter keys (
domain,format,equipment,quality,intent) synchronize intoTag(withtype: TagType) andNoteTagrows. -
Effort Containment is Static: A workout's exercise composition is known at compile/save time from the AST (
parseScript), independent of whether the workout has ever been executed. -
Pages are Pure Placements: Pages contain no content or telemetry; they group notes via
page_notes. -
Notes Own Telemetry:
sessionsandeventslink directly tonoteIdandblockContentId.
Every registered tag type (TagTypeRecord.name) becomes an immediate, first-class filter key in WQL without requiring custom grammar keywords.
| Filter Syntax | Tag Type Target | Example Query | Resolution Mechanism |
|---|---|---|---|
domain:<slug> |
domain |
find:note{domain:crossfit} |
Matches Tag.type == 'domain' AND Tag.label == 'crossfit' via note_tags
|
format:<slug> |
format |
find:note{format:for-time} |
Matches Tag.type == 'format' AND Tag.label == 'for-time'
|
equipment:<slug> |
equipment |
find:note{equipment:kettlebell} |
Matches Tag.type == 'equipment' (multi-valued per note) |
quality:<slug> |
quality |
find:note{quality:strength} |
Matches Tag.type == 'quality' (multi-valued per note) |
intent:<slug> |
intent |
find:note{intent:benchmark} |
Matches Tag.type == 'intent' AND Tag.label == 'benchmark'
|
tags:<label> |
Any / Untyped | find:note{tags:girl} |
Matches any Tag.label regardless of type |
Within a query, filters follow standard WQL Boolean algebra:
-
Comma (AND across dimensions):
Resolves:
find:note{domain:crossfit, equipment:barbell, format:for-time}$\text{Notes}(\text{domain}=\text{crossfit}) \cap \text{Notes}(\text{equipment}=\text{barbell}) \cap \text{Notes}(\text{format}=\text{for-time})$ . -
Pipe (OR within a dimension):
Resolves:
find:note{equipment:kettlebell|clubs}$\text{Notes}(\text{equipment}=\text{kettlebell}) \cup \text{Notes}(\text{equipment}=\text{clubs})$ .
In @bitcobblers/wod-wiki-wql (language.ts), the CodeMirror extension dynamically supplies completions:
-
Filter Key Typeahead:
When the cursor is before a colon inside a filter block (
{...}):- In addition to static keys (
source:,text:,has:), queryIStorage.getAllTagTypes()orIFieldCatalog. - Suggests all registered tag type names:
domain:,format:,equipment:,quality:,intent:.
- In addition to static keys (
-
Filter Value Typeahead:
When typing after a typed key (e.g.
equipment:orformat:):- Look up the matching
TagTypeRecord. - Query
tagsStore.getAllFromIndex('by-type', tagTypeName). - Suggests all existing distinct values:
-
equipment:$\to$ kettlebell,barbell,dumbbell,pullup-bar,clubs,sandbag -
format:$\to$ for-time,amrap,emom,intervals,complex,circuit,skill -
domain:$\to$ crossfit,parkour,swimming,triathlon,climbing,girevoy-sport -
intent:$\to$ benchmark,competition,sport
-
- Look up the matching
Historically, finding which notes contain an exercise (e.g. thruster or pull-up) required:
- Scanning raw script text with regex/substrings (unreliable, misses aliases/synonyms), or
- Querying historical session telemetry events (fails entirely for unperformed seed wods).
A dedicated relational junction indexed in IndexedDB:
export interface BlockEffort {
id: string; // UUID or deterministic hash
noteId: string; // Parent Note UUID
blockId: string; // Positional block ID within note
blockContentId: string; // Structural content hash (SHA-256 of parsed block AST)
effortSlug: string; // Canonical exercise slug (e.g. 'thruster', 'pull-up')
reps?: number; // Nominal reps if statically declarable
load?: string; // Nominal load string (e.g. '95lb')
}-
by-effort(key:effortSlug)$\to$ Instant lookup of allnoteIds andblockContentIds containing the exercise. -
by-note(key:noteId)$\to$ Instant lookup of all exercises contained within a note. -
by-block(key:blockContentId)$\to$ All exercises within a specific block.
-
Seed Compiler (
scripts/generate-seed.ts): During build, for everywodsection parsed byparseDocumentSections():- Extract statement AST efforts:
section.scriptBlock.statements.map(s => s.effortSlug). - Emits precomputed
block-efforts.<n>.jsonchunks alongsideblock-index.
- Extract statement AST efforts:
-
User Save (
IndexedDBContentProvider.ts): When saving a user note:- Run
parseDocumentSections(rawContent). - Clear existing
block_effortsfornoteId. - Insert new rows for each detected effort statement.
- Run
WQL queries operate across 4 distinct target planes:
Level 1: Page (Placements & Groupings) find:page
Level 2: Note (Authored Workouts) find:note
Level 3: Block (Script Sections / WODs) find:block
Level 4: Fact (Telemetry & Executions) rows:all / rows:segment / sum:
┌─────────────────┐ page_notes
│ find:page │ ◄─────────────────────┐
└─────────────────┘ │
│
┌─────────────────┐ note_tags ▼ block_efforts
│ find:note │ ◄────────────────── [Note] ────────────────► [Effort]
└─────────────────┘ ▲
│ │
│ block_index │
▼ │
┌─────────────────┐ │
│ find:block │ ───────────────────────────────────────────────┘
└─────────────────┘
│
│ blockContentId / noteId
▼
┌─────────────────┐
│ rows: / agg: │ (Analytics Events & Telemetry)
└─────────────────┘
Finds collections, dashboards, or daily journal pages containing notes with specific tags, efforts, or metadata.
-
Example:
find:page{domain:crossfit, intent:benchmark} find:page{effort:clean-and-jerk} -
Join Resolution:
$$\text{Query Predicate } {k: v} \xrightarrow{\text{note_tags} \text{ or } \text{block_efforts}} \text{Candidate Note IDs } \xrightarrow[\text{by-note}]{\text{page_notes}} \text{Page IDs}$$
Finds authored workout notes by tag dimensions and exercise containment.
-
Example:
find:note{effort:thruster, equipment:barbell, format:for-time} -
Join Resolution:
-
$\text{Notes}_{\text{tags}} = \text{note_tags}(\text{equipment}=\text{barbell}) \cap \text{note_tags}(\text{format}=\text{for-time})$ . -
$\text{Notes}_{\text{effort}} = \text{block_efforts}(\text{effort}=\text{thruster})$ . - $\text{Result} = \text{Notes}{\text{tags}} \cap \text{Notes}{\text{effort}}$.
-
Discovers specific workout blocks within notes matching structural or note-level criteria.
-
Example 1: Direct Effort Containment:
Resolves directly via
find:block{effort:pull-up}block_efforts.getAllFromIndex('by-effort', 'pull-up')$\to$ returns matchingblockContentIds in$O(1)$ time. -
Example 2: Cross-Store Note Join:
Resolves:
find:block where find:note{domain:parkour, format:intervals}- Subquery
$\text{find:note} \to { \text{noteId}_1, \text{noteId}_2, \dots }$ . - Scans
block_indexwherenoteId IN (candidateNoteIds).
- Subquery
Computes performance metrics across workouts filtered by note metadata or exercise containment.
-
Example 1: Scoped Rows:
Resolves:
rows:segment{effort:snatch} where find:note{format:emom} last 8w- Subquery
$\text{find:note{format:emom}} \to { \text{noteId}_k }$ . - Filters
eventstable wherenoteId IN (noteIds) AND effortSlug == 'snatch' AND timestamp >= now - 8w.
- Subquery
-
Example 2: Cross-Store Metric Join:
Calculates cumulative thruster tonnage strictly across benchmark CrossFit workouts.
sum:totalVolume{effort:thruster} where find:note{domain:crossfit, intent:benchmark}
| Analytical Question | Old WQL (V10-V20) | New WQL (V24+ with Typed Tags & Block Efforts) | Internal Execution Optimization |
|---|---|---|---|
| Find all workouts using a kettlebell | find:note{tags:kettlebell} |
find:note{equipment:kettlebell} |
Indexed typed tag lookup via note_tags. |
| Find benchmark workouts with thrusters | find:note{text:thruster, tags:benchmark} |
find:note{effort:thruster, intent:benchmark} |
Set intersection between block_efforts and note_tags; zero regex scanning. |
| Find all EMOM workouts containing pullups |
find:block{text:emom} (fragile)
|
find:block{effort:pull-up} where find:note{format:emom} |
Exact effort containment joined with note format tag. |
| Find collections containing swimming workouts | Not expressible | find:page{domain:swimming} |
Joins note_tags('domain', 'swimming') page_notes page. |
| Total volume on benchmark days | sum:totalVolume{tags:benchmark} |
sum:totalVolume{} where find:note{intent:benchmark} |
Joins telemetry fact stream to candidate notes. |
-
block_effortsTable & Compiler:- Add
block_effortsstore toIndexedDBStorage.tsandSeedImportStorage.ts. - Update
scripts/generate-seed.tsto emit precomputedblock-efforts.<n>.jsonchunks. - Update
IndexedDBContentProvider.tsto syncblock_effortson note edit/save.
- Add
-
WQL Grammar & Vocabulary Update:
- In
packages/wql/src/vocabulary.ts, promotedomain,format,equipment,quality,intentto canonical filter keys. - Update
QueryService.tsto resolvefind:note{effort:...}andfind:block{effort:...}using theblock_effortsstore.
- In
-
Typeahead Completion Service:
- In
packages/wql/src/language.ts, updatetagValueOptionsto query distinct values from thetagsstore dynamically based on the active key.
- In