@trilogy-data/trilogy-studio-components 0.1.26 → 0.1.28

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 (145) hide show
  1. package/dist/AssetAutoImporter-C_7Ms9xe.js +403 -0
  2. package/dist/{ChatCreatorModal-CL-jBnc3.js → ChatCreatorModal-Cd40SvdM.js} +3 -3
  3. package/dist/{ChatModelSelect-Do-VRUFc.js → ChatModelSelect-Cz7axWjP.js} +1 -1
  4. package/dist/{CodeBlock-PT4no49I.js → CodeBlock-Dj-GfTbW.js} +4 -4
  5. package/dist/{CommunityModels-Cmro0hV3.js → CommunityModels-lX2RhHCG.js} +7 -7
  6. package/dist/{ConnectionErrorPopup-FSs7U-Yl.js → ConnectionErrorPopup-7vpqgF0s.js} +1 -1
  7. package/dist/{ConnectionView-COnTTuA4.js → ConnectionView-CZ06qjjE.js} +49 -49
  8. package/dist/{CreateEditorFromDatasourcePopup-snOh1d5h.js → CreateEditorFromDatasourcePopup-DwA938kf.js} +4 -4
  9. package/dist/{Dashboard-BH9ZwMVJ.js → Dashboard-CQsIqWZC.js} +276 -280
  10. package/dist/{DashboardImportSelector-DQSvu9T-.js → DashboardImportSelector-CkqH52FX.js} +2 -2
  11. package/dist/{DashboardMobile-BUPtdN2V.js → DashboardMobile-DWFlel5p.js} +39 -39
  12. package/dist/{DrilldownPane-CBs5ZojS.js → DrilldownPane-BheRHJMs.js} +5 -7
  13. package/dist/{Editor-DQsYpkwl.js → Editor-DQ66Vp6v.js} +105 -103
  14. package/dist/{ErrorMessage-XMn_08gJ.js → ErrorMessage-D5JeF3Iz.js} +4 -4
  15. package/dist/{GlobalChatPanel-DhnOaige.js → GlobalChatPanel-B_s3COHE.js} +20 -21
  16. package/dist/{IDE-HLa8vybk.js → IDE-4-Jg6gsM.js} +200 -203
  17. package/dist/{JobsView-B97HBoIE.js → JobsView-B5NWDUgP.js} +13 -14
  18. package/dist/{LLMChat-CxJcPLEG.js → LLMChat-eKgEc-I3.js} +3 -3
  19. package/dist/{LLMView-C1MUf0c2.js → LLMView-CdTwMLVV.js} +23 -23
  20. package/dist/{LoadingView-s1uznGwG.js → LoadingView-Bu3wLJ7J.js} +2 -2
  21. package/dist/{MarkdownRenderer-BNDuEyG-.js → MarkdownRenderer-B6lqdkgc.js} +531 -430
  22. package/dist/{MobileIDE-BpqXoaMR.js → MobileIDE-CZlg20oa.js} +179 -182
  23. package/dist/{ModelCard-BhN25jaV.js → ModelCard-Bs_d7Jis.js} +12 -13
  24. package/dist/{ModelView-DUDA_PtZ.js → ModelView-BP5yyecx.js} +7 -7
  25. package/dist/PageLoading-DKFeqkmK.js +5937 -0
  26. package/dist/{ResultComponent-CAn_r3BL.js → ResultComponent-BaDheojB.js} +8 -8
  27. package/dist/{Results-BJtlwebY.js → Results-CrpHB101.js} +17 -19
  28. package/dist/{StatusIcon-BylPx2EO.js → StatusIcon-Clfyy-Yn.js} +2 -2
  29. package/dist/{StoreTokenModal-zCSYhfrw.js → StoreTokenModal-CO5hXMs3.js} +2 -2
  30. package/dist/{SymbolsPane-KYSwN9Sv.js → SymbolsPane-XFVErDFb.js} +6 -3
  31. package/dist/{TrilogyEmbedProvider-WA_dlv9e.js → TrilogyEmbedProvider-D3h0gYnX.js} +9 -9
  32. package/dist/{TutorialPage-NRcee6Dx.js → TutorialPage-CMcTYDOV.js} +27 -28
  33. package/dist/{VegaLiteChart-D1slfOUY.js → VegaLiteChart-DX_LbF3g.js} +12131 -10947
  34. package/dist/{WelcomePage-D5mU2x1L.js → WelcomePage-B6E35FUa.js} +5 -5
  35. package/dist/{embedTheme-Bub46ILG.js → _plugin-vue_export-helper-f9Rw-Il4.js} +14 -6
  36. package/dist/assets/{css.worker-9aDGcEUy.js → css.worker-niORQtzO.js} +14 -14
  37. package/dist/assets/{html.worker-D2LKDpnG.js → html.worker-CO5LvJpF.js} +17 -17
  38. package/dist/assets/json.worker-BezHzWUV.js +58 -0
  39. package/dist/assets/{ts.worker-Bz0-EaKK.js → ts.worker-CYhxGg0r.js} +107 -107
  40. package/dist/{results-DTgAKr5j.js → base-DuKnVD-A.js} +216 -45
  41. package/dist/components/FeedbackBanner.vue.d.ts +4 -0
  42. package/dist/components/FeedbackBanner.vue.d.ts.map +1 -0
  43. package/dist/components/dashboard/DashboardCreatorIcon.vue.d.ts +54 -0
  44. package/dist/components/dashboard/DashboardCreatorIcon.vue.d.ts.map +1 -0
  45. package/dist/components/dashboard/DashboardProvenance.vue.d.ts +30 -0
  46. package/dist/components/dashboard/DashboardProvenance.vue.d.ts.map +1 -0
  47. package/dist/components/dashboard/ReportLayout.vue.d.ts +19 -0
  48. package/dist/components/dashboard/ReportLayout.vue.d.ts.map +1 -0
  49. package/dist/components/editor/Editor.vue.d.ts +3 -3
  50. package/dist/components/editor/EditorCreatorIcon.vue.d.ts +62 -0
  51. package/dist/components/editor/EditorCreatorIcon.vue.d.ts.map +1 -0
  52. package/dist/components/editor/ValidationComponent.vue.d.ts +50 -0
  53. package/dist/components/editor/ValidationComponent.vue.d.ts.map +1 -0
  54. package/dist/components/model/ModelCard.vue.d.ts +3 -3
  55. package/dist/components/sidebar/LLMConnectionList.vue.d.ts.map +1 -1
  56. package/dist/components/sidebar/Sidebar.vue.d.ts.map +1 -1
  57. package/dist/components/tutorial/TutorialPrompt.vue.d.ts +3 -3
  58. package/dist/components/tutorial/TutorialPrompt.vue.d.ts.map +1 -1
  59. package/dist/{remoteWorker-Dkv3TJ0j.js → connections-CWH3zwH2.js} +4544 -4126
  60. package/dist/connections.js +3 -4
  61. package/dist/{cssMode-D4Rzr1Cu.js → cssMode-zpO2ap2B.js} +5 -6
  62. package/dist/dashboard.js +28 -31
  63. package/dist/dashboards/dashboardQueryExecutor.d.ts +9 -0
  64. package/dist/dashboards/dashboardQueryExecutor.d.ts.map +1 -1
  65. package/dist/{dist-CXoL8kXK.js → dist-1z5KURzS.js} +55 -50
  66. package/dist/{dist-DKkuvbPb.js → dist-Bv3D2p5A.js} +20 -18
  67. package/dist/{editor.main-Cl8i7rHY.js → editor.main-BQh6DxDB.js} +33 -33
  68. package/dist/{editorRefinementRuntime-DoP9P5J2.js → editorStore-CiWH_nGN.js} +1692 -575
  69. package/dist/embed.js +2 -2
  70. package/dist/{freemarker2-fW5NBILf.js → freemarker2-DQBqlEXc.js} +1 -1
  71. package/dist/{globalChatRuntime-DWZxeS57.js → globalChatRuntime-DAERmOdu.js} +16 -16
  72. package/dist/{handlebars-dG4w8UUY.js → handlebars-tgYxaM70.js} +1 -1
  73. package/dist/{helpers-BLQAgX3C.js → helpers-C2pARJoZ.js} +34 -35
  74. package/dist/{html-1Dq70BdU.js → html-jJahD3Mi.js} +1 -1
  75. package/dist/{htmlMode--IQmLBeh.js → htmlMode-BA1-8l2e.js} +5 -6
  76. package/dist/{javascript-CTzBM4sF.js → javascript-Cypfo7va.js} +1 -1
  77. package/dist/{jsonMode-DzeJjIEH.js → jsonMode-xJye8PWS.js} +25 -25
  78. package/dist/{liquid-DlHThiki.js → liquid-BGDrv5da.js} +1 -1
  79. package/dist/llm/data/prompts.d.ts +1 -1
  80. package/dist/llm/data/prompts.d.ts.map +1 -1
  81. package/dist/llm/data/trilogySyntax.generated.d.ts +3 -3
  82. package/dist/llm/data/trilogySyntax.generated.d.ts.map +1 -1
  83. package/dist/llm-Wf4Etiu9.js +18493 -0
  84. package/dist/llm.js +2 -3
  85. package/dist/{localStorage-D9uyOFEO.js → localStorage-C3xms-ab.js} +40 -43
  86. package/dist/{lspLanguageFeatures-Cdfs3EKq.js → lspLanguageFeatures-V3e_p9oN.js} +32 -32
  87. package/dist/{mdx-fn3g4PH5.js → mdx-BUR1aImW.js} +1 -1
  88. package/dist/monaco.js +4 -4
  89. package/dist/{python-CCVj0pFt.js → python-YLkl0pc6.js} +1 -1
  90. package/dist/{razor-Cj3flYnk.js → razor-BP8TRMDY.js} +1 -1
  91. package/dist/{reconcile-Dyf4UZmj.js → reconcile-BH_ReGDR.js} +1 -1
  92. package/dist/{remoteStoreSync-Z-yjlYtI.js → remoteStoreSync-B0K8QScO.js} +3 -3
  93. package/dist/{rolldown-runtime-Dqa2HsxW.js → rolldown-runtime-B0aSnxlc.js} +2 -2
  94. package/dist/static/test_import.vue.d.ts +4 -0
  95. package/dist/static/test_import.vue.d.ts.map +1 -0
  96. package/dist/stores-DTGQZ4_B.js +2612 -0
  97. package/dist/stores.js +4 -7
  98. package/dist/trilogy-studio-components.css +1 -1
  99. package/dist/{tsMode-GI4xMYAl.js → tsMode-BH9HS_sH.js} +64 -66
  100. package/dist/{typescript-CPibXi3J.js → typescript-BM-ynfk9.js} +1 -1
  101. package/dist/{useChatWithTools-B0HZ_DVh.js → useChatWithTools-BSrzJtYI.js} +11 -11
  102. package/dist/{useDashboard-CdSnPe_6.js → useDashboard-CGeIdDuX.js} +1014 -1022
  103. package/dist/{tabulator_esm-CxLvGDp8.js → useGlobalChatPanel-D9kMZLeF.js} +1729 -711
  104. package/dist/{useResizableDialog-B0ePSts4.js → useResizableDialog-Cs90WJdF.js} +2 -2
  105. package/dist/views/ModelView.vue.d.ts +6 -6
  106. package/dist/views/ModelView.vue.d.ts.map +1 -1
  107. package/dist/views/TutorialPage.vue.d.ts +9 -9
  108. package/dist/views/TutorialPage.vue.d.ts.map +1 -1
  109. package/dist/views.js +192 -194
  110. package/dist/{vocabulary-CKLGILtR.js → vocabulary-DQK5jB7m.js} +3 -1
  111. package/dist/{editor.api2-ClD5pp8H.js → workers-B9Eybl-M.js} +16252 -15926
  112. package/dist/{xml-BNyphceI.js → xml-M5WFPSpA.js} +1 -1
  113. package/dist/{yaml-DE5Vz0PH.js → yaml-CEeFwjz9.js} +1 -1
  114. package/package.json +29 -20
  115. package/dist/AssetAutoImporter-CBzvBaOt.js +0 -405
  116. package/dist/DashboardCreatorInline-CDU7kLy3.js +0 -129
  117. package/dist/DataTable-DoRDynP1.js +0 -365
  118. package/dist/EditorCreatorInline-D6_y1H_h.js +0 -125
  119. package/dist/LoadingButton-OH3frQuO.js +0 -138
  120. package/dist/ModalDialog-D1EOG4rl.js +0 -42
  121. package/dist/ModelCreator-CCdC7EXt.js +0 -253
  122. package/dist/PageLoading-BzniJM0C.js +0 -2669
  123. package/dist/Tooltip-Dihir4zs.js +0 -103
  124. package/dist/_plugin-vue_export-helper-B3ysoDQm.js +0 -8
  125. package/dist/assets/json.worker-CJ5MmO5Q.js +0 -58
  126. package/dist/base-C8IuZWqY.js +0 -164
  127. package/dist/connectionHistoryStorage-DKJHiKJY.js +0 -111
  128. package/dist/connectionHistoryStore-DQeBvStZ.js +0 -61
  129. package/dist/connections-CJSb1TZl.js +0 -229
  130. package/dist/constants-DgFEYN2b.js +0 -7
  131. package/dist/dashboardStore-ppM6nGks.js +0 -641
  132. package/dist/demoSetup-mwa-RDC6.js +0 -2942
  133. package/dist/editorRefinementToolExecutor-CVY3FA7k.js +0 -4624
  134. package/dist/editorStore-BhhmXbi-.js +0 -1019
  135. package/dist/formatting-BbJ5ICgh.js +0 -6
  136. package/dist/llm-BBOVWb-F.js +0 -11967
  137. package/dist/llmStore-DJM2JhzY.js +0 -153
  138. package/dist/monaco.contribution-ikesdolO.js +0 -128
  139. package/dist/stores-BT9hTLny.js +0 -1708
  140. package/dist/trilogy-BroD2Qmp.js +0 -4
  141. package/dist/urlStore-52HG6lsX.js +0 -55
  142. package/dist/useGlobalChatPanel-D_0luajQ.js +0 -85
  143. package/dist/useIsMobile-CNpUQAvk.js +0 -8
  144. package/dist/usePrismHighlight-DVyDiDAL.js +0 -183
  145. package/dist/workers-Br7ThUKL.js +0 -47
@@ -1,6 +1,6 @@
1
- export declare const pytrilogySyntaxVersion = "0.3.370";
2
- export declare const trilogySyntaxReference = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` \u2014 replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; \u2014 a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a \u2286 b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order \u2014 all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection \u2014 grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation \u2014 it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`, and joined back like any concept (`subset join foo.key = other.key`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL \u2014 what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT \u2014 never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select \u2014 hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain \u2014 a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** \u2014 a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column) \u2014 never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` \u2261 `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages \u2014 give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship \u2014 the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology \u2014 use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE \u2014 its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` \u2192 leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows \u2014 use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain \u2014 on a select item (`~customer.id`) or in `merge a into ~b` (a \u2286 b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary \u2014 wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end \u2212 start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.\n\n\nFunctions (the `aggregate` family doubles as the Aggregate Functions list):\naggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)\nstring: concat|concat_strict|concat_ws(<arg1>, <arg2>, ..., <argN>) ; split|strpos|contains|trim|regexp_contains|format_time|parse_time(<arg1>, <arg2>) ; len|lower|upper|ltrim|rtrim|hex(<arg1>) ; substring|replace|regexp_extract|regexp_replace(<arg1>, <arg2>, <arg3>)\ndate/time: date|datetime|timestamp|second|minute|hour|day|day_of_week|week|month|quarter|year|month_name|day_name|unix_to_timestamp(<arg1>) ; date_spine(<arg1>, <arg2>) ; current_date|current_datetime()\n date_part(<arg1>, <arg2>) e.g. date_part('2020-01-01'::date, year)\n date_truncate(<arg1>, <arg2>) e.g. date_trunc('2020-01-01'::date, month)\n date_add(<arg1>, <arg2>, <arg3>) e.g. date_add('2020-01-01'::date, month, 1)\n date_sub(<arg1>, <arg2>, <arg3>) e.g. date_sub('2020-01-01'::date, day, 1)\n date_diff(<arg1>, <arg2>, <arg3>) e.g. date_diff('2020-01-01'::date, '2020-01-02'::date, day)\n current_timestamp() e.g. now()\narray/map/struct: unnest|array_distinct|array_sum|map_keys|map_values(<arg1>) ; struct|array(<arg1>, <arg2>, ..., <argN>) ; array_sort|array_to_string(<arg1>, <arg2>) ; array_transform|array_filter|generate_array(<arg1>, <arg2>, <arg3>)\nmath: mod|round|log|power|hash(<arg1>, <arg2>) ; abs|sqrt|random|floor|ceil(<arg1>)\nconditional/cast: case|simple_case|coalesce|greatest|least(<arg1>, <arg2>, ..., <argN>) ; cast|nullif(<arg1>, <arg2>) ; isnull(<arg1>)\ngeo: geo_from_text|geo_x|geo_y|geo_centroid(<arg1>) ; geo_point|geo_distance(<arg1>, <arg2>) ; geo_transform(<arg1>, <arg2>, <arg3>)\nother: is_not_distinct(<arg1>, <arg2>)\n\nSome types carry trait metadata that adds meaning: 'latitude', 'longitude' and 'currency' are all 'float' with extra semantics.\n\nAdditional syntax examples:\nThese less-common patterns have complete, copy-pasteable examples. Do NOT guess\nthe syntax \u2014 print the full example on demand with\n`trilogy agent-info syntax example <name>`:\n- `python-datasource` - run a local Python script as a datasource: wrap a function in `trilogy.io.run`, which writes the Arrow IPC stream to stdout for you from a table, dataframe, or list of dicts; declare concepts, map script columns in `datasource (...)`, use `grain (...) file `path.py`;`, then reference locally declared concepts WITHOUT the datasource name as a prefix\n- `query-structure` - the exact clause order of a query, and how to define a rowset\n- `chart` - drawing a result rather than tabulating it: `chart layer <type> ( <role> <- <expr> as <name>, ... )` is a STATEMENT that wraps its own query - the bindings ARE the select; layers, roles, scales, reference lines and `from <select>`\n- `filtered-aggregate` - aggregating only the rows matching a condition, and counting rows correctly\n- `row-level-where-vs-having` - choosing between WHERE and HAVING for a threshold condition\n- `nested-aggregate-group-average` - the \"above 1.2x the group norm\" ask: inner `sum(x) by entity, group`, outer `avg(inner) by group`; use WHERE for the comparison when a separate output-only filter must not narrow the benchmark\n- `pivot-columns` - turn distinct values of a dimension into columns with a reusable `def` macro + filtered aggregate\n- `case-when` - `case when <cond> then <val> ... else <val> end` to relabel codes or bucket a measure into a derived value, then group by it\n- `window-period-over-period` - `lead`/`lag` over (order by ...) to compare a row to an offset period; build a scoped set with `<col> ? <cond>` and filter with `in`\n- `union-stack-channels` - stacking several sources/channels together as rows\n- `except-intersect-setops` - set difference or co-occurrence across sources\n- `scoped-join` - blending a second fact model or rowset into one query\n- `existence-anti-join` - keeping or excluding rows by whether a key matches another model\n- `set-intersect-difference` - count tuples present in / absent from other groups via per-group presence flags - the multi-column INTERSECT/EXCEPT idiom\n- `dedup-before-aggregate` - a `rowset <- select <cols>` collapses identical tuples to one row (SELECT-grain group by) before a downstream sum - the `distinct`/UNION substitute\n- `rollup` - subtotals and a grand total in one pass, and labeling the levels\n- `rank-over-rollup` - rank rollup subtotals/leaves with a SINGLE `rank(a,b) over (partition by level, parent ...)` - not separate ranks per level\n- `staged-membership` - filtering a fine-grain query by a set computed at a coarser grain\n- `correlated-exists-via-grouped-counts` - translate `EXISTS other` / `NOT EXISTS other matching` over the same model into two `count(...) by <grain>` compared in `where` (`> 1` = another exists, `= 1` = no other matches) - pin the correlation grain with `by`";
3
- export declare const rulesInput = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` \u2014 replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; \u2014 a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a \u2286 b; b authoritative for the key) or `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` declares EXACT domain equivalence. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order \u2014 all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection \u2014 grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation \u2014 it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`, and joined back like any concept (`subset join foo.key = other.key`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL \u2014 what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT \u2014 never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select \u2014 hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain \u2014 a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** \u2014 a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column) \u2014 never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` \u2261 `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages \u2014 give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship \u2014 the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology \u2014 use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE \u2014 its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` \u2192 leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows \u2014 use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain \u2014 on a select item (`~customer.id`) or in `merge a into ~b` (a \u2286 b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary \u2014 wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end \u2212 start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.";
1
+ export declare const pytrilogySyntaxVersion = "0.3.391";
2
+ export declare const trilogySyntaxReference = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` \u2014 replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; \u2014 a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union\\|equal join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a \u2286 b; b authoritative for the key), `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept) or `equal join a = b` (ONE domain: a is an alias of b, every value on either side is on the other; the join renders inner).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` is the persistent equivalent of `equal join a = b`. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order \u2014 all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection \u2014 grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union|equal join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation \u2014 it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`. Pairing an output with a concept outside the rowset REQUIRES a declared join on the rowset's key (`subset join foo.key = key`); without one the query is disconnected. Alternatively project the concept inside the rowset and filter or select it through the handle (`where foo.cat = 'a'`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL \u2014 what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT \u2014 never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select \u2014 hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain \u2014 a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** \u2014 a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column; a scalar must also be one row: an aggregate with no `by`, or `limit 1`) \u2014 never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` \u2261 `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages \u2014 give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship \u2014 the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology \u2014 use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE \u2014 its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` \u2192 leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows \u2014 use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain \u2014 on a select item (`~customer.id`) or in `merge a into ~b` (a \u2286 b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary \u2014 wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end \u2212 start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.\n\n\nFunctions (the `aggregate` family doubles as the Aggregate Functions list):\naggregate: count|count_distinct|sum|max|min|avg|stddev|variance|array_agg|bool_or|bool_and|any|grouping(<arg1>) ; grouping_id(<arg1>, <arg2>, ..., <argN>)\nstring: concat|concat_strict|concat_ws(<arg1>, <arg2>, ..., <argN>) ; split|strpos|contains|trim|regexp_contains|format_time|parse_time(<arg1>, <arg2>) ; len|lower|upper|ltrim|rtrim|hex(<arg1>) ; substring|replace|regexp_extract|regexp_replace(<arg1>, <arg2>, <arg3>)\ndate/time: date|datetime|timestamp|second|minute|hour|day|day_of_week|week|month|quarter|year|month_name|day_name|unix_to_timestamp(<arg1>) ; date_spine(<arg1>, <arg2>) ; current_date|current_datetime()\n date_part(<arg1>, <arg2>) e.g. date_part('2020-01-01'::date, year)\n date_truncate(<arg1>, <arg2>) e.g. date_trunc('2020-01-01'::date, month)\n date_add(<arg1>, <arg2>, <arg3>) e.g. date_add('2020-01-01'::date, month, 1)\n date_sub(<arg1>, <arg2>, <arg3>) e.g. date_sub('2020-01-01'::date, day, 1)\n date_diff(<arg1>, <arg2>, <arg3>) e.g. date_diff('2020-01-01'::date, '2020-01-02'::date, day)\n current_timestamp() e.g. now()\narray/map/struct: unnest|array_distinct|array_sum|map_keys|map_values(<arg1>) ; struct|array(<arg1>, <arg2>, ..., <argN>) ; array_sort|array_to_string(<arg1>, <arg2>) ; array_transform|array_filter|generate_array(<arg1>, <arg2>, <arg3>)\nmath: mod|round|log|power|hash(<arg1>, <arg2>) ; abs|sqrt|random|floor|ceil(<arg1>)\nconditional/cast: case|simple_case|coalesce|greatest|least(<arg1>, <arg2>, ..., <argN>) ; cast|nullif(<arg1>, <arg2>) ; isnull(<arg1>)\ngeo: geo_from_text|geo_x|geo_y|geo_centroid(<arg1>) ; geo_point|geo_distance(<arg1>, <arg2>) ; geo_transform(<arg1>, <arg2>, <arg3>)\nother: is_not_distinct(<arg1>, <arg2>) ; prior(<arg1>) ; grain_pin(<arg1>, <arg2>, ..., <argN>)\n\nSome types carry trait metadata that adds meaning: 'latitude', 'longitude' and 'currency' are all 'float' with extra semantics.\n\nAdditional syntax examples:\nThese less-common patterns have complete, copy-pasteable examples. Do NOT guess\nthe syntax \u2014 print the full example on demand with\n`trilogy agent-info syntax example <name>`:\n- `python-datasource` - run a local Python script as a datasource: wrap a function in `trilogy.io.run`, which writes the Arrow IPC stream to stdout for you from a table, dataframe, or list of dicts; declare concepts, map script columns in `datasource (...)`, use `grain (...) file `path.py`;`, then reference locally declared concepts WITHOUT the datasource name as a prefix\n- `query-structure` - the exact clause order of a query, and how to define a rowset\n- `chart` - drawing a result rather than tabulating it: `chart layer <type> ( <role> <- <expr> as <name>, ... )` is a STATEMENT that wraps its own query - the bindings ARE the select; layers, roles, scales, reference lines and `from <select>`\n- `filtered-aggregate` - aggregating only the rows matching a condition, and counting rows correctly\n- `row-level-where-vs-having` - choosing between WHERE and HAVING for a threshold condition\n- `nested-aggregate-group-average` - the \"above 1.2x the group norm\" ask: inner `sum(x) by entity, group`, outer `avg(inner) by group`; use WHERE for the comparison when a separate output-only filter must not narrow the benchmark\n- `pivot-columns` - turn distinct values of a dimension into columns with a reusable `def` macro + filtered aggregate\n- `case-when` - `case when <cond> then <val> ... else <val> end` to relabel codes or bucket a measure into a derived value, then group by it\n- `window-period-over-period` - `lead`/`lag` over (order by ...) to compare a row to an offset period; build a scoped set with `<col> ? <cond>` and filter with `in`\n- `union-stack-channels` - stacking several sources/channels together as rows\n- `except-intersect-setops` - set difference or co-occurrence across sources\n- `scoped-join` - blending a second fact model or rowset into one query\n- `existence-anti-join` - keeping or excluding rows by whether a key matches another model\n- `set-intersect-difference` - count tuples present in / absent from other groups via per-group presence flags - the multi-column INTERSECT/EXCEPT idiom\n- `dedup-before-aggregate` - a `rowset <- select <cols>` collapses identical tuples to one row (SELECT-grain group by) before a downstream sum - the `distinct`/UNION substitute\n- `rollup` - subtotals and a grand total in one pass, and labeling the levels\n- `rank-over-rollup` - rank rollup subtotals/leaves with a SINGLE `rank(a,b) over (partition by level, parent ...)` - not separate ranks per level\n- `staged-membership` - filtering a fine-grain query by a set computed at a coarser grain\n- `correlated-exists-via-grouped-counts` - translate `EXISTS other` / `NOT EXISTS other matching` over the same model into two `count(...) by <grain>` compared in `where` (`> 1` = another exists, `= 1` = no other matches) - pin the correlation grain with `by`";
3
+ export declare const rulesInput = "# Trilogy Syntax\n\nTrilogy statements define a semantic model or a query; collections of statements\nare written as `.preql` files. A statement returns data (SELECT) or defines\nconcepts (key | property | auto | metric), datasources, or parameters.\n\nimport <model> as <alias>; makes a model's fields available. <model> is a DOT-SEPARATED MODULE PATH relative to the project root (where trilogy.toml lives), never a file path: the file `raw/store_sales.preql` is imported as `import raw.store_sales as ss;` \u2014 replace each `/` with `.`, drop the `.preql`, keep the folder segment. When an imported model imports others (a fact with imported dimensions), those are dot paths: `enroll.student.name`. Typical usage imports ONE fact and reaches dimensions through it; nested dimensions carry ALL values (order.customers includes customers without orders).\n\nkey | property | auto | metric; define new concepts. `auto x <- ...` is a DEFINITION, not a precomputed value: each reference expands and re-evaluates in the consuming query's scope.\n\nparameter NAME TYPE [default <literal>]; \u2014 a runtime value supplied via `--param NAME=VALUE` (repeatable); append `?` to the type to allow null.\n\n## Combining models\n\n| Goal | Use |\n|---|---|\n| Typical query | no merge, no join: access all fields through dot-paths |\n| Blend two models on shared keys inside one query | scoped `subset\\|union\\|equal join` |\n| Make a connection universal to a whole file | `merge` |\n| Stack subsets/channels as rows | `union(...)` |\n| Rows in A but never in B (set difference) | `except(...)` |\n| Rows present in every source (set intersection) | `intersect(...)` |\n\n### Query-scoped join\n\nA typical fact already has its dimensions merged in, so no join is needed. Blending fact models or rowset outputs takes a scoped join, placed right after the select list, which DECLARES how the key domains relate: `subset join a = b` (a's values are contained in b's, a \u2286 b; b authoritative for the key), `union join a = b` (neither contains the other; the key is the coalesce of both sides and unmatched rows from BOTH sides are kept) or `equal join a = b` (ONE domain: a is an alias of b, every value on either side is on the other; the join renders inner).\n\nTwo rules decide whether the numbers are right, so apply them before reaching for the example:\n\n- Joins NEVER drop a row and NULL keys match null-safely; there is NO inner join. An intersection is an explicit `where <optional-side attr> is not null`.\n- JOIN ON THE FULL GRAIN: `explore` prints each fact's grain as @<k1, k2>; write one clause PER key. Matching only one key of a multi-key grain causes duplication.\n\nChaining, expression keys, union/subset mixing, multi-key and self-pair shapes: trilogy agent-info syntax example scoped-join.\n\nmerge <a> into ~<b>; is the persistent (whole-file) equivalent of `subset join a = b`; plain `merge a into b;` is the persistent equivalent of `equal join a = b`. Standalone statements; prefer a scoped join unless the connection is universal.\n\n### union / except / intersect (row set operations)\n\n`union(...)` row-stacks self-contained select arms positionally (SQL UNION ALL) into one named rowset (`with combined as union((armA), (armB), ...) -> (out1, out2, ...)`); `except(...)` / `intersect(...)` share that arm shape but are SQL SET operators (DISTINCT rows, null-safe whole-row compare, and except subtracts later arms from the FIRST, so arm order matters). Prefer except over a multi-column `not in` for \"in A but never in B\". Each arm dedups to its own output grain BEFORE stacking, so aggregate INSIDE each arm or the total silently undersums. Examples: trilogy agent-info syntax example union-stack-channels / except-intersect-setops.\n\n## Query structure\n\nTop-level statements first (each `;`-terminated), then a query with clauses in this EXACT order \u2014 all optional except select:\n\n```\n<top-level statements>; # import / key / auto / rowset / merge / def / parameter\nwith <name> as # optional: name the select as a reusable rowset (CTE)\nwhere <row condition> # 1. filters INPUT rows, BEFORE aggregation\nselect <col>, <agg> as name, # 2. projection \u2014 grouping is AUTOMATIC by the\n # non-aggregated columns; never write GROUP BY\n subset|union|equal join a = b (= c)? # 3. blend models; one clause per key, right after the select list\nby rollup|cube|grouping sets (...) # 4. optional multi-level grouping for the whole select\nhaving <result condition> # 5. filters aggregated/joined RESULTS\norder by <col> asc|desc # 6. sort\nlimit <n>; # 7. cap rows\n```\n\nA rowset (`with <name> as where ... select ...;`) is a standalone statement, evaluated in isolation \u2014 it does NOT respond to the consuming query's context. All its outputs are namespaced: output `abc.def` of rowset `foo` is referenced as `foo.abc.def`. Pairing an output with a concept outside the rowset REQUIRES a declared join on the rowset's key (`subset join foo.key = key`); without one the query is disconnected. Alternatively project the concept inside the rowset and filter or select it through the handle (`where foo.cat = 'a'`). Alias every reused expression with `as`.\n\nFull annotated example: trilogy agent-info syntax example query-structure.\n\n### Not SQL \u2014 what to never write\n\n- **No FROM, GROUP BY, DISTINCT, SELECT \\*, or SQL-style set operators between selects.** Stack rows with `union(...)`; set difference/intersection with `except(...)`/`intersect(...)`; blend fact models with a scoped join.\n- **Grouping is automatic** by the non-aggregated fields in the SELECT \u2014 never write GROUP BY. Aggregates inherit the select's grain wherever they appear (where/select/having). Override per-aggregate with `agg(x) by <dims>`; `by *` aggregates across all data (single row). To weight a related dimension property once per fact row, project it to the fact grain first: `auto row_birth_year <- group(enroll.student.birth_year) by enroll.student_id, enroll.course_id;` then aggregate that.\n- **Output rows are deduplicated to the select grain.** To preserve legitimate duplicates (one row per matching fact), include the fact's grain keys in the select \u2014 hidden with the PREFIX `--` if they shouldn't appear in the output.\n- **Never write `distinct`.** `count(<key>)` is already distinct because keys are unique; `count_distinct(<property>)` counts distinct values of a non-key property.\n- **Count a COMPOSITE grain with `grain(...)`.** `count(grain(order_id, item.id))` counts order+item combinations; `grain(...)` is NEVER NULL. Never count one column of a multi-key grain \u2014 a coarser key counts its own distinct values and undercounts. `count_distinct(a, b)` is accepted as sugar for `count(grain(a, b))` (distinct combinations, NULL members included).\n- **An aggregate over a group with no qualifying rows is NULL, never 0.** `sum(x ? cond) by k` yields NULL for groups where nothing matches, so `= 0` matches nothing. Test emptiness with `count(key ? cond) by k = 0` or wrap: `coalesce(sum(x ? cond), 0)`.\n- **`count(1)` is invalid** \u2014 a constant does not identify rows. Use the row key, `count(grain(...))`, or `sum(1) by <grain>`.\n- **One-column expression subqueries only.** `(select ...)` is allowed where a scalar or membership set is expected (exactly one projected column; a scalar must also be one row: an aggregate with no `by`, or `limit 1`) \u2014 never as a SQL-style FROM-subquery. For related-entity filters prefer the dot-path: `where enrollments.student.state = 'TN'`, not a subselect.\n- **`--` is a HIDDEN-field prefix, not a comment** (it still changes query structure). Comments use `#` only.\n- **Aliases**: always use the full dot-path (`enroll.student.id`); alias every new expression with `as`; a select alias is usable in `having`/`order by` but NOT inside other select expressions or `where`, and must not rename a field back to an existing concept name.\n\n## Filtering\n\n`where` filters input rows before aggregation; `having` filters results after aggregation/windows. The inline filter `x ? cond` filters the immediate prior expression: `sum(credits ? enrolled = true)`.\n\nAggregates/windows in WHERE do not filter each other's inputs. Use inline filters, or a staged `then where` chain: each `then where` stage's aggregates compute over only the rows passing earlier stages (`where x = 5 then where sum(y) > 10` \u2261 `where x = 5 and sum(y ? x = 5) > 10`). One rule: the SAME cross-row expression cannot gate two different stages \u2014 give one an inline filter.\n\nWindows need all rows in their range: filter AFTER the window (in `having`) rather than in `where` when the window must see the full set.\n\n## Membership (semijoins)\n\n`in` / `not in` cross models without any declared relationship \u2014 the semijoin IS a scoped intersection. The right side is a concept or expression (never a subselect); build computed sets with an inline filter: `auto big_zip <- student.zip ? (count(student.id ? student.honors = true) by student.zip) > 10; ... where school.zip in big_zip`.\n\nMembership is IDENTITY matching, not SQL three-valued logic: NULL matches NULL; the result is always TRUE or FALSE (safe as a projected flag); `not in` is the exact complement (a NULL in the set never empties the result). For strict SQL behavior, add `x is not null`.\n\nA fact model contains ALL dimensional members, so `where students.id not in enroll.student_id` is typically a tautology \u2014 use `where enroll.id is null` instead. Tuple membership `(a, b) in (m.a, m.b)` requires both right-side concepts to resolve to ONE source (model or rowset); to test against a cross-model pair, stage it through a rowset first. Examples: existence-anti-join, staged-membership.\n\n## Aggregation\n\nAn aggregate without an explicit `by` is RESPONSIVE \u2014 its grain is set where it is consumed, not where it is declared (`auto avg_credits <- avg(enroll.credits);` becomes per-department when selected with `enroll.department`). Pin a grain inline: `sum(metric) by dim1, dim2 as name`; `by` accepts bare identifiers or parenthesized expressions: `avg(price) by (substring(phone, 1, 2))`.\n\nMulti-level grouping (`by rollup|cube|grouping sets (...)`) is a clause of the WHOLE select, placed before `having`: it computes every no-explicit-grain aggregate at every level in one pass (`by rollup (d1, d2)` \u2192 leaf, per-d1 subtotal, grand total). `grouping(<field>)` = 1 on rolled-up rows \u2014 use it to label levels rather than testing for NULL. Full detail: trilogy agent-info syntax example rollup.\n\n## Window functions\n\nDefault to windows for self-referential queries (period-over-period, running totals, rank, share of group). SQL-style syntax: `rank(<key>) over (partition by <g> order by <expr> desc)` (`dense_rank`/`row_number` same shape; multi-key `rank(a, b)`); `sum(x) over (partition by g order by t)` for running totals; `lag(<field>, <offset>) over (...)` / `lead(...)` for offsets (default 1). `partition by` is optional and accepts expressions.\n\n## Data types\n\nScalars: `string`, `bytes`, `bool`, `int`, `bigint`, `float`, `double`, `number`, `numeric(p,s)` (alias `decimal`; use for exact money), `date`, `datetime`, `timestamp`, `geography`, `any`. Composites: `array<T>`, `map<K, V>`, `struct<name: T, ...>`, `enum<T>[v1, v2, ...]`. Cast with `::type` or `cast(x as type)`: `\"2020-01-01\"::date`.\n\n`?` after a type marks it nullable (`property user.middle_name string?;`). `~` prefixing a concept marks the reference as a PARTIAL subset of the concept's domain \u2014 on a select item (`~customer.id`) or in `merge a into ~b` (a \u2286 b); it tells the planner this side is not authoritative.\n\n## Expression gotchas\n\n- `x ? cond` binds to a primary \u2014 wrap arithmetic: `(a - b) ? cond`, NOT `a - b ? cond`.\n- `a || b` is NULL if either side is NULL; `concat(...)` skips NULL arguments; `concat_ws(sep, ...)` joins with sep, skipping NULLs.\n- Date parts are unquoted: `date_part(d, year)`, never `'year'`; prefer `year(d)`. `date_diff(start, end, unit)` = end \u2212 start (a shipping lag is `date_diff(sold_date, ship_date, day)`).\n- Zero-argument functions still take parentheses: `current_date()`.\n- Factor repeated calculations into a `def` macro (invoked `@name(...)`) or reusable `auto` concepts.";
4
4
  export declare const aggFunctions: string[];
5
5
  export declare const functions: string[];
6
6
  export declare const datatypes: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"trilogySyntax.generated.d.ts","sourceRoot":"","sources":["../../../llm/data/trilogySyntax.generated.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,sBAAsB,YAAY,CAAA;AAC/C,eAAO,MAAM,sBAAsB,mjhBAA27gB,CAAA;AAC99gB,eAAO,MAAM,UAAU,+vXAA4oX,CAAA;AACnqX,eAAO,MAAM,YAAY,UAExB,CAAA;AACD,eAAO,MAAM,SAAS,UAerB,CAAA;AACD,eAAO,MAAM,SAAS,UAsBrB,CAAA;AACD,eAAO,MAAM,cAAc;;;;;GAmH1B,CAAA"}
1
+ {"version":3,"file":"trilogySyntax.generated.d.ts","sourceRoot":"","sources":["../../../llm/data/trilogySyntax.generated.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,sBAAsB,YAAY,CAAA;AAC/C,eAAO,MAAM,sBAAsB,qiiBAA66hB,CAAA;AACh9hB,eAAO,MAAM,UAAU,wrYAAqkY,CAAA;AAC5lY,eAAO,MAAM,YAAY,UAExB,CAAA;AACD,eAAO,MAAM,SAAS,UAerB,CAAA;AACD,eAAO,MAAM,SAAS,UAsBrB,CAAA;AACD,eAAO,MAAM,cAAc;;;;;GAmH1B,CAAA"}