@rejot-dev/thalo 0.0.0

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/dist/ast/ast-types.d.ts +469 -0
  4. package/dist/ast/ast-types.d.ts.map +1 -0
  5. package/dist/ast/ast-types.js +11 -0
  6. package/dist/ast/ast-types.js.map +1 -0
  7. package/dist/ast/builder.js +158 -0
  8. package/dist/ast/builder.js.map +1 -0
  9. package/dist/ast/extract.js +748 -0
  10. package/dist/ast/extract.js.map +1 -0
  11. package/dist/ast/node-at-position.d.ts +147 -0
  12. package/dist/ast/node-at-position.d.ts.map +1 -0
  13. package/dist/ast/node-at-position.js +382 -0
  14. package/dist/ast/node-at-position.js.map +1 -0
  15. package/dist/ast/visitor.js +232 -0
  16. package/dist/ast/visitor.js.map +1 -0
  17. package/dist/checker/check.d.ts +53 -0
  18. package/dist/checker/check.d.ts.map +1 -0
  19. package/dist/checker/check.js +105 -0
  20. package/dist/checker/check.js.map +1 -0
  21. package/dist/checker/rules/actualize-missing-updated.js +34 -0
  22. package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
  23. package/dist/checker/rules/actualize-unresolved-target.js +42 -0
  24. package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
  25. package/dist/checker/rules/alter-before-define.js +53 -0
  26. package/dist/checker/rules/alter-before-define.js.map +1 -0
  27. package/dist/checker/rules/alter-undefined-entity.js +32 -0
  28. package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
  29. package/dist/checker/rules/create-requires-section.js +34 -0
  30. package/dist/checker/rules/create-requires-section.js.map +1 -0
  31. package/dist/checker/rules/define-entity-requires-section.js +31 -0
  32. package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
  33. package/dist/checker/rules/duplicate-entity-definition.js +37 -0
  34. package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
  35. package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
  36. package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
  37. package/dist/checker/rules/duplicate-link-id.js +52 -0
  38. package/dist/checker/rules/duplicate-link-id.js.map +1 -0
  39. package/dist/checker/rules/duplicate-metadata-key.js +21 -0
  40. package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
  41. package/dist/checker/rules/duplicate-section-heading.js +41 -0
  42. package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
  43. package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
  44. package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
  45. package/dist/checker/rules/duplicate-timestamp.js +104 -0
  46. package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
  47. package/dist/checker/rules/empty-required-value.js +45 -0
  48. package/dist/checker/rules/empty-required-value.js.map +1 -0
  49. package/dist/checker/rules/empty-section.js +21 -0
  50. package/dist/checker/rules/empty-section.js.map +1 -0
  51. package/dist/checker/rules/invalid-date-range-value.js +56 -0
  52. package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
  53. package/dist/checker/rules/invalid-default-value.js +86 -0
  54. package/dist/checker/rules/invalid-default-value.js.map +1 -0
  55. package/dist/checker/rules/invalid-field-type.js +45 -0
  56. package/dist/checker/rules/invalid-field-type.js.map +1 -0
  57. package/dist/checker/rules/missing-required-field.js +48 -0
  58. package/dist/checker/rules/missing-required-field.js.map +1 -0
  59. package/dist/checker/rules/missing-required-section.js +51 -0
  60. package/dist/checker/rules/missing-required-section.js.map +1 -0
  61. package/dist/checker/rules/missing-title.js +56 -0
  62. package/dist/checker/rules/missing-title.js.map +1 -0
  63. package/dist/checker/rules/remove-undefined-field.js +42 -0
  64. package/dist/checker/rules/remove-undefined-field.js.map +1 -0
  65. package/dist/checker/rules/remove-undefined-section.js +42 -0
  66. package/dist/checker/rules/remove-undefined-section.js.map +1 -0
  67. package/dist/checker/rules/rules.d.ts +71 -0
  68. package/dist/checker/rules/rules.d.ts.map +1 -0
  69. package/dist/checker/rules/rules.js +102 -0
  70. package/dist/checker/rules/rules.js.map +1 -0
  71. package/dist/checker/rules/synthesis-empty-query.js +35 -0
  72. package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
  73. package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
  74. package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
  75. package/dist/checker/rules/synthesis-missing-sources.js +32 -0
  76. package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
  77. package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
  78. package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
  79. package/dist/checker/rules/timestamp-out-of-order.js +55 -0
  80. package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
  81. package/dist/checker/rules/unknown-entity.js +32 -0
  82. package/dist/checker/rules/unknown-entity.js.map +1 -0
  83. package/dist/checker/rules/unknown-field.js +40 -0
  84. package/dist/checker/rules/unknown-field.js.map +1 -0
  85. package/dist/checker/rules/unknown-section.js +47 -0
  86. package/dist/checker/rules/unknown-section.js.map +1 -0
  87. package/dist/checker/rules/unresolved-link.js +34 -0
  88. package/dist/checker/rules/unresolved-link.js.map +1 -0
  89. package/dist/checker/rules/update-without-create.js +65 -0
  90. package/dist/checker/rules/update-without-create.js.map +1 -0
  91. package/dist/checker/visitor.d.ts +69 -0
  92. package/dist/checker/visitor.d.ts.map +1 -0
  93. package/dist/checker/visitor.js +67 -0
  94. package/dist/checker/visitor.js.map +1 -0
  95. package/dist/checker/workspace-index.d.ts +50 -0
  96. package/dist/checker/workspace-index.d.ts.map +1 -0
  97. package/dist/checker/workspace-index.js +108 -0
  98. package/dist/checker/workspace-index.js.map +1 -0
  99. package/dist/commands/actualize.d.ts +113 -0
  100. package/dist/commands/actualize.d.ts.map +1 -0
  101. package/dist/commands/actualize.js +111 -0
  102. package/dist/commands/actualize.js.map +1 -0
  103. package/dist/commands/check.d.ts +65 -0
  104. package/dist/commands/check.d.ts.map +1 -0
  105. package/dist/commands/check.js +61 -0
  106. package/dist/commands/check.js.map +1 -0
  107. package/dist/commands/format.d.ts +90 -0
  108. package/dist/commands/format.d.ts.map +1 -0
  109. package/dist/commands/format.js +80 -0
  110. package/dist/commands/format.js.map +1 -0
  111. package/dist/commands/query.d.ts +152 -0
  112. package/dist/commands/query.d.ts.map +1 -0
  113. package/dist/commands/query.js +151 -0
  114. package/dist/commands/query.js.map +1 -0
  115. package/dist/constants.d.ts +31 -0
  116. package/dist/constants.d.ts.map +1 -0
  117. package/dist/constants.js +51 -0
  118. package/dist/constants.js.map +1 -0
  119. package/dist/files.d.ts +58 -0
  120. package/dist/files.d.ts.map +1 -0
  121. package/dist/files.js +103 -0
  122. package/dist/files.js.map +1 -0
  123. package/dist/formatters.d.ts +39 -0
  124. package/dist/formatters.d.ts.map +1 -0
  125. package/dist/formatters.js +200 -0
  126. package/dist/formatters.js.map +1 -0
  127. package/dist/fragment.d.ts +22 -0
  128. package/dist/fragment.d.ts.map +1 -0
  129. package/dist/git/git.js +240 -0
  130. package/dist/git/git.js.map +1 -0
  131. package/dist/merge/conflict-detector.d.ts +89 -0
  132. package/dist/merge/conflict-detector.d.ts.map +1 -0
  133. package/dist/merge/conflict-detector.js +352 -0
  134. package/dist/merge/conflict-detector.js.map +1 -0
  135. package/dist/merge/conflict-formatter.js +143 -0
  136. package/dist/merge/conflict-formatter.js.map +1 -0
  137. package/dist/merge/driver.d.ts +54 -0
  138. package/dist/merge/driver.d.ts.map +1 -0
  139. package/dist/merge/driver.js +112 -0
  140. package/dist/merge/driver.js.map +1 -0
  141. package/dist/merge/entry-matcher.d.ts +50 -0
  142. package/dist/merge/entry-matcher.d.ts.map +1 -0
  143. package/dist/merge/entry-matcher.js +141 -0
  144. package/dist/merge/entry-matcher.js.map +1 -0
  145. package/dist/merge/entry-merger.js +194 -0
  146. package/dist/merge/entry-merger.js.map +1 -0
  147. package/dist/merge/merge-result-builder.d.ts +62 -0
  148. package/dist/merge/merge-result-builder.d.ts.map +1 -0
  149. package/dist/merge/merge-result-builder.js +89 -0
  150. package/dist/merge/merge-result-builder.js.map +1 -0
  151. package/dist/mod.d.ts +31 -0
  152. package/dist/mod.js +23 -0
  153. package/dist/model/document.d.ts +134 -0
  154. package/dist/model/document.d.ts.map +1 -0
  155. package/dist/model/document.js +275 -0
  156. package/dist/model/document.js.map +1 -0
  157. package/dist/model/line-index.d.ts +85 -0
  158. package/dist/model/line-index.d.ts.map +1 -0
  159. package/dist/model/line-index.js +159 -0
  160. package/dist/model/line-index.js.map +1 -0
  161. package/dist/model/workspace.d.ts +296 -0
  162. package/dist/model/workspace.d.ts.map +1 -0
  163. package/dist/model/workspace.js +562 -0
  164. package/dist/model/workspace.js.map +1 -0
  165. package/dist/parser.js +27 -0
  166. package/dist/parser.js.map +1 -0
  167. package/dist/parser.native.d.ts +51 -0
  168. package/dist/parser.native.d.ts.map +1 -0
  169. package/dist/parser.native.js +62 -0
  170. package/dist/parser.native.js.map +1 -0
  171. package/dist/parser.shared.d.ts +99 -0
  172. package/dist/parser.shared.d.ts.map +1 -0
  173. package/dist/parser.shared.js +124 -0
  174. package/dist/parser.shared.js.map +1 -0
  175. package/dist/parser.web.d.ts +67 -0
  176. package/dist/parser.web.d.ts.map +1 -0
  177. package/dist/parser.web.js +49 -0
  178. package/dist/parser.web.js.map +1 -0
  179. package/dist/schema/registry.d.ts +108 -0
  180. package/dist/schema/registry.d.ts.map +1 -0
  181. package/dist/schema/registry.js +281 -0
  182. package/dist/schema/registry.js.map +1 -0
  183. package/dist/semantic/analyzer.d.ts +107 -0
  184. package/dist/semantic/analyzer.d.ts.map +1 -0
  185. package/dist/semantic/analyzer.js +261 -0
  186. package/dist/semantic/analyzer.js.map +1 -0
  187. package/dist/services/change-tracker/change-tracker.d.ts +111 -0
  188. package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
  189. package/dist/services/change-tracker/change-tracker.js +62 -0
  190. package/dist/services/change-tracker/change-tracker.js.map +1 -0
  191. package/dist/services/change-tracker/create-tracker.d.ts +42 -0
  192. package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
  193. package/dist/services/change-tracker/create-tracker.js +53 -0
  194. package/dist/services/change-tracker/create-tracker.js.map +1 -0
  195. package/dist/services/change-tracker/git-tracker.d.ts +59 -0
  196. package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
  197. package/dist/services/change-tracker/git-tracker.js +218 -0
  198. package/dist/services/change-tracker/git-tracker.js.map +1 -0
  199. package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
  200. package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
  201. package/dist/services/change-tracker/timestamp-tracker.js +74 -0
  202. package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
  203. package/dist/services/definition.d.ts +37 -0
  204. package/dist/services/definition.d.ts.map +1 -0
  205. package/dist/services/definition.js +43 -0
  206. package/dist/services/definition.js.map +1 -0
  207. package/dist/services/entity-navigation.d.ts +200 -0
  208. package/dist/services/entity-navigation.d.ts.map +1 -0
  209. package/dist/services/entity-navigation.js +211 -0
  210. package/dist/services/entity-navigation.js.map +1 -0
  211. package/dist/services/hover.d.ts +81 -0
  212. package/dist/services/hover.d.ts.map +1 -0
  213. package/dist/services/hover.js +669 -0
  214. package/dist/services/hover.js.map +1 -0
  215. package/dist/services/query.d.ts +116 -0
  216. package/dist/services/query.d.ts.map +1 -0
  217. package/dist/services/query.js +225 -0
  218. package/dist/services/query.js.map +1 -0
  219. package/dist/services/references.d.ts +52 -0
  220. package/dist/services/references.d.ts.map +1 -0
  221. package/dist/services/references.js +66 -0
  222. package/dist/services/references.js.map +1 -0
  223. package/dist/services/semantic-tokens.d.ts +54 -0
  224. package/dist/services/semantic-tokens.d.ts.map +1 -0
  225. package/dist/services/semantic-tokens.js +213 -0
  226. package/dist/services/semantic-tokens.js.map +1 -0
  227. package/dist/services/synthesis.d.ts +90 -0
  228. package/dist/services/synthesis.d.ts.map +1 -0
  229. package/dist/services/synthesis.js +113 -0
  230. package/dist/services/synthesis.js.map +1 -0
  231. package/dist/source-map.d.ts +42 -0
  232. package/dist/source-map.d.ts.map +1 -0
  233. package/dist/source-map.js +170 -0
  234. package/dist/source-map.js.map +1 -0
  235. package/package.json +128 -0
  236. package/tree-sitter-thalo.wasm +0 -0
  237. package/web-tree-sitter.wasm +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 - present ReJot Nederland B.V., and individual contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,396 @@
1
+ # @rejot-dev/thalo
2
+
3
+ Core library for **thalo** (Thought And Lore Language). Provides parsing, semantic analysis,
4
+ validation, and workspace management for `.thalo` files.
5
+
6
+ ## Features
7
+
8
+ - **Parser**: Parse `.thalo` files and extract thalo blocks from Markdown
9
+ - **Incremental Parsing**: Efficient incremental updates via Tree-sitter's edit API
10
+ - **Fragment Parsing**: Parse individual expressions (queries, values, type expressions)
11
+ - **AST**: Strongly-typed abstract syntax tree with inline syntax error nodes
12
+ - **Semantic Analysis**: Build semantic models with link indexes and schema resolution
13
+ - **Schema**: Entity schema registry with `define-entity` / `alter-entity` support
14
+ - **Checker**: Visitor-based validation rules with incremental checking support
15
+ - **Services**: Definition lookup, references, hover, query execution, and semantic tokens
16
+ - **Source Mapping**: Position translation for embedded blocks in Markdown files
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add @rejot-dev/thalo
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Working with a Workspace
27
+
28
+ ```typescript
29
+ import { Workspace } from "@rejot-dev/thalo";
30
+
31
+ const workspace = new Workspace();
32
+
33
+ // Add documents (schemas first, then instances)
34
+ workspace.addDocument(schemaSource, { filename: "entities.thalo" });
35
+ workspace.addDocument(instanceSource, { filename: "entries.thalo" });
36
+
37
+ // Access semantic models
38
+ const model = workspace.getModel("entries.thalo");
39
+ console.log(model.ast.entries); // Array of parsed entries
40
+
41
+ // Query links across workspace
42
+ const linkDef = workspace.getLinkDefinition("my-link-id");
43
+ const refs = workspace.getLinkReferences("my-link-id");
44
+ ```
45
+
46
+ ### Incremental Document Updates
47
+
48
+ The workspace supports efficient incremental updates:
49
+
50
+ ```typescript
51
+ import { Workspace } from "@rejot-dev/thalo";
52
+
53
+ const workspace = new Workspace();
54
+ workspace.addDocument(source, { filename: "test.thalo" });
55
+
56
+ // Apply an incremental edit (more efficient than re-adding)
57
+ const invalidation = workspace.applyEdit(
58
+ "test.thalo",
59
+ startLine,
60
+ startColumn,
61
+ endLine,
62
+ endColumn,
63
+ newText,
64
+ );
65
+
66
+ // Check what was invalidated
67
+ if (invalidation.schemasChanged) {
68
+ // Entity schemas changed - may affect other files
69
+ }
70
+ if (invalidation.linksChanged) {
71
+ // Link definitions/references changed
72
+ }
73
+
74
+ // Get files affected by changes in a specific file
75
+ const affected = workspace.getAffectedFiles("test.thalo");
76
+ ```
77
+
78
+ ### Validating with the Checker
79
+
80
+ The checker reports both syntax errors (from AST) and semantic errors (from rules):
81
+
82
+ ```typescript
83
+ import { Workspace, check } from "@rejot-dev/thalo";
84
+
85
+ const workspace = new Workspace();
86
+ workspace.addDocument(source, { filename: "test.thalo" });
87
+
88
+ const diagnostics = check(workspace);
89
+ for (const d of diagnostics) {
90
+ // Syntax errors have codes like "syntax-missing_timezone"
91
+ // Semantic errors have codes like "unknown-entity", "unresolved-link"
92
+ console.log(`${d.severity}: ${d.message} (${d.code})`);
93
+ }
94
+ ```
95
+
96
+ ### Configuring Rule Severity
97
+
98
+ ```typescript
99
+ import { check } from "@rejot-dev/thalo";
100
+
101
+ const diagnostics = check(workspace, {
102
+ rules: {
103
+ "unknown-field": "off", // Disable this rule
104
+ "unresolved-link": "error", // Upgrade to error
105
+ },
106
+ });
107
+ ```
108
+
109
+ ### Parsing Fragments
110
+
111
+ Parse individual expressions without a full document context:
112
+
113
+ ```typescript
114
+ import { parseFragment, parseQuery } from "@rejot-dev/thalo";
115
+ import { createParser } from "@rejot-dev/thalo/native";
116
+
117
+ // Create a parser (native for Node.js, or web parser for browser)
118
+ const parser = createParser();
119
+
120
+ // Parse a query expression
121
+ const queryResult = parseQuery(parser, "lore where subject = ^self and #career");
122
+ if (queryResult.valid) {
123
+ console.log(queryResult.node.type); // "query"
124
+ }
125
+
126
+ // Parse a type expression
127
+ const typeResult = parseFragment(parser, "type_expression", 'string | "literal"');
128
+ ```
129
+
130
+ ### Executing Queries
131
+
132
+ Query entries in a workspace based on synthesis source definitions:
133
+
134
+ ```typescript
135
+ import { Workspace, executeQuery } from "@rejot-dev/thalo";
136
+
137
+ const workspace = new Workspace();
138
+ // ... add documents ...
139
+
140
+ const results = executeQuery(workspace, {
141
+ entity: "lore",
142
+ conditions: [
143
+ { kind: "field", field: "subject", value: "^self" },
144
+ { kind: "tag", tag: "career" },
145
+ ],
146
+ });
147
+ ```
148
+
149
+ ### Source Mapping for Embedded Blocks
150
+
151
+ Handle positions in markdown files with embedded thalo blocks:
152
+
153
+ ```typescript
154
+ import { parseDocument, findBlockAtPosition, toFileLocation } from "@rejot-dev/thalo";
155
+
156
+ const parsed = parseDocument(markdownSource, { filename: "notes.md" });
157
+
158
+ // Find which block contains a file position
159
+ const match = findBlockAtPosition(parsed.blocks, { line: 10, column: 5 });
160
+ if (match) {
161
+ // Convert block-relative location to file-absolute
162
+ const fileLocation = toFileLocation(match.block.sourceMap, entryLocation);
163
+ }
164
+ ```
165
+
166
+ ## Module Exports
167
+
168
+ | Export | Description |
169
+ | --------------------------- | ---------------------------------------------------------- |
170
+ | `@rejot-dev/thalo` | Main entry (parser, workspace, checker, services, types) |
171
+ | `@rejot-dev/thalo/native` | Native parser factory (`createParser`) |
172
+ | `@rejot-dev/thalo/web` | WASM parser factory (`createParser`) for browsers |
173
+ | `@rejot-dev/thalo/ast` | AST types, builder, visitor, extraction |
174
+ | `@rejot-dev/thalo/model` | Workspace, Document, LineIndex, model types |
175
+ | `@rejot-dev/thalo/semantic` | SemanticModel, analyzer, link index types |
176
+ | `@rejot-dev/thalo/schema` | SchemaRegistry, EntitySchema types |
177
+ | `@rejot-dev/thalo/checker` | Validation rules, visitor pattern, check functions |
178
+ | `@rejot-dev/thalo/services` | Definition, references, hover, query execution, entity nav |
179
+
180
+ ## Parser API
181
+
182
+ Both `/native` and `/web` exports provide a `createParser()` function that returns a `ThaloParser`
183
+ instance with a consistent API:
184
+
185
+ ```typescript
186
+ // Native (Node.js) - synchronous
187
+ import { createParser } from "@rejot-dev/thalo/native";
188
+ const parser = createParser();
189
+
190
+ // Web (WASM) - asynchronous, requires WASM binaries
191
+ import { createParser } from "@rejot-dev/thalo/web";
192
+
193
+ const [treeSitterWasm, languageWasm] = await Promise.all([
194
+ fetch("/wasm/web-tree-sitter.wasm").then((r) => r.arrayBuffer()),
195
+ fetch("/wasm/tree-sitter-thalo.wasm").then((r) => r.arrayBuffer()),
196
+ ]);
197
+
198
+ const parser = await createParser({
199
+ treeSitterWasm: new Uint8Array(treeSitterWasm),
200
+ languageWasm: new Uint8Array(languageWasm),
201
+ });
202
+
203
+ // Both return the same ThaloParser interface
204
+ const tree = parser.parse(source);
205
+ const doc = parser.parseDocument(source, { fileType: "thalo" });
206
+ const tree2 = parser.parseIncremental(newSource, tree);
207
+ ```
208
+
209
+ ### Vite Usage
210
+
211
+ For Vite projects, you can import the WASM files directly using Vite's `?url` suffix:
212
+
213
+ ```typescript
214
+ import { createParser } from "@rejot-dev/thalo/web";
215
+ import treeSitterWasmUrl from "web-tree-sitter/web-tree-sitter.wasm?url";
216
+ import languageWasmUrl from "@rejot-dev/tree-sitter-thalo/tree-sitter-thalo.wasm?url";
217
+
218
+ const [treeSitterWasm, languageWasm] = await Promise.all([
219
+ fetch(treeSitterWasmUrl).then((r) => r.arrayBuffer()),
220
+ fetch(languageWasmUrl).then((r) => r.arrayBuffer()),
221
+ ]);
222
+
223
+ const parser = await createParser({
224
+ treeSitterWasm: new Uint8Array(treeSitterWasm),
225
+ languageWasm: new Uint8Array(languageWasm),
226
+ });
227
+ ```
228
+
229
+ Both WASM files are re-exported from this package for convenience:
230
+
231
+ - `@rejot-dev/thalo/web-tree-sitter.wasm` — The web-tree-sitter runtime
232
+ - `@rejot-dev/thalo/tree-sitter-thalo.wasm` — The thalo language grammar
233
+
234
+ ### Cloudflare Workers / WebAssembly.Module Support
235
+
236
+ In environments like Cloudflare Workers, WASM imports are provided as pre-compiled
237
+ `WebAssembly.Module` instances rather than raw bytes. The web parser supports this directly:
238
+
239
+ ```typescript
240
+ import { createParser } from "@rejot-dev/thalo/web";
241
+ import treeSitterWasm from "./tree-sitter.wasm"; // WebAssembly.Module
242
+ import languageWasm from "./tree-sitter-thalo.wasm"; // WebAssembly.Module
243
+
244
+ const parser = await createParser({ treeSitterWasm, languageWasm });
245
+ ```
246
+
247
+ The main entry (`@rejot-dev/thalo`) provides convenience functions that use a singleton native
248
+ parser internally for backwards compatibility.
249
+
250
+ ## Validation Rules
251
+
252
+ The checker validates documents using rules organized into categories:
253
+
254
+ - **Instance**: Validates instance entries — entity types, required fields, field types, sections
255
+ - **Link**: Validates link references and definitions — unresolved links, duplicate IDs
256
+ - **Schema**: Validates entity schema definitions — duplicates, alter/define ordering, defaults
257
+ - **Metadata**: Validates metadata values — empty values, date range formats
258
+ - **Content**: Validates entry content structure — duplicate section headings
259
+
260
+ Rules use a visitor pattern for efficient single-pass execution. Each rule declares a scope (entry,
261
+ document, or workspace) to enable incremental checking when documents change.
262
+
263
+ To see all available rules, use the CLI:
264
+
265
+ ```bash
266
+ thalo rules list
267
+ ```
268
+
269
+ ## Architecture
270
+
271
+ The library follows a clear pipeline from source to services:
272
+
273
+ ```
274
+ Source → Tree-sitter → CST → AST Builder → AST → Semantic Analyzer → SemanticModel
275
+ ↓ ↓
276
+ Formatter Checker
277
+ (CST only) (syntax + semantic)
278
+
279
+ Services
280
+ ```
281
+
282
+ ### Incremental Processing
283
+
284
+ The workspace supports incremental updates at multiple levels:
285
+
286
+ 1. **Document Level**: The `Document` class wraps source text with a `LineIndex` for efficient
287
+ position lookups and tracks Tree-sitter trees per block
288
+ 2. **Semantic Level**: `updateSemanticModel` incrementally updates link indexes and schema entries
289
+ 3. **Workspace Level**: Dependency tracking (`linkDependencies`, `entityDependencies`) enables
290
+ targeted invalidation
291
+ 4. **Rule Level**: Rules declare their scope and dependencies, enabling incremental checking
292
+
293
+ ### Error Separation
294
+
295
+ | Error Type | Created In | Examples |
296
+ | ------------ | ------------- | --------------------------------------------- |
297
+ | **Syntax** | AST Builder | Missing timezone, missing entity name |
298
+ | **Semantic** | Checker Rules | Unresolved link, unknown field, type mismatch |
299
+
300
+ Syntax errors are inline `SyntaxErrorNode` instances in the AST. Semantic errors are diagnostics
301
+ from checker rules.
302
+
303
+ ### Directory Structure
304
+
305
+ ```
306
+ src/
307
+ ├── parser.ts # Default parser using native singleton (backwards compat)
308
+ ├── parser.native.ts # Native parser factory (createParser)
309
+ ├── parser.web.ts # WASM parser factory (createParser)
310
+ ├── parser.shared.ts # Shared parser logic (ThaloParser interface, createThaloParser)
311
+ ├── fragment.ts # Parse individual expressions (query, value, type)
312
+ ├── source-map.ts # Position mapping for embedded blocks
313
+ ├── constants.ts # Language constants (directives, types)
314
+ ├── ast/
315
+ │ ├── types.ts # AST node types (Entry, Timestamp, SyntaxErrorNode, etc.)
316
+ │ ├── extract.ts # Extract AST from tree-sitter CST
317
+ │ ├── builder.ts # Build decomposed types (timestamps with parts)
318
+ │ ├── visitor.ts # AST traversal utilities (walkAst, collectSyntaxErrors)
319
+ │ └── node-at-position.ts # Find semantic context at cursor position
320
+ ├── semantic/
321
+ │ ├── types.ts # SemanticModel, LinkIndex, LinkDefinition, LinkReference
322
+ │ └── analyzer.ts # Build SemanticModel from AST (indexes links, collects schemas)
323
+ ├── model/
324
+ │ ├── types.ts # Model types (ModelSchemaEntry for SchemaRegistry)
325
+ │ ├── document.ts # Document class with LineIndex and incremental edit support
326
+ │ ├── line-index.ts # Fast offset↔position conversion
327
+ │ └── workspace.ts # Multi-document workspace with dependency tracking
328
+ ├── schema/
329
+ │ ├── types.ts # Schema types (EntitySchema, FieldSchema, TypeExpr)
330
+ │ └── registry.ts # Schema registry with define/alter resolution
331
+ ├── checker/
332
+ │ ├── types.ts # Rule, diagnostic, and dependency types
333
+ │ ├── check.ts # Main check functions (check, checkDocument, checkIncremental)
334
+ │ ├── visitor.ts # RuleVisitor interface and visitor execution
335
+ │ ├── workspace-index.ts # Pre-computed indices for efficient rule execution
336
+ │ └── rules/ # Individual validation rules (27 rules)
337
+ └── services/
338
+ ├── definition.ts # Go-to-definition lookup
339
+ ├── references.ts # Find-all-references
340
+ ├── hover.ts # Hover information (entries, types, directives)
341
+ ├── query.ts # Query execution engine
342
+ ├── entity-navigation.ts # Entity/tag/field/section navigation
343
+ └── semantic-tokens.ts # LSP semantic tokens
344
+ ```
345
+
346
+ ## Development
347
+
348
+ ```bash
349
+ # Build
350
+ pnpm build
351
+
352
+ # Type check
353
+ pnpm types:check
354
+
355
+ # Run tests
356
+ pnpm test
357
+
358
+ # Watch mode
359
+ pnpm test:watch
360
+ ```
361
+
362
+ ## Future Work
363
+
364
+ ### Grammar-Based Type System
365
+
366
+ The Tree-Sitter grammar parses all values into typed AST nodes:
367
+
368
+ - ✅ Type validation using AST structure (no regex parsing)
369
+ - ✅ Query parsing at the grammar level
370
+ - ✅ Proper array element validation
371
+ - ✅ Typed default values (`quoted_value`, `link`, `datetime_value`)
372
+ - ✅ Query execution engine for synthesis entries
373
+ - ✅ Decomposed timestamps with date/time/timezone parts
374
+ - ✅ Inline syntax error nodes for recoverable parse errors
375
+ - ✅ Incremental parsing and semantic analysis
376
+ - ✅ Visitor-based rule system with dependency declarations
377
+
378
+ **Remaining work:**
379
+
380
+ 1. **Entity-only queries**: The grammar requires `where` for query expressions. Supporting
381
+ entity-only queries (e.g., `sources: lore`) would require grammar changes to avoid ambiguity.
382
+
383
+ ### Semantic Analysis
384
+
385
+ 1. **Cross-file link resolution**: Currently, link references are validated within the workspace.
386
+ Future work could include import/export semantics for external references.
387
+
388
+ ### Validation Rules
389
+
390
+ 1. **Cyclic reference detection**: Links can form cycles; detection would prevent infinite loops.
391
+
392
+ 2. **Schema evolution validation**: `alter-entity` changes could be validated for backwards
393
+ compatibility.
394
+
395
+ 3. **Content structure validation**: Validate that content sections match the schema's section
396
+ definitions.