@cratis/screenplay-language 4.52.0 → 4.54.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 (191) hide show
  1. package/README.md +15 -0
  2. package/dist/TypeReferenceSymbol.d.ts +8 -0
  3. package/dist/TypeReferenceSymbol.d.ts.map +1 -0
  4. package/dist/bundles/chunk-GJX4NJYC.js +4836 -0
  5. package/dist/bundles/chunk-GJX4NJYC.js.map +7 -0
  6. package/dist/bundles/index.js +1417 -0
  7. package/dist/bundles/index.js.map +7 -0
  8. package/dist/bundles/sub-languages/capture/index.js +677 -0
  9. package/dist/bundles/sub-languages/capture/index.js.map +7 -0
  10. package/dist/bundles/sub-languages/projection/index.js +1647 -0
  11. package/dist/bundles/sub-languages/projection/index.js.map +7 -0
  12. package/dist/code-actions.d.ts +3 -0
  13. package/dist/code-actions.d.ts.map +1 -0
  14. package/dist/completion-items.d.ts +1 -0
  15. package/dist/completion-items.d.ts.map +1 -1
  16. package/dist/completion-planner.d.ts.map +1 -1
  17. package/dist/completions.d.ts.map +1 -1
  18. package/dist/diagnostic-codes.d.ts +3 -0
  19. package/dist/diagnostic-codes.d.ts.map +1 -1
  20. package/dist/diagnostics.d.ts.map +1 -1
  21. package/dist/hover-content.d.ts.map +1 -1
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/keyword-docs.d.ts.map +1 -1
  25. package/dist/language.d.ts +1 -0
  26. package/dist/language.d.ts.map +1 -1
  27. package/dist/production-destinations.d.ts.map +1 -1
  28. package/dist/symbols.d.ts +4 -0
  29. package/dist/symbols.d.ts.map +1 -1
  30. package/dist/tokens.d.ts.map +1 -1
  31. package/dist/validation.d.ts +1 -0
  32. package/dist/validation.d.ts.map +1 -1
  33. package/package.json +13 -9
  34. package/DestinationHint.ts +0 -8
  35. package/ProductionSymbol.ts +0 -11
  36. package/completion-items.ts +0 -261
  37. package/completion-planner.ts +0 -167
  38. package/completions.ts +0 -156
  39. package/diagnostic-codes.ts +0 -43
  40. package/diagnostics.ts +0 -55
  41. package/dist/DestinationHint.js +0 -4
  42. package/dist/DestinationHint.js.map +0 -1
  43. package/dist/ProductionSymbol.js +0 -4
  44. package/dist/ProductionSymbol.js.map +0 -1
  45. package/dist/completion-items.js +0 -228
  46. package/dist/completion-items.js.map +0 -1
  47. package/dist/completion-planner.js +0 -143
  48. package/dist/completion-planner.js.map +0 -1
  49. package/dist/completions.js +0 -101
  50. package/dist/completions.js.map +0 -1
  51. package/dist/diagnostic-codes.js +0 -41
  52. package/dist/diagnostic-codes.js.map +0 -1
  53. package/dist/diagnostics.js +0 -50
  54. package/dist/diagnostics.js.map +0 -1
  55. package/dist/document-context.js +0 -85
  56. package/dist/document-context.js.map +0 -1
  57. package/dist/event-analysis-source.js +0 -19
  58. package/dist/event-analysis-source.js.map +0 -1
  59. package/dist/event-context-catalog.js +0 -116
  60. package/dist/event-context-catalog.js.map +0 -1
  61. package/dist/event-context.js +0 -88
  62. package/dist/event-context.js.map +0 -1
  63. package/dist/file-imports.js +0 -58
  64. package/dist/file-imports.js.map +0 -1
  65. package/dist/file-references.js +0 -70
  66. package/dist/file-references.js.map +0 -1
  67. package/dist/hover-content.js +0 -98
  68. package/dist/hover-content.js.map +0 -1
  69. package/dist/hover.js +0 -15
  70. package/dist/hover.js.map +0 -1
  71. package/dist/index.js +0 -61
  72. package/dist/index.js.map +0 -1
  73. package/dist/inlay-hints.js +0 -15
  74. package/dist/inlay-hints.js.map +0 -1
  75. package/dist/inline-event-validation.js +0 -131
  76. package/dist/inline-event-validation.js.map +0 -1
  77. package/dist/keyword-docs.js +0 -148
  78. package/dist/keyword-docs.js.map +0 -1
  79. package/dist/language.js +0 -196
  80. package/dist/language.js.map +0 -1
  81. package/dist/production-destinations.js +0 -32
  82. package/dist/production-destinations.js.map +0 -1
  83. package/dist/sub-language-registry.js +0 -18
  84. package/dist/sub-language-registry.js.map +0 -1
  85. package/dist/sub-languages/capture/CompletionProvider.js +0 -332
  86. package/dist/sub-languages/capture/CompletionProvider.js.map +0 -1
  87. package/dist/sub-languages/capture/HoverProvider.js +0 -87
  88. package/dist/sub-languages/capture/HoverProvider.js.map +0 -1
  89. package/dist/sub-languages/capture/Validator.js +0 -72
  90. package/dist/sub-languages/capture/Validator.js.map +0 -1
  91. package/dist/sub-languages/capture/index.js +0 -58
  92. package/dist/sub-languages/capture/index.js.map +0 -1
  93. package/dist/sub-languages/capture/language.js +0 -160
  94. package/dist/sub-languages/capture/language.js.map +0 -1
  95. package/dist/sub-languages/cdl.js +0 -74
  96. package/dist/sub-languages/cdl.js.map +0 -1
  97. package/dist/sub-languages/pdl.js +0 -73
  98. package/dist/sub-languages/pdl.js.map +0 -1
  99. package/dist/sub-languages/projection/CodeActionProvider.js +0 -93
  100. package/dist/sub-languages/projection/CodeActionProvider.js.map +0 -1
  101. package/dist/sub-languages/projection/CompletionProvider.js +0 -595
  102. package/dist/sub-languages/projection/CompletionProvider.js.map +0 -1
  103. package/dist/sub-languages/projection/HoverProvider.js +0 -333
  104. package/dist/sub-languages/projection/HoverProvider.js.map +0 -1
  105. package/dist/sub-languages/projection/Validator.js +0 -388
  106. package/dist/sub-languages/projection/Validator.js.map +0 -1
  107. package/dist/sub-languages/projection/index.js +0 -160
  108. package/dist/sub-languages/projection/index.js.map +0 -1
  109. package/dist/sub-languages/projection/language.js +0 -171
  110. package/dist/sub-languages/projection/language.js.map +0 -1
  111. package/dist/symbols.js +0 -275
  112. package/dist/symbols.js.map +0 -1
  113. package/dist/themes/screenplay-dark.js +0 -30
  114. package/dist/themes/screenplay-dark.js.map +0 -1
  115. package/dist/themes/screenplay-light.js +0 -30
  116. package/dist/themes/screenplay-light.js.map +0 -1
  117. package/dist/tokens.js +0 -147
  118. package/dist/tokens.js.map +0 -1
  119. package/dist/validation.js +0 -248
  120. package/dist/validation.js.map +0 -1
  121. package/document-context.ts +0 -93
  122. package/event-analysis-source.ts +0 -18
  123. package/event-context-catalog.ts +0 -135
  124. package/event-context.ts +0 -119
  125. package/file-imports.ts +0 -70
  126. package/file-references.ts +0 -87
  127. package/for_documentContext/when_stripping_comments.ts +0 -32
  128. package/for_eventContext/when_resolving_generated_catalog_vectors.ts +0 -15
  129. package/for_eventGenerations/when_editing_event_generations.ts +0 -57
  130. package/for_fileImports/when_finding_file_imports.ts +0 -38
  131. package/for_fileReferences/when_recognizing_an_absolute_path/with_paths_of_every_shape.ts +0 -26
  132. package/for_fileReferences/when_scanning_a_document/inside_a_code_fence.ts +0 -26
  133. package/for_fileReferences/when_scanning_a_document/with_a_bare_file_keyword.ts +0 -19
  134. package/for_fileReferences/when_scanning_a_document/with_a_file_directive.ts +0 -30
  135. package/for_fileReferences/when_scanning_a_document/with_a_property_named_file.ts +0 -25
  136. package/for_fileReferences/when_scanning_a_document/with_a_trailing_comment.ts +0 -25
  137. package/for_fileReferences/when_scanning_a_document/with_directives_on_several_constructs.ts +0 -41
  138. package/for_importablePaths/when_listing_what_a_document_can_import.ts +0 -34
  139. package/for_inlineEvents/when_analyzing_many_inline_events.ts +0 -59
  140. package/for_inlineEvents/when_authoring_inline_events.ts +0 -149
  141. package/for_inlineEvents/when_completing_many_events.ts +0 -49
  142. package/for_inlineEvents/when_indexing_command_production_ownership.ts +0 -79
  143. package/for_planCompletions/when_completing_a_file_import.ts +0 -26
  144. package/for_planCompletions/when_completing_a_specification_query.ts +0 -24
  145. package/for_planCompletions/when_completing_a_specification_step.ts +0 -42
  146. package/for_planCompletions/when_completing_an_absent_read_model.ts +0 -14
  147. package/for_planCompletions/when_completing_reaction_reads.ts +0 -36
  148. package/for_projectionCompletionProvider/when_completing_an_event_context_path.ts +0 -102
  149. package/for_projectionHoverProvider/when_hovering_an_event_context_path.ts +0 -44
  150. package/for_projectionValidator/when_validating_a_projection_with_variants.ts +0 -128
  151. package/for_projectionValidator/when_validating_event_context_paths.ts +0 -31
  152. package/for_scanDocument/when_scanning_a_query/with_a_dotted_return_type.ts +0 -34
  153. package/for_scanDocument/when_scanning_aliased_reads.ts +0 -33
  154. package/for_scanDocument/when_scanning_many_commands.ts +0 -47
  155. package/for_tokens/when_highlighting_a_file_import.ts +0 -18
  156. package/for_tokens/when_highlighting_event_metadata.ts +0 -63
  157. package/for_tokens/when_highlighting_specification_steps.ts +0 -28
  158. package/for_validateLines/when_validating_a_document/with_a_problem_of_every_coded_kind.ts +0 -77
  159. package/for_validateLines/when_validating_a_document/with_an_unknown_policy.ts +0 -33
  160. package/for_validateLines/when_validating_a_file_placed_by_an_import/with_names_declared_in_other_files.ts +0 -43
  161. package/for_validateLines/when_validating_event_context_paths/in_a_projection.ts +0 -97
  162. package/for_validateLines/when_validating_file_imports/at_every_level.ts +0 -36
  163. package/for_validateLines/when_validating_production_destinations/with_an_available_identifier.ts +0 -120
  164. package/hover-content.ts +0 -117
  165. package/hover.ts +0 -23
  166. package/index.ts +0 -125
  167. package/inlay-hints.ts +0 -17
  168. package/inline-event-validation.ts +0 -115
  169. package/keyword-docs.ts +0 -152
  170. package/language.ts +0 -212
  171. package/production-destinations.ts +0 -32
  172. package/sub-language-registry.ts +0 -42
  173. package/sub-languages/capture/CompletionProvider.ts +0 -365
  174. package/sub-languages/capture/HoverProvider.ts +0 -99
  175. package/sub-languages/capture/Validator.ts +0 -97
  176. package/sub-languages/capture/index.ts +0 -72
  177. package/sub-languages/capture/language.ts +0 -176
  178. package/sub-languages/cdl.ts +0 -76
  179. package/sub-languages/pdl.ts +0 -75
  180. package/sub-languages/projection/CodeActionProvider.ts +0 -123
  181. package/sub-languages/projection/CompletionProvider.ts +0 -660
  182. package/sub-languages/projection/HoverProvider.ts +0 -377
  183. package/sub-languages/projection/Validator.ts +0 -441
  184. package/sub-languages/projection/index.ts +0 -223
  185. package/sub-languages/projection/language.ts +0 -193
  186. package/symbols.ts +0 -374
  187. package/themes/screenplay-dark.ts +0 -33
  188. package/themes/screenplay-light.ts +0 -33
  189. package/tokens.ts +0 -174
  190. package/validation.ts +0 -443
  191. package/vitest.setup.ts +0 -5
@@ -0,0 +1,1647 @@
1
+ import {
2
+ describeEventContext,
3
+ describeEventContextMember,
4
+ eventContextMemberAt,
5
+ eventContextMembers,
6
+ eventContextMembersAfter,
7
+ fenceMap,
8
+ validateEventContextPaths
9
+ } from "../../chunk-GJX4NJYC.js";
10
+
11
+ // sub-languages/projection/language.ts
12
+ var languageId = "pdl";
13
+ var KEYWORDS = [
14
+ "projection",
15
+ "sequence",
16
+ "all",
17
+ "every",
18
+ "on",
19
+ "from",
20
+ "key",
21
+ "parent",
22
+ "join",
23
+ "automap",
24
+ "no",
25
+ "children",
26
+ "nested",
27
+ "identified",
28
+ "by",
29
+ "add",
30
+ "subtract",
31
+ "remove",
32
+ "via",
33
+ "with",
34
+ "default",
35
+ "increment",
36
+ "decrement",
37
+ "count",
38
+ "set",
39
+ "unset",
40
+ "clear",
41
+ "events",
42
+ "id",
43
+ "exclude",
44
+ "variant",
45
+ "enters"
46
+ ];
47
+ var BUILTINS = [
48
+ "$eventSourceId",
49
+ "$causedBy",
50
+ "$eventContext"
51
+ ];
52
+ var OPERATORS = ["=", "+=", "-=", "=>", "."];
53
+ var configuration = {
54
+ comments: {
55
+ lineComment: "#"
56
+ },
57
+ brackets: [
58
+ ["{", "}"],
59
+ ["[", "]"],
60
+ ["(", ")"]
61
+ ],
62
+ autoClosingPairs: [
63
+ { open: "{", close: "}" },
64
+ { open: "[", close: "]" },
65
+ { open: "(", close: ")" },
66
+ { open: '"', close: '"' },
67
+ { open: "'", close: "'" },
68
+ { open: "`", close: "`" }
69
+ ],
70
+ surroundingPairs: [
71
+ { open: "{", close: "}" },
72
+ { open: "[", close: "]" },
73
+ { open: "(", close: ")" },
74
+ { open: '"', close: '"' },
75
+ { open: "'", close: "'" },
76
+ { open: "`", close: "`" }
77
+ ],
78
+ indentationRules: {
79
+ increaseIndentPattern: /^.*(:|\bon\b|\ball\b|\bevery\b|\bchildren\b|\bnested\b|\bparent\b|\bvariant\b)\s*$/,
80
+ decreaseIndentPattern: /^.*\}.*$/
81
+ },
82
+ folding: {
83
+ offSide: true,
84
+ markers: {
85
+ start: /^\s*(projection|all|every|on|children|nested|parent|variant)/,
86
+ end: /^\s*$/
87
+ }
88
+ },
89
+ wordPattern: /@?[a-zA-Z_$][\w$-]*/
90
+ };
91
+ var monarchLanguage = {
92
+ defaultToken: "",
93
+ tokenPostfix: ".pdl",
94
+ keywords: KEYWORDS,
95
+ builtins: BUILTINS,
96
+ operators: OPERATORS,
97
+ // Symbols
98
+ symbols: /[=><!~?:&|+\-*/^%]+/,
99
+ escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
100
+ tokenizer: {
101
+ root: [
102
+ // Whitespace
103
+ { include: "@whitespace" },
104
+ // Template strings with ${...} interpolation
105
+ [/`/, { token: "string.template", next: "@templateString" }],
106
+ // Escaped identifiers (@ prefix allows using keywords as identifiers)
107
+ [/@[a-zA-Z_$][\w$]*/, "identifier.escape"],
108
+ // Check for uppercase keywords (error case - keywords must be lowercase)
109
+ [
110
+ /[A-Z][a-zA-Z_$][\w$]*/,
111
+ {
112
+ cases: {
113
+ "@keywords": "invalid",
114
+ "@default": "identifier"
115
+ }
116
+ }
117
+ ],
118
+ // Identifiers and keywords (lowercase only for keywords)
119
+ [
120
+ /[a-z_$][\w$]*/,
121
+ {
122
+ cases: {
123
+ "@keywords": "keyword",
124
+ "@builtins": "variable.predefined",
125
+ "@default": "identifier"
126
+ }
127
+ }
128
+ ],
129
+ // Delimiters and operators
130
+ [/[{}()[\]]/, "@brackets"],
131
+ [/=>/, "operator.arrow"],
132
+ [
133
+ /@symbols/,
134
+ {
135
+ cases: {
136
+ "@operators": "operator",
137
+ "@default": ""
138
+ }
139
+ }
140
+ ],
141
+ // Numbers
142
+ [/\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
143
+ [/0[xX][0-9a-fA-F]+/, "number.hex"],
144
+ [/\d+/, "number"],
145
+ // Delimiter: after number because of .\d floats
146
+ [/[;,.]/, "delimiter"],
147
+ // Strings
148
+ [/"([^"\\]|\\.)*$/, "string.invalid"],
149
+ [/"/, { token: "string.quote", next: "@string" }]
150
+ ],
151
+ whitespace: [
152
+ [/[ \t\r\n]+/, ""],
153
+ [/#.*$/, "comment"]
154
+ ],
155
+ // Template string state
156
+ templateString: [
157
+ [/\$\{/, { token: "delimiter.bracket", next: "@templateExpression" }],
158
+ [/[^\\`$]+/, "string.template"],
159
+ [/@escapes/, "string.template.escape"],
160
+ [/\\./, "string.template.escape.invalid"],
161
+ [/`/, { token: "string.template", next: "@pop" }]
162
+ ],
163
+ // Template expression inside ${}
164
+ templateExpression: [
165
+ [/\}/, { token: "delimiter.bracket", next: "@pop" }],
166
+ { include: "@root" }
167
+ ],
168
+ // Regular string state
169
+ string: [
170
+ [/[^\\"]+/, "string"],
171
+ [/@escapes/, "string.escape"],
172
+ [/\\./, "string.escape.invalid"],
173
+ [/"/, { token: "string.quote", next: "@pop" }]
174
+ ]
175
+ }
176
+ };
177
+
178
+ // sub-languages/projection/CompletionProvider.ts
179
+ var EVENT_CONTEXT_PATH = /\$eventContext\.((?:\w+\.)*)(\w*)$/;
180
+ var CompletionProvider = class {
181
+ readModels = [];
182
+ eventSchemas = {};
183
+ eventSequences = [];
184
+ setReadModels(readModels) {
185
+ this.readModels = readModels || [];
186
+ }
187
+ setEventSequences(sequences) {
188
+ this.eventSequences = sequences || [];
189
+ }
190
+ // Keep for backwards compatibility
191
+ setReadModelSchemas(schemas) {
192
+ this.readModels = schemas.map((schema) => ({
193
+ identifier: this.getSchemaName(schema) || "",
194
+ displayName: this.getSchemaName(schema) || "",
195
+ schema
196
+ }));
197
+ }
198
+ setEventSchemas(schemas) {
199
+ if (!schemas) {
200
+ this.eventSchemas = {};
201
+ return;
202
+ }
203
+ if (Array.isArray(schemas)) {
204
+ const map = {};
205
+ schemas.forEach((s, i) => {
206
+ if (!s) return;
207
+ const name = this.getSchemaName(s) || `Event${i + 1}`;
208
+ map[name] = s;
209
+ });
210
+ this.eventSchemas = map;
211
+ return;
212
+ }
213
+ this.eventSchemas = schemas;
214
+ }
215
+ provideCompletionItems(model, position) {
216
+ const context = this.buildContext(model, position);
217
+ const suggestions = [];
218
+ const activeSchema = this.getActiveReadModelSchema(model);
219
+ if (context.lineNumber === 1) {
220
+ this.addProjectionLineCompletions(suggestions, context);
221
+ } else if (this.isAfterKeyword(context, "sequence")) {
222
+ this.addEventSequenceCompletions(suggestions, context);
223
+ } else if (this.isAfterKeyword(context, "from") || this.isAfterKeyword(context, "every")) {
224
+ this.addEventTypeCompletions(suggestions, context);
225
+ } else if (/\benters\s+on\s+\w*$/.test(context.textBeforeCursor)) {
226
+ this.addEventTypeCompletions(suggestions, context);
227
+ } else if (/\benters\s+\w*$/.test(context.textBeforeCursor)) {
228
+ suggestions.push({
229
+ label: "on",
230
+ kind: 14,
231
+ // Keyword
232
+ insertText: "on ",
233
+ documentation: "Declares the event that activates this variant.",
234
+ detail: "enters on <EventType>",
235
+ range: this.getRangeForWord(context)
236
+ });
237
+ } else if (this.isAfterKeyword(context, "variant")) {
238
+ this.addVariantNameCompletions(suggestions, context);
239
+ } else if (this.isAfterKeyword(context, "key")) {
240
+ this.addKeyCompletions(suggestions, context, activeSchema);
241
+ } else if (this.isAfterKeyword(context, "parent")) {
242
+ this.addParentCompletions(suggestions, context);
243
+ } else if (this.isAfterKeyword(context, "children")) {
244
+ this.addChildrenCompletions(suggestions, context, activeSchema);
245
+ } else if (this.isAfterKeyword(context, "join")) {
246
+ this.addJoinCompletions(suggestions, context, activeSchema);
247
+ } else if (this.isAfterKeyword(context, "with")) {
248
+ this.addEventTypeCompletions(suggestions, context);
249
+ } else if (this.isAfterNumericOperationKeyword(context)) {
250
+ this.addNumericPropertyCompletions(suggestions, context, activeSchema);
251
+ } else if (this.isAfterAddOrSubtractBy(context)) {
252
+ this.addAssignmentValueCompletions(suggestions, context, model, position);
253
+ } else if (this.isInAssignment(context)) {
254
+ this.addAssignmentValueCompletions(suggestions, context, model, position);
255
+ } else if (this.isPropertyDotAccess(context)) {
256
+ this.addPropertyMemberCompletions(suggestions, context);
257
+ } else if (this.isStartOfLine(context) || this.isAfterIndent(context)) {
258
+ this.addStatementCompletions(suggestions, context, activeSchema);
259
+ }
260
+ return { suggestions };
261
+ }
262
+ buildContext(model, position) {
263
+ const lineContent = model.getLineContent(position.lineNumber);
264
+ const textBeforeCursor = lineContent.substring(0, position.column - 1);
265
+ const textAfterCursor = lineContent.substring(position.column - 1);
266
+ const word = model.getWordAtPosition(position);
267
+ const currentWord = word?.word || "";
268
+ const indentLevel = this.getIndentLevel(textBeforeCursor);
269
+ return {
270
+ lineContent,
271
+ textBeforeCursor,
272
+ textAfterCursor,
273
+ currentWord,
274
+ indentLevel,
275
+ lineNumber: position.lineNumber
276
+ };
277
+ }
278
+ getIndentLevel(text) {
279
+ const match = text.match(/^(\s*)/);
280
+ return match ? Math.floor(match[1].length / 2) : 0;
281
+ }
282
+ isStartOfLine(context) {
283
+ return context.textBeforeCursor.trim() === "";
284
+ }
285
+ isAfterIndent(context) {
286
+ return /^\s+$/.test(context.textBeforeCursor);
287
+ }
288
+ isAfterKeyword(context, keyword) {
289
+ const pattern = new RegExp(`\\b${keyword}\\s+\\w*$`);
290
+ return pattern.test(context.textBeforeCursor);
291
+ }
292
+ isAfterNumericOperationKeyword(context) {
293
+ const countIncrDecrPattern = /\b(count|increment|decrement)\s+\w*$/;
294
+ const addSubtractPattern = /\b(add|subtract)\s+\w*$/;
295
+ if (countIncrDecrPattern.test(context.textBeforeCursor)) {
296
+ return true;
297
+ }
298
+ if (addSubtractPattern.test(context.textBeforeCursor)) {
299
+ return !this.isAfterAddOrSubtractBy(context);
300
+ }
301
+ return false;
302
+ }
303
+ isAfterAddOrSubtractBy(context) {
304
+ const pattern = /\b(add|subtract)\s+\w+\s+by\s+\w*$/;
305
+ return pattern.test(context.textBeforeCursor);
306
+ }
307
+ isInAssignment(context) {
308
+ return /\w+\s*=\s*\w*$/.test(context.textBeforeCursor);
309
+ }
310
+ isPropertyDotAccess(context) {
311
+ return /(\w+|\$\w+)\.(\w*)$/.test(context.textBeforeCursor) || EVENT_CONTEXT_PATH.test(context.textBeforeCursor);
312
+ }
313
+ addProjectionLineCompletions(suggestions, context) {
314
+ const trimmed = context.textBeforeCursor.trim();
315
+ if (trimmed === "" || "projection".startsWith(trimmed)) {
316
+ suggestions.push({
317
+ label: "projection",
318
+ kind: 14,
319
+ // Keyword
320
+ insertText: "projection ",
321
+ documentation: "Define a projection that transforms events into a read model",
322
+ detail: "projection <ProjectionName>",
323
+ range: this.getRangeForWord(context)
324
+ });
325
+ }
326
+ const arrowMatch = trimmed.match(/^projection\s+[\w.]+\s*=>\s*([\w.]*)$/);
327
+ if (arrowMatch) {
328
+ const partialReadModel = arrowMatch[1] || "";
329
+ this.readModels.forEach((readModel) => {
330
+ if (!partialReadModel || readModel.displayName.toLowerCase().startsWith(partialReadModel.toLowerCase())) {
331
+ const label = `${readModel.displayName} (${readModel.identifier})`;
332
+ suggestions.push({
333
+ label,
334
+ kind: 6,
335
+ // Class
336
+ insertText: readModel.displayName,
337
+ filterText: `${readModel.displayName} ${readModel.identifier}`,
338
+ documentation: `Read model: ${readModel.displayName} (${readModel.identifier})`,
339
+ detail: readModel.identifier,
340
+ range: this.getRangeForWord(context)
341
+ });
342
+ }
343
+ });
344
+ return;
345
+ }
346
+ if (trimmed.startsWith("projection ") && !trimmed.includes("=>")) {
347
+ const afterProjection = trimmed.substring("projection ".length);
348
+ this.readModels.forEach((readModel) => {
349
+ if (!afterProjection || readModel.displayName.toLowerCase().startsWith(afterProjection.toLowerCase())) {
350
+ const label = `${readModel.displayName} (${readModel.identifier})`;
351
+ suggestions.push({
352
+ label,
353
+ kind: 6,
354
+ // Class
355
+ insertText: `${readModel.displayName} => ${readModel.displayName}`,
356
+ filterText: `${readModel.displayName} ${readModel.identifier}`,
357
+ documentation: `Read model: ${readModel.displayName} (${readModel.identifier})`,
358
+ detail: readModel.identifier,
359
+ range: this.getRangeForWord(context)
360
+ });
361
+ }
362
+ });
363
+ }
364
+ }
365
+ addEventTypeCompletions(suggestions, context) {
366
+ Object.keys(this.eventSchemas).forEach((eventName) => {
367
+ if (!context.currentWord || eventName.startsWith(context.currentWord)) {
368
+ const schema = this.eventSchemas[eventName];
369
+ suggestions.push({
370
+ label: eventName,
371
+ kind: 6,
372
+ // Class
373
+ insertText: eventName,
374
+ documentation: this.getSchemaDescription(schema) || `Event type: ${eventName}`,
375
+ detail: "Event Type",
376
+ range: this.getRangeForWord(context)
377
+ });
378
+ }
379
+ });
380
+ }
381
+ addEventSequenceCompletions(suggestions, context) {
382
+ this.eventSequences.forEach((sequenceId) => {
383
+ if (!context.currentWord || sequenceId.startsWith(context.currentWord)) {
384
+ suggestions.push({
385
+ label: sequenceId,
386
+ kind: 13,
387
+ // Value
388
+ insertText: sequenceId,
389
+ documentation: `Event sequence: ${sequenceId}`,
390
+ detail: "Event Sequence",
391
+ range: this.getRangeForWord(context)
392
+ });
393
+ }
394
+ });
395
+ }
396
+ addKeyCompletions(suggestions, context, activeSchema) {
397
+ if (activeSchema?.definitions) {
398
+ Object.keys(activeSchema.definitions).forEach((typeName) => {
399
+ const typeDef = activeSchema.definitions[typeName];
400
+ if (typeDef.type === "object") {
401
+ suggestions.push({
402
+ label: typeName,
403
+ kind: 6,
404
+ // Class
405
+ insertText: typeName,
406
+ documentation: `Composite key type: ${typeName}`,
407
+ detail: "Composite Key Type",
408
+ range: this.getRangeForWord(context)
409
+ });
410
+ }
411
+ });
412
+ }
413
+ this.addExpressionCompletions(suggestions, context);
414
+ }
415
+ addParentCompletions(suggestions, context) {
416
+ this.addExpressionCompletions(suggestions, context);
417
+ }
418
+ addNumericPropertyCompletions(suggestions, context, activeSchema) {
419
+ if (activeSchema?.properties) {
420
+ Object.entries(activeSchema.properties).forEach(([propName, propSchema]) => {
421
+ const isNumeric = propSchema.type === "integer" || propSchema.type === "number";
422
+ if (isNumeric) {
423
+ if (!context.currentWord || propName.startsWith(context.currentWord)) {
424
+ suggestions.push({
425
+ label: propName,
426
+ kind: 9,
427
+ // Property
428
+ insertText: propName,
429
+ documentation: `Numeric property: ${propName}`,
430
+ detail: this.getPropertyTypeDescription(propSchema),
431
+ range: this.getRangeForWord(context)
432
+ });
433
+ }
434
+ }
435
+ });
436
+ }
437
+ const addSubtractMatch = context.textBeforeCursor.match(/\b(add|subtract)\s+(\w*)$/);
438
+ if (addSubtractMatch) {
439
+ const partialProp = addSubtractMatch[2] || "";
440
+ if (activeSchema?.properties) {
441
+ Object.entries(activeSchema.properties).forEach(([propName, propSchema]) => {
442
+ const isNumeric = propSchema.type === "integer" || propSchema.type === "number";
443
+ if (isNumeric && (!partialProp || propName.startsWith(partialProp))) {
444
+ suggestions.push({
445
+ label: propName,
446
+ kind: 9,
447
+ // Property
448
+ insertText: `${propName} by `,
449
+ documentation: `Numeric property: ${propName}`,
450
+ detail: this.getPropertyTypeDescription(propSchema),
451
+ range: this.getRangeForWord(context)
452
+ });
453
+ }
454
+ });
455
+ }
456
+ }
457
+ }
458
+ addChildrenCompletions(suggestions, context, activeSchema) {
459
+ if (activeSchema?.properties) {
460
+ Object.entries(activeSchema.properties).forEach(([propName, propSchema]) => {
461
+ if (propSchema.type === "array" || propSchema.items) {
462
+ suggestions.push({
463
+ label: propName,
464
+ kind: 9,
465
+ // Property
466
+ insertText: `${propName} id `,
467
+ documentation: `Collection property: ${propName}`,
468
+ detail: this.getPropertyTypeDescription(propSchema),
469
+ range: this.getRangeForWord(context)
470
+ });
471
+ }
472
+ });
473
+ }
474
+ }
475
+ addJoinCompletions(suggestions, context, activeSchema) {
476
+ const trimmed = context.textBeforeCursor.trim();
477
+ if (trimmed === "join" || !trimmed.includes(" ")) {
478
+ if (activeSchema?.properties) {
479
+ Object.entries(activeSchema.properties).forEach(([propName, propSchema]) => {
480
+ if (propSchema.type === "array" || propSchema.items) {
481
+ suggestions.push({
482
+ label: propName,
483
+ kind: 9,
484
+ insertText: `${propName} on `,
485
+ documentation: `Join on collection: ${propName}`,
486
+ range: this.getRangeForWord(context)
487
+ });
488
+ }
489
+ });
490
+ }
491
+ } else if (trimmed.match(/join\s+\w+\s+on\s+/)) {
492
+ if (activeSchema?.properties) {
493
+ Object.keys(activeSchema.properties).forEach((propName) => {
494
+ suggestions.push({
495
+ label: propName,
496
+ kind: 9,
497
+ insertText: propName,
498
+ documentation: `Property: ${propName}`,
499
+ range: this.getRangeForWord(context)
500
+ });
501
+ });
502
+ }
503
+ }
504
+ }
505
+ addVariantNameCompletions(suggestions, context) {
506
+ this.readModels.forEach((readModel) => {
507
+ if (!context.currentWord || readModel.displayName.toLowerCase().startsWith(context.currentWord.toLowerCase())) {
508
+ suggestions.push({
509
+ label: readModel.displayName,
510
+ kind: 6,
511
+ // Class
512
+ insertText: readModel.displayName,
513
+ documentation: `Read model: ${readModel.displayName} (${readModel.identifier})`,
514
+ detail: readModel.identifier,
515
+ range: this.getRangeForWord(context)
516
+ });
517
+ }
518
+ });
519
+ }
520
+ addStatementCompletions(suggestions, context, activeSchema) {
521
+ const keywords = [
522
+ { label: "sequence", insertText: "sequence ", documentation: "Specify which event sequence to use for this projection", detail: "sequence <event-sequence-id>" },
523
+ { label: "variant", insertText: "variant ", documentation: "Declare one of several mutually exclusive named read models sharing this projection's identity", detail: "variant <Name>" },
524
+ { label: "enters on", insertText: "enters on ", documentation: "Declare the event that activates a variant", detail: "enters on <EventType>" },
525
+ { label: "from", insertText: "from ", documentation: "Handle specific event types", detail: "from <EventType>" },
526
+ { label: "all", insertText: "all", documentation: "Subscribe to all event types in the system \u2014 mappings run for every event system-wide, regardless of explicit from blocks", detail: "all" },
527
+ { label: "every", insertText: "every", documentation: "Handle all events for automatic mapping (only events explicitly subscribed to via from blocks)", detail: "every" },
528
+ { label: "key", insertText: "key ", documentation: "Define the projection key", detail: "key <expression>" },
529
+ { label: "parent", insertText: "parent ", documentation: "Define parent relationship", detail: "parent <expression>" },
530
+ { label: "join", insertText: "join ", documentation: "Join with child collection", detail: "join <collection> on <property>" },
531
+ { label: "children", insertText: "children ", documentation: "Define child collection operations", detail: "children <collection> identified by <expression>" },
532
+ { label: "remove with", insertText: "remove with ", documentation: "Remove items based on event", detail: "remove with <EventType>" },
533
+ { label: "remove via", insertText: "remove via join on ", documentation: "Remove items via join", detail: "remove via join on <EventType>" },
534
+ { label: "no automap", insertText: "no automap", documentation: "Disable automatic property mapping", detail: "no automap" },
535
+ { label: "clear", insertText: "clear ", documentation: "Remove the value a property holds", detail: "clear <property>" },
536
+ { label: "increment", insertText: "increment ", documentation: "Increment a numeric property", detail: "increment <property>" },
537
+ { label: "decrement", insertText: "decrement ", documentation: "Decrement a numeric property", detail: "decrement <property>" },
538
+ { label: "count", insertText: "count ", documentation: "Count occurrences", detail: "count <property>" },
539
+ { label: "add", insertText: "add ", documentation: "Add value to property", detail: "add <property> by <expression>" },
540
+ { label: "subtract", insertText: "subtract ", documentation: "Subtract value from property", detail: "subtract <property> by <expression>" },
541
+ { label: "exclude children", insertText: "exclude children", documentation: "Exclude child collections from automap", detail: "exclude children" }
542
+ ];
543
+ keywords.forEach((kw) => {
544
+ if (!context.currentWord || kw.label.startsWith(context.currentWord)) {
545
+ suggestions.push({
546
+ label: kw.label,
547
+ kind: 14,
548
+ // Keyword
549
+ insertText: kw.insertText,
550
+ documentation: kw.documentation,
551
+ detail: kw.detail,
552
+ range: this.getRangeForWord(context)
553
+ });
554
+ }
555
+ });
556
+ if (activeSchema?.properties) {
557
+ Object.entries(activeSchema.properties).forEach(([propName, propSchema]) => {
558
+ if (!context.currentWord || propName.startsWith(context.currentWord)) {
559
+ suggestions.push({
560
+ label: propName,
561
+ kind: 9,
562
+ // Property
563
+ insertText: `${propName} = `,
564
+ documentation: `Assign value to property: ${propName}`,
565
+ detail: this.getPropertyTypeDescription(propSchema),
566
+ range: this.getRangeForWord(context)
567
+ });
568
+ }
569
+ });
570
+ }
571
+ }
572
+ addAssignmentValueCompletions(suggestions, context, model, position) {
573
+ const eventType = this.getCurrentEventType(model, position.lineNumber);
574
+ if (eventType && this.eventSchemas[eventType]) {
575
+ const eventSchema = this.eventSchemas[eventType];
576
+ if (eventSchema.properties) {
577
+ Object.entries(eventSchema.properties).forEach(([propName, propSchema]) => {
578
+ suggestions.push({
579
+ label: propName,
580
+ kind: 9,
581
+ insertText: propName,
582
+ documentation: `Event property: ${propName}`,
583
+ detail: this.getPropertyTypeDescription(propSchema),
584
+ range: this.getRangeForWord(context)
585
+ });
586
+ });
587
+ }
588
+ }
589
+ this.addExpressionCompletions(suggestions, context);
590
+ }
591
+ addExpressionCompletions(suggestions, context) {
592
+ const expressions = [
593
+ {
594
+ label: "$eventSourceId",
595
+ insertText: "$eventSourceId",
596
+ documentation: "The identifier of the event source (aggregate/entity)",
597
+ detail: "Event Source Expression"
598
+ },
599
+ {
600
+ label: "$causedBy",
601
+ insertText: "$causedBy",
602
+ documentation: "The identity that caused the event. Chronicle cannot yet evaluate this short form (Cratis/Chronicle#4119) - prefer $eventContext.causedBy.<property>.",
603
+ detail: "Identity Expression"
604
+ },
605
+ {
606
+ label: "$eventContext",
607
+ insertText: "$eventContext.",
608
+ documentation: `Metadata about the event: ${eventContextMembers.map((member) => member.name).join(", ")}`,
609
+ detail: "Event Context Expression"
610
+ }
611
+ ];
612
+ expressions.forEach((expr) => {
613
+ if (!context.currentWord || expr.label.startsWith(context.currentWord) || expr.label.startsWith("$" + context.currentWord)) {
614
+ suggestions.push({
615
+ label: expr.label,
616
+ kind: 14,
617
+ // Keyword/builtin
618
+ insertText: expr.insertText,
619
+ documentation: expr.documentation,
620
+ detail: expr.detail,
621
+ range: this.getRangeForWord(context)
622
+ });
623
+ }
624
+ });
625
+ }
626
+ addPropertyMemberCompletions(suggestions, context) {
627
+ const eventContextMatch = context.textBeforeCursor.match(EVENT_CONTEXT_PATH);
628
+ if (eventContextMatch) {
629
+ const segments = eventContextMatch[1].split(".").filter((segment) => segment.length > 0);
630
+ this.addEventContextMemberCompletions(suggestions, context, segments, eventContextMatch[2]);
631
+ return;
632
+ }
633
+ const match = context.textBeforeCursor.match(/(\w+|\$\w+)\.(\w*)$/);
634
+ if (!match) return;
635
+ const [, objectName, partialProp] = match;
636
+ const causedByMatch = context.textBeforeCursor.match(/\$causedBy\.((?:\w+\.)*)(\w*)$/);
637
+ if (causedByMatch) {
638
+ const segments = causedByMatch[1].split(".").filter((segment) => segment.length > 0);
639
+ this.addEventContextMemberCompletions(suggestions, context, ["causedBy", ...segments], causedByMatch[2]);
640
+ return;
641
+ }
642
+ const eventSchema = this.eventSchemas[objectName];
643
+ if (eventSchema?.properties) {
644
+ Object.entries(eventSchema.properties).forEach(([propName, propSchema]) => {
645
+ if (!partialProp || propName.startsWith(partialProp)) {
646
+ suggestions.push({
647
+ label: propName,
648
+ kind: 9,
649
+ insertText: propName,
650
+ documentation: `Event property: ${propName}`,
651
+ detail: this.getPropertyTypeDescription(propSchema),
652
+ range: this.getRangeForWord(context)
653
+ });
654
+ }
655
+ });
656
+ }
657
+ }
658
+ addEventContextMemberCompletions(suggestions, context, segments, partial) {
659
+ const members = eventContextMembersAfter(segments) ?? [];
660
+ members.filter((member) => !partial || member.name.startsWith(partial)).forEach((member) => {
661
+ suggestions.push({
662
+ label: member.name,
663
+ kind: member.kind === "function" ? 1 : 9,
664
+ insertText: member.name,
665
+ documentation: member.description,
666
+ detail: member.type,
667
+ range: this.getRangeForWord(context)
668
+ });
669
+ });
670
+ }
671
+ getCurrentEventType(model, lineNumber) {
672
+ for (let i = lineNumber; i >= 1; i--) {
673
+ const line = model.getLineContent(i).trim();
674
+ const fromMatch = line.match(/^from\s+([\w-]+)/);
675
+ if (fromMatch) {
676
+ return fromMatch[1];
677
+ }
678
+ const everyMatch = line.match(/^every$/);
679
+ if (everyMatch) {
680
+ return null;
681
+ }
682
+ }
683
+ return null;
684
+ }
685
+ getActiveReadModelSchema(model) {
686
+ const firstLine = model.getLineContent(1).trim();
687
+ const match = firstLine.match(/^projection\s+[\w.]+\s*=>\s*([\w.]+)/);
688
+ if (!match) return void 0;
689
+ const readModelName = match[1];
690
+ const readModel = this.resolveReadModelInfo(readModelName);
691
+ return readModel?.schema;
692
+ }
693
+ resolveReadModelInfo(readModelToken) {
694
+ return this.readModels.find((rm) => rm.identifier === readModelToken || rm.displayName === readModelToken) || null;
695
+ }
696
+ getSchemaName(schema) {
697
+ if (schema.name) return schema.name;
698
+ if (schema.title) return schema.title;
699
+ if (typeof schema.$id === "string") {
700
+ const parts = schema.$id.split("/");
701
+ return parts[parts.length - 1] || schema.$id;
702
+ }
703
+ return void 0;
704
+ }
705
+ getSchemaDescription(schema) {
706
+ return schema.description || void 0;
707
+ }
708
+ getPropertyTypeDescription(propSchema) {
709
+ if (propSchema.type === "array" && propSchema.items) {
710
+ const itemType = propSchema.items.type || propSchema.items.$id?.split("/").pop() || "unknown";
711
+ return `${itemType}[]`;
712
+ }
713
+ return propSchema.type || propSchema.$ref?.split("/").pop() || "unknown";
714
+ }
715
+ getRangeForWord(context) {
716
+ const startColumn = context.textBeforeCursor.length - context.currentWord.length + 1;
717
+ const endColumn = startColumn + context.currentWord.length;
718
+ return {
719
+ startLineNumber: context.lineNumber,
720
+ startColumn: Math.max(1, startColumn),
721
+ endLineNumber: context.lineNumber,
722
+ endColumn: Math.max(1, endColumn)
723
+ };
724
+ }
725
+ };
726
+
727
+ // sub-languages/projection/Validator.ts
728
+ var Validator = class {
729
+ readModels = [];
730
+ eventSchemas = {};
731
+ draftReadModel = null;
732
+ setReadModels(readModels) {
733
+ this.readModels = readModels || [];
734
+ }
735
+ setDraftReadModel(draft) {
736
+ this.draftReadModel = draft;
737
+ }
738
+ // Keep for backwards compatibility
739
+ setReadModelSchemas(schemas) {
740
+ this.readModels = schemas.map((schema) => ({
741
+ identifier: this.getSchemaName(schema) || "",
742
+ displayName: this.getSchemaName(schema) || "",
743
+ schema
744
+ }));
745
+ }
746
+ setEventSchemas(schemas) {
747
+ if (!schemas) {
748
+ this.eventSchemas = {};
749
+ return;
750
+ }
751
+ if (Array.isArray(schemas)) {
752
+ const map = {};
753
+ schemas.forEach((s, i) => {
754
+ if (!s) return;
755
+ const name = this.getSchemaName(s) || `Event${i + 1}`;
756
+ map[name] = s;
757
+ });
758
+ this.eventSchemas = map;
759
+ return;
760
+ }
761
+ this.eventSchemas = schemas;
762
+ }
763
+ validate(model) {
764
+ const markers = [];
765
+ const content = model.getValue();
766
+ if (!content.trim()) {
767
+ return markers;
768
+ }
769
+ const lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
770
+ let firstNonEmptyLineIndex = 0;
771
+ let firstLine = "";
772
+ for (let i = 0; i < lines.length; i++) {
773
+ const trimmed = lines[i].trim();
774
+ if (trimmed && !trimmed.startsWith("#")) {
775
+ firstNonEmptyLineIndex = i;
776
+ firstLine = trimmed;
777
+ break;
778
+ }
779
+ }
780
+ if (!firstLine) {
781
+ return markers;
782
+ }
783
+ const projectionWithReadModelMatch = firstLine.match(/^projection\s+([\w.]+)\s*=>\s*([\w.]+)\s*$/);
784
+ const projectionAdHocMatch = !projectionWithReadModelMatch && firstLine.match(/^projection\s+([\w.]+)\s*$/);
785
+ if (!projectionWithReadModelMatch && !projectionAdHocMatch) {
786
+ const lineNumber = firstNonEmptyLineIndex + 1;
787
+ markers.push(this.createError(lineNumber, 1, firstLine.length + 1, 'Projection definition must start with "projection <ProjectionName>" or "projection <ProjectionName> => <ReadModelIdentifier>"'));
788
+ return markers;
789
+ }
790
+ let activeSchema;
791
+ let readModelName = null;
792
+ if (projectionWithReadModelMatch) {
793
+ readModelName = projectionWithReadModelMatch[2];
794
+ const readModelExists = this.resolveReadModelIdentifier(readModelName) !== null;
795
+ const isDraftReadModel = this.isDraftReadModel(readModelName);
796
+ const readModelStartColumn = this.getReadModelStartColumn(firstLine, readModelName);
797
+ if (!readModelExists && !isDraftReadModel) {
798
+ const lineNumber = firstNonEmptyLineIndex + 1;
799
+ const col = readModelStartColumn;
800
+ markers.push(this.createError(lineNumber, col, col + readModelName.length, `Read model '${readModelName}' not found`));
801
+ } else if (isDraftReadModel) {
802
+ const lineNumber = firstNonEmptyLineIndex + 1;
803
+ const col = readModelStartColumn;
804
+ markers.push(this.createInfo(lineNumber, col, col + readModelName.length, `Read model '${readModelName}' is a draft (not yet saved)`));
805
+ }
806
+ const isDraft = this.isDraftReadModel(readModelName);
807
+ const activeReadModel = isDraft ? null : this.resolveReadModelInfo(readModelName);
808
+ activeSchema = isDraft ? this.draftReadModel.schema : activeReadModel?.schema;
809
+ }
810
+ const contextStack = [activeSchema];
811
+ const indentStack = [0];
812
+ let lastIndent = 0;
813
+ const eventContextStack = [/* @__PURE__ */ new Map()];
814
+ for (let i = 1; i < lines.length; i++) {
815
+ const lineNumber = i + 1;
816
+ const line = lines[i];
817
+ const trimmed = line.trim();
818
+ if (!trimmed || trimmed.startsWith("#")) continue;
819
+ const currentIndent = line.search(/\S/);
820
+ while (currentIndent < lastIndent && indentStack.length > 1) {
821
+ const poppedIndent = indentStack.pop();
822
+ if (poppedIndent !== void 0 && currentIndent < poppedIndent) {
823
+ contextStack.pop();
824
+ eventContextStack.pop();
825
+ }
826
+ }
827
+ lastIndent = currentIndent;
828
+ const currentEventContext = eventContextStack[eventContextStack.length - 1];
829
+ const fromMatch = trimmed.match(/^from\s+([\w-]+)/);
830
+ if (fromMatch) {
831
+ const eventType = fromMatch[1];
832
+ const col = line.indexOf(eventType) + 1;
833
+ if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
834
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
835
+ }
836
+ if (currentEventContext.has(eventType)) {
837
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
838
+ } else {
839
+ currentEventContext.set(eventType, { lineNumber, col });
840
+ }
841
+ }
842
+ const variantMatch = trimmed.match(/^variant\s+([\w.]+)\s*$/);
843
+ if (variantMatch) {
844
+ const [, variantName] = variantMatch;
845
+ const col = line.indexOf(variantName) + 1;
846
+ const isDraft = this.isDraftReadModel(variantName);
847
+ const readModelExists = this.resolveReadModelIdentifier(variantName) !== null;
848
+ if (!readModelExists && !isDraft) {
849
+ markers.push(this.createError(lineNumber, col, col + variantName.length, `Read model '${variantName}' not found`));
850
+ } else if (isDraft) {
851
+ markers.push(this.createInfo(lineNumber, col, col + variantName.length, `Read model '${variantName}' is a draft (not yet saved)`));
852
+ }
853
+ const variantSchema = isDraft ? this.draftReadModel.schema : this.resolveReadModelInfo(variantName)?.schema;
854
+ contextStack.push(variantSchema);
855
+ indentStack.push(currentIndent);
856
+ eventContextStack.push(/* @__PURE__ */ new Map());
857
+ }
858
+ const entersOnMatch = trimmed.match(/^enters\s+on\s+([\w-]+)/);
859
+ if (entersOnMatch) {
860
+ const eventType = entersOnMatch[1];
861
+ const col = line.indexOf(eventType) + 1;
862
+ if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
863
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
864
+ }
865
+ if (currentEventContext.has(eventType)) {
866
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
867
+ } else {
868
+ currentEventContext.set(eventType, { lineNumber, col });
869
+ }
870
+ }
871
+ const eventsMatch = trimmed.match(/^events\s+([\w-]+)/);
872
+ if (eventsMatch) {
873
+ const eventType = eventsMatch[1];
874
+ const col = line.indexOf(eventType) + 1;
875
+ if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
876
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
877
+ }
878
+ if (currentEventContext.has(eventType)) {
879
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
880
+ } else {
881
+ currentEventContext.set(eventType, { lineNumber, col });
882
+ }
883
+ }
884
+ const removeWithMatch = trimmed.match(/^remove\s+(?:with|via\s+join\s+on)\s+([\w-]+)/);
885
+ if (removeWithMatch) {
886
+ const eventType = removeWithMatch[1];
887
+ const col = line.indexOf(eventType) + 1;
888
+ if (Object.keys(this.eventSchemas).length > 0 && !this.eventSchemas[eventType]) {
889
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Event type '${eventType}' not found`));
890
+ }
891
+ if (currentEventContext.has(eventType)) {
892
+ markers.push(this.createError(lineNumber, col, col + eventType.length, `Duplicate event type '${eventType}' - event types can only be used once at each level`));
893
+ } else {
894
+ currentEventContext.set(eventType, { lineNumber, col });
895
+ }
896
+ }
897
+ const assignmentMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
898
+ if (assignmentMatch) {
899
+ const currentSchema = contextStack[contextStack.length - 1];
900
+ if (currentSchema?.properties) {
901
+ const [, propertyName] = assignmentMatch;
902
+ if (!currentSchema.properties[propertyName]) {
903
+ const col = line.indexOf(propertyName) + 1;
904
+ const schemaName = this.getSchemaName(currentSchema) || "current schema";
905
+ markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' not found in ${schemaName}`));
906
+ }
907
+ }
908
+ }
909
+ const numericOpMatch = trimmed.match(/^(?:increment|decrement|count)\s+(\w+)$/);
910
+ if (numericOpMatch) {
911
+ const currentSchema = contextStack[contextStack.length - 1];
912
+ if (currentSchema?.properties) {
913
+ const [, propertyName] = numericOpMatch;
914
+ if (!currentSchema.properties[propertyName]) {
915
+ const col = line.indexOf(propertyName) + 1;
916
+ const schemaName = this.getSchemaName(currentSchema) || "current schema";
917
+ markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' not found in ${schemaName}`));
918
+ }
919
+ }
920
+ }
921
+ const addSubMatch = trimmed.match(/^(?:add|subtract)\s+(\w+)\s+by\s+/);
922
+ if (addSubMatch) {
923
+ const currentSchema = contextStack[contextStack.length - 1];
924
+ if (currentSchema?.properties) {
925
+ const [, propertyName] = addSubMatch;
926
+ if (!currentSchema.properties[propertyName]) {
927
+ const col = line.indexOf(propertyName) + 1;
928
+ const schemaName = this.getSchemaName(currentSchema) || "current schema";
929
+ markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' not found in ${schemaName}`));
930
+ }
931
+ }
932
+ }
933
+ const childrenMatch = trimmed.match(/^children\s+(\w+)\s+identified\s+by\s+/);
934
+ if (childrenMatch) {
935
+ const currentSchema = contextStack[contextStack.length - 1];
936
+ if (currentSchema?.properties) {
937
+ const [, propertyName] = childrenMatch;
938
+ const prop = currentSchema.properties[propertyName];
939
+ if (!prop) {
940
+ const col = line.indexOf(propertyName) + 1;
941
+ const schemaName = this.getSchemaName(currentSchema) || "current schema";
942
+ markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Collection property '${propertyName}' not found in ${schemaName}`));
943
+ } else if (prop.type !== "array" && !prop.items) {
944
+ const col = line.indexOf(propertyName) + 1;
945
+ markers.push(this.createWarning(lineNumber, col, col + propertyName.length, `Property '${propertyName}' is not a collection (array) type`));
946
+ } else {
947
+ const childSchema = prop.items;
948
+ if (childSchema) {
949
+ contextStack.push(childSchema);
950
+ indentStack.push(currentIndent);
951
+ eventContextStack.push(/* @__PURE__ */ new Map());
952
+ }
953
+ }
954
+ }
955
+ }
956
+ const joinMatch = trimmed.match(/^join\s+(\w+)\s+on\s+(\w+)/);
957
+ if (joinMatch && activeSchema?.properties) {
958
+ const [, collectionName, joinProperty] = joinMatch;
959
+ const collectionProp = activeSchema.properties[collectionName];
960
+ if (!collectionProp) {
961
+ const col = line.indexOf(collectionName) + 1;
962
+ markers.push(this.createWarning(lineNumber, col, col + collectionName.length, `Collection property '${collectionName}' not found in read model '${readModelName}'`));
963
+ } else if (collectionProp.type !== "array" && !collectionProp.items) {
964
+ const col = line.indexOf(collectionName) + 1;
965
+ markers.push(this.createWarning(lineNumber, col, col + collectionName.length, `Property '${collectionName}' is not a collection (array) type`));
966
+ }
967
+ if (!activeSchema.properties[joinProperty]) {
968
+ const col = line.indexOf(joinProperty, line.indexOf("on")) + 1;
969
+ markers.push(this.createWarning(lineNumber, col, col + joinProperty.length, `Property '${joinProperty}' not found in read model '${readModelName}'`));
970
+ }
971
+ eventContextStack.push(/* @__PURE__ */ new Map());
972
+ indentStack.push(currentIndent);
973
+ }
974
+ const keyMatch = trimmed.match(/^key\s+(\w+)\s*$/);
975
+ if (keyMatch && activeSchema) {
976
+ const [, keyName] = keyMatch;
977
+ const isInDefinitions = activeSchema.definitions && activeSchema.definitions[keyName];
978
+ const isProperty = activeSchema.properties && activeSchema.properties[keyName];
979
+ if (!isInDefinitions && !isProperty && !this.isBuiltInExpression(keyName)) {
980
+ const col = line.indexOf(keyName) + 1;
981
+ markers.push(this.createWarning(lineNumber, col, col + keyName.length, `Composite key type '${keyName}' not found in read model schema`));
982
+ }
983
+ }
984
+ }
985
+ for (const issue of validateEventContextPaths(lines, fenceMap(lines))) {
986
+ markers.push({
987
+ severity: issue.severity === "error" ? 8 : 4,
988
+ startLineNumber: issue.line + 1,
989
+ endLineNumber: issue.line + 1,
990
+ startColumn: issue.startColumn,
991
+ endColumn: issue.endColumn,
992
+ message: issue.message,
993
+ code: issue.code
994
+ });
995
+ }
996
+ return markers;
997
+ }
998
+ resolveReadModelInfo(readModelToken) {
999
+ return this.readModels.find((rm) => rm.identifier === readModelToken || rm.displayName === readModelToken) || null;
1000
+ }
1001
+ getReadModelStartColumn(firstLine, readModelName) {
1002
+ const prefixMatch = firstLine.match(/^\s*projection\s+[\w.]+\s*=>\s*/);
1003
+ if (!prefixMatch) {
1004
+ const fallbackIndex = firstLine.lastIndexOf(readModelName);
1005
+ return (fallbackIndex >= 0 ? fallbackIndex : 0) + 1;
1006
+ }
1007
+ return prefixMatch[0].length + 1;
1008
+ }
1009
+ resolveReadModelIdentifier(readModelToken) {
1010
+ if (this.isDraftReadModel(readModelToken)) {
1011
+ return this.draftReadModel.identifier;
1012
+ }
1013
+ const readModel = this.resolveReadModelInfo(readModelToken);
1014
+ return readModel?.identifier ?? null;
1015
+ }
1016
+ isDraftReadModel(readModelToken) {
1017
+ return !!this.draftReadModel && (this.draftReadModel.identifier === readModelToken || this.draftReadModel.displayName === readModelToken);
1018
+ }
1019
+ isBuiltInExpression(text) {
1020
+ return text.startsWith("$") || ["true", "false", "null"].includes(text);
1021
+ }
1022
+ createError(line, startCol, endCol, message) {
1023
+ return {
1024
+ severity: 8,
1025
+ // Error
1026
+ startLineNumber: line,
1027
+ startColumn: startCol,
1028
+ endLineNumber: line,
1029
+ endColumn: endCol,
1030
+ message
1031
+ };
1032
+ }
1033
+ createWarning(line, startCol, endCol, message) {
1034
+ return {
1035
+ severity: 4,
1036
+ // Warning
1037
+ startLineNumber: line,
1038
+ startColumn: startCol,
1039
+ endLineNumber: line,
1040
+ endColumn: endCol,
1041
+ message
1042
+ };
1043
+ }
1044
+ createInfo(line, startCol, endCol, message) {
1045
+ return {
1046
+ severity: 2,
1047
+ // Info (hint) - shows as blue/green squiggly
1048
+ startLineNumber: line,
1049
+ startColumn: startCol,
1050
+ endLineNumber: line,
1051
+ endColumn: endCol,
1052
+ message
1053
+ };
1054
+ }
1055
+ getSchemaName(schema) {
1056
+ if (schema.name) return schema.name;
1057
+ if (schema.title) return schema.title;
1058
+ if (typeof schema.$id === "string") {
1059
+ const parts = schema.$id.split("/");
1060
+ return parts[parts.length - 1] || schema.$id;
1061
+ }
1062
+ return void 0;
1063
+ }
1064
+ };
1065
+
1066
+ // sub-languages/projection/HoverProvider.ts
1067
+ var HoverProvider = class {
1068
+ readModels = [];
1069
+ eventSchemas = {};
1070
+ draftReadModel = null;
1071
+ setReadModels(readModels) {
1072
+ this.readModels = readModels || [];
1073
+ }
1074
+ // Keep for backwards compatibility
1075
+ setReadModelSchemas(schemas) {
1076
+ this.readModels = schemas.map((schema) => ({
1077
+ identifier: this.getSchemaName(schema) || "",
1078
+ displayName: this.getSchemaName(schema) || "",
1079
+ schema
1080
+ }));
1081
+ }
1082
+ setEventSchemas(schemas) {
1083
+ if (!schemas) {
1084
+ this.eventSchemas = {};
1085
+ return;
1086
+ }
1087
+ if (Array.isArray(schemas)) {
1088
+ const map = {};
1089
+ schemas.forEach((s, i) => {
1090
+ if (!s) return;
1091
+ const name = this.getSchemaName(s) || `Event${i + 1}`;
1092
+ map[name] = s;
1093
+ });
1094
+ this.eventSchemas = map;
1095
+ return;
1096
+ }
1097
+ this.eventSchemas = schemas;
1098
+ }
1099
+ setDraftReadModel(draft) {
1100
+ this.draftReadModel = draft;
1101
+ }
1102
+ provideHover(model, position) {
1103
+ const word = model.getWordAtPosition(position);
1104
+ if (!word) return null;
1105
+ const wordText = word.word;
1106
+ const lineContent = model.getLineContent(position.lineNumber);
1107
+ const projectionNameMatch = this.getProjectionNameAtPosition(lineContent, position.column);
1108
+ if (projectionNameMatch) {
1109
+ return {
1110
+ contents: [{ value: "The name of the projection" }],
1111
+ range: {
1112
+ startLineNumber: position.lineNumber,
1113
+ startColumn: word.startColumn,
1114
+ endLineNumber: position.lineNumber,
1115
+ endColumn: word.endColumn
1116
+ }
1117
+ };
1118
+ }
1119
+ const eventContextInfo = this.getEventContextInfo(lineContent, word.startColumn);
1120
+ if (eventContextInfo) {
1121
+ return {
1122
+ contents: [{ value: eventContextInfo }],
1123
+ range: {
1124
+ startLineNumber: position.lineNumber,
1125
+ startColumn: word.startColumn,
1126
+ endLineNumber: position.lineNumber,
1127
+ endColumn: word.endColumn
1128
+ }
1129
+ };
1130
+ }
1131
+ const keywordInfo = this.getKeywordInfo(wordText);
1132
+ if (keywordInfo) {
1133
+ return {
1134
+ contents: [{ value: keywordInfo }],
1135
+ range: {
1136
+ startLineNumber: position.lineNumber,
1137
+ startColumn: word.startColumn,
1138
+ endLineNumber: position.lineNumber,
1139
+ endColumn: word.endColumn
1140
+ }
1141
+ };
1142
+ }
1143
+ const expressionInfo = this.getExpressionInfo(wordText);
1144
+ if (expressionInfo) {
1145
+ return {
1146
+ contents: [{ value: expressionInfo }],
1147
+ range: {
1148
+ startLineNumber: position.lineNumber,
1149
+ startColumn: word.startColumn,
1150
+ endLineNumber: position.lineNumber,
1151
+ endColumn: word.endColumn
1152
+ }
1153
+ };
1154
+ }
1155
+ if (this.eventSchemas[wordText]) {
1156
+ const schema = this.eventSchemas[wordText];
1157
+ const description = this.getSchemaDescription(schema);
1158
+ const properties = this.getSchemaProperties(schema);
1159
+ let content = `**Event Type:** \`${wordText}\`
1160
+
1161
+ `;
1162
+ if (description) {
1163
+ content += `${description}
1164
+
1165
+ `;
1166
+ }
1167
+ if (properties.length > 0) {
1168
+ content += `**Properties:**
1169
+
1170
+ ${properties.map((p) => `- \`${p.name}\`: ${p.type}`).join("\n")}`;
1171
+ }
1172
+ return {
1173
+ contents: [{ value: content }],
1174
+ range: {
1175
+ startLineNumber: position.lineNumber,
1176
+ startColumn: word.startColumn,
1177
+ endLineNumber: position.lineNumber,
1178
+ endColumn: word.endColumn
1179
+ }
1180
+ };
1181
+ }
1182
+ if (this.isDraftReadModel(wordText)) {
1183
+ const description = this.getSchemaDescription(this.draftReadModel.schema);
1184
+ const properties = this.getSchemaProperties(this.draftReadModel.schema);
1185
+ const identifier = this.draftReadModel.identifier;
1186
+ const displayName = this.draftReadModel.displayName;
1187
+ let content = `**Read Model (Draft):** \`${displayName}\`
1188
+
1189
+ **Identifier:** \`${identifier}\`
1190
+
1191
+ `;
1192
+ if (description) {
1193
+ content += `${description}
1194
+
1195
+ `;
1196
+ }
1197
+ if (properties.length > 0) {
1198
+ content += `**Properties:**
1199
+
1200
+ ${properties.map((p) => `- \`${p.name}\`: ${p.type}`).join("\n")}`;
1201
+ }
1202
+ return {
1203
+ contents: [{ value: content }],
1204
+ range: {
1205
+ startLineNumber: position.lineNumber,
1206
+ startColumn: word.startColumn,
1207
+ endLineNumber: position.lineNumber,
1208
+ endColumn: word.endColumn
1209
+ }
1210
+ };
1211
+ }
1212
+ const readModel = this.resolveReadModelInfo(wordText);
1213
+ if (readModel) {
1214
+ const description = this.getSchemaDescription(readModel.schema);
1215
+ const properties = this.getSchemaProperties(readModel.schema);
1216
+ let content = `**Read Model:** \`${readModel.displayName}\`
1217
+
1218
+ **Identifier:** \`${readModel.identifier}\`
1219
+
1220
+ `;
1221
+ if (description) {
1222
+ content += `${description}
1223
+
1224
+ `;
1225
+ }
1226
+ if (properties.length > 0) {
1227
+ content += `**Properties:**
1228
+
1229
+ ${properties.map((p) => `- \`${p.name}\`: ${p.type}`).join("\n")}`;
1230
+ }
1231
+ return {
1232
+ contents: [{ value: content }],
1233
+ range: {
1234
+ startLineNumber: position.lineNumber,
1235
+ startColumn: word.startColumn,
1236
+ endLineNumber: position.lineNumber,
1237
+ endColumn: word.endColumn
1238
+ }
1239
+ };
1240
+ }
1241
+ const propertyInfo = this.getPropertyInfo(model, position, wordText);
1242
+ if (propertyInfo) {
1243
+ return {
1244
+ contents: [{ value: propertyInfo }],
1245
+ range: {
1246
+ startLineNumber: position.lineNumber,
1247
+ startColumn: word.startColumn,
1248
+ endLineNumber: position.lineNumber,
1249
+ endColumn: word.endColumn
1250
+ }
1251
+ };
1252
+ }
1253
+ return null;
1254
+ }
1255
+ getKeywordInfo(word) {
1256
+ const keywords = {
1257
+ "projection": "**projection** - Defines a projection that transforms events into a read model",
1258
+ "from": "**from** *<EventType>*\n\nHandles specific event types and maps their properties to the read model.",
1259
+ "all": "**all**\n\nSubscribes to **all event types in the system**, regardless of explicit `from` blocks. Mappings inside `all` run for every event that arrives system-wide.",
1260
+ "every": "**every**\n\nHandles all events the projection **explicitly subscribes to** through `from` blocks, with automatic property mapping.",
1261
+ "key": "**key** *<expression>*\n\nDefines the projection key or composite key type.",
1262
+ "parent": "**parent** *<expression>*\n\nDefines a parent relationship for hierarchical projections.",
1263
+ "join": "**join** *<collection>* **on** *<property>*\n\nJoins with a child collection based on a property.",
1264
+ "children": "**children** *<collection>* **id** *<expression>*\n\nDefines operations on child collections.",
1265
+ "remove": "**remove with** *<EventType>* or **remove via join on** *<EventType>*\n\nRemoves items from the read model based on events.",
1266
+ "clear": "**clear** *<property>*\n\nRemoves the value a property holds. **clear with** *<EventType>* nulls a whole nested object instead.",
1267
+ "automap": "**automap** or **no automap**\n\nControls automatic property mapping from events to the read model.",
1268
+ "increment": "**increment** *<property>*\n\nIncrements a numeric property by 1.",
1269
+ "decrement": "**decrement** *<property>*\n\nDecrements a numeric property by 1.",
1270
+ "count": "**count** *<property>*\n\nCounts occurrences and stores in a property.",
1271
+ "add": "**add** *<property>* **by** *<expression>*\n\nAdds a value to a property.",
1272
+ "subtract": "**subtract** *<property>* **by** *<expression>*\n\nSubtracts a value from a property.",
1273
+ "exclude": "**exclude children**\n\nExcludes child collections from automatic mapping.",
1274
+ "events": "**events** *<EventType>*, ...\n\nSpecifies which event types to include in a join.",
1275
+ "via": "Used with **remove via join on** to remove items via a join relationship.",
1276
+ "with": "Used with **remove with** to specify which event type triggers removal.",
1277
+ "on": "Used with **join** to specify the join property.",
1278
+ "by": "Used with **add** or **subtract** to specify the value.",
1279
+ "id": "Used with **children** to specify the child identifier expression.",
1280
+ "no": "Used with **no automap** to disable automatic property mapping.",
1281
+ "variant": "**variant** *<Name>*\n\nDeclares one of several mutually exclusive named read models sharing the projection's identity. Entering one variant removes the entity from every other variant of the same projection.",
1282
+ "enters": "**enters on** *<EventType>*\n\nDeclares the event that activates this variant - the only event allowed to create it. Every other event the variant subscribes to is update-only."
1283
+ };
1284
+ return keywords[word] || null;
1285
+ }
1286
+ getExpressionInfo(word) {
1287
+ const expressions = {
1288
+ "$eventSourceId": "**$eventSourceId**\n\nThe identifier of the event source (aggregate/entity) that generated the event.",
1289
+ "$causedBy": "**$causedBy**\n\nThe identity that caused the event - `subject`, `name`, `userName` or `onBehalfOf`, itself an identity.\n\nChronicle cannot yet evaluate this short form (Cratis/Chronicle#4119); prefer **$eventContext.causedBy**.*property*.",
1290
+ "$eventContext": describeEventContext()
1291
+ };
1292
+ return expressions[word] || null;
1293
+ }
1294
+ // Describes the $eventContext path segment starting at the given column, if the word there is one.
1295
+ getEventContextInfo(lineContent, startColumn) {
1296
+ for (const match of lineContent.matchAll(/\$eventContext((?:\.\w+(?:\(\))?)*)/g)) {
1297
+ let column = (match.index ?? 0) + 1;
1298
+ if (startColumn === column) return describeEventContext();
1299
+ column += "$eventContext".length;
1300
+ const segments = match[1].split(".").slice(1);
1301
+ for (let index = 0; index < segments.length; index++) {
1302
+ column += 1;
1303
+ if (startColumn === column) {
1304
+ const path = segments.slice(0, index + 1);
1305
+ const member = eventContextMemberAt(path);
1306
+ return member ? describeEventContextMember(path.join("."), member) : null;
1307
+ }
1308
+ column += segments[index].length;
1309
+ }
1310
+ }
1311
+ return null;
1312
+ }
1313
+ getPropertyInfo(model, position, propertyName) {
1314
+ const firstLine = model.getLineContent(1).trim();
1315
+ const match = firstLine.match(/^projection\s+[\w.]+\s*=>\s*([\w.]+)/);
1316
+ if (!match) return null;
1317
+ const readModelName = match[1];
1318
+ const draftMatch = this.isDraftReadModel(readModelName) ? this.draftReadModel : null;
1319
+ const resolvedReadModel = draftMatch ? null : this.resolveReadModelInfo(readModelName);
1320
+ const activeSchema = draftMatch?.schema || resolvedReadModel?.schema;
1321
+ if (activeSchema?.properties && activeSchema.properties[propertyName]) {
1322
+ const prop = activeSchema.properties[propertyName];
1323
+ const type = this.getPropertyTypeDescription(prop);
1324
+ const description = prop.description;
1325
+ let content = `**Property:** \`${propertyName}\`
1326
+
1327
+ **Type:** \`${type}\``;
1328
+ if (description) {
1329
+ content += `
1330
+
1331
+ ${description}`;
1332
+ }
1333
+ return content;
1334
+ }
1335
+ const eventType = this.getCurrentEventType(model, position.lineNumber);
1336
+ if (eventType && this.eventSchemas[eventType]) {
1337
+ const eventSchema = this.eventSchemas[eventType];
1338
+ if (eventSchema.properties && eventSchema.properties[propertyName]) {
1339
+ const prop = eventSchema.properties[propertyName];
1340
+ const type = this.getPropertyTypeDescription(prop);
1341
+ const description = prop.description;
1342
+ let content = `**Event Property:** \`${propertyName}\`
1343
+
1344
+ **Type:** \`${type}\``;
1345
+ if (description) {
1346
+ content += `
1347
+
1348
+ ${description}`;
1349
+ }
1350
+ return content;
1351
+ }
1352
+ }
1353
+ return null;
1354
+ }
1355
+ resolveReadModelInfo(readModelToken) {
1356
+ return this.readModels.find((rm) => rm.identifier === readModelToken || rm.displayName === readModelToken) || null;
1357
+ }
1358
+ isDraftReadModel(readModelToken) {
1359
+ return !!this.draftReadModel && (this.draftReadModel.identifier === readModelToken || this.draftReadModel.displayName === readModelToken);
1360
+ }
1361
+ getCurrentEventType(model, lineNumber) {
1362
+ for (let i = lineNumber; i >= 1; i--) {
1363
+ const line = model.getLineContent(i).trim();
1364
+ const fromMatch = line.match(/^from\s+([\w-]+)/);
1365
+ if (fromMatch) {
1366
+ return fromMatch[1];
1367
+ }
1368
+ }
1369
+ return null;
1370
+ }
1371
+ getProjectionNameAtPosition(lineContent, column) {
1372
+ const projectionMatch = lineContent.match(/^\s*projection\s+(\S+)\s*=>/);
1373
+ if (!projectionMatch) return false;
1374
+ const projectionKeywordEnd = lineContent.indexOf("projection") + "projection".length;
1375
+ const arrowStart = lineContent.indexOf("=>");
1376
+ return column > projectionKeywordEnd && column <= arrowStart;
1377
+ }
1378
+ getSchemaName(schema) {
1379
+ if (schema.name) return schema.name;
1380
+ if (schema.title) return schema.title;
1381
+ if (typeof schema.$id === "string") {
1382
+ const parts = schema.$id.split("/");
1383
+ return parts[parts.length - 1] || schema.$id;
1384
+ }
1385
+ return void 0;
1386
+ }
1387
+ getSchemaDescription(schema) {
1388
+ return schema.description || void 0;
1389
+ }
1390
+ getSchemaProperties(schema) {
1391
+ if (!schema.properties) return [];
1392
+ return Object.entries(schema.properties).map(([name, prop]) => ({
1393
+ name,
1394
+ type: this.getPropertyTypeDescription(prop)
1395
+ }));
1396
+ }
1397
+ getPropertyTypeDescription(propSchema) {
1398
+ if (propSchema.type === "array" && propSchema.items) {
1399
+ const itemType = propSchema.items.type || propSchema.items.$ref?.split("/").pop() || "unknown";
1400
+ return `${itemType}[]`;
1401
+ }
1402
+ return propSchema.type || propSchema.$ref?.split("/").pop() || "unknown";
1403
+ }
1404
+ };
1405
+
1406
+ // sub-languages/projection/CodeActionProvider.ts
1407
+ var CodeActionProvider = class {
1408
+ readModels = [];
1409
+ draftReadModel = null;
1410
+ onCreateReadModel;
1411
+ onEditReadModel;
1412
+ setReadModels(readModels) {
1413
+ this.readModels = readModels;
1414
+ }
1415
+ setDraftReadModel(draft) {
1416
+ this.draftReadModel = draft;
1417
+ }
1418
+ // Keep for backwards compatibility
1419
+ setReadModelSchemas(schemas) {
1420
+ this.readModels = schemas.map((schema) => ({
1421
+ identifier: schema.title || schema.name || "",
1422
+ displayName: schema.title || schema.name || "",
1423
+ schema
1424
+ }));
1425
+ }
1426
+ setCreateReadModelCallback(callback) {
1427
+ this.onCreateReadModel = callback;
1428
+ }
1429
+ setEditReadModelCallback(callback) {
1430
+ this.onEditReadModel = callback;
1431
+ }
1432
+ invokeCreateReadModel(readModelName) {
1433
+ this.onCreateReadModel?.(readModelName);
1434
+ }
1435
+ invokeEditReadModel(readModelName, currentSchema) {
1436
+ this.onEditReadModel?.(readModelName, currentSchema);
1437
+ }
1438
+ provideCodeActions(_model, _range, context) {
1439
+ const actions = [];
1440
+ const notFoundDiagnostics = context.markers.filter(
1441
+ (marker) => marker.message.includes("Read model") && marker.message.includes("not found")
1442
+ );
1443
+ const draftDiagnostics = context.markers.filter(
1444
+ (marker) => marker.message.includes("Read model") && marker.message.includes("is a draft")
1445
+ );
1446
+ for (const diagnostic of notFoundDiagnostics) {
1447
+ const match = diagnostic.message.match(/Read model ['"]([\w.]+)['"]/);
1448
+ if (match && match[1]) {
1449
+ const readModelName = match[1];
1450
+ const existsAsPersisted = this.readModels.some(
1451
+ (rm) => rm.identifier === readModelName || rm.displayName === readModelName
1452
+ );
1453
+ if (!existsAsPersisted && this.onCreateReadModel) {
1454
+ actions.push({
1455
+ title: `Create read model '${readModelName}'`,
1456
+ diagnostics: [diagnostic],
1457
+ kind: "quickfix",
1458
+ edit: void 0,
1459
+ isPreferred: true,
1460
+ command: {
1461
+ id: "pdl.createReadModel",
1462
+ title: `Create ${readModelName}`,
1463
+ arguments: [readModelName]
1464
+ }
1465
+ });
1466
+ }
1467
+ }
1468
+ }
1469
+ for (const diagnostic of draftDiagnostics) {
1470
+ const match = diagnostic.message.match(/Read model ['"]([\w.]+)['"]/);
1471
+ if (match && match[1]) {
1472
+ const readModelName = match[1];
1473
+ if (this.draftReadModel && this.isDraftReadModel(readModelName) && this.onEditReadModel) {
1474
+ actions.push({
1475
+ title: `Edit read model '${readModelName}'`,
1476
+ diagnostics: [diagnostic],
1477
+ kind: "quickfix",
1478
+ edit: void 0,
1479
+ isPreferred: true,
1480
+ command: {
1481
+ id: "pdl.editReadModel",
1482
+ title: `Edit ${readModelName}`,
1483
+ arguments: [readModelName, this.draftReadModel.schema]
1484
+ }
1485
+ });
1486
+ }
1487
+ }
1488
+ }
1489
+ return {
1490
+ actions,
1491
+ dispose: () => {
1492
+ }
1493
+ };
1494
+ }
1495
+ isDraftReadModel(readModelToken) {
1496
+ return !!this.draftReadModel && (this.draftReadModel.identifier === readModelToken || this.draftReadModel.displayName === readModelToken);
1497
+ }
1498
+ };
1499
+
1500
+ // sub-languages/projection/index.ts
1501
+ var validator;
1502
+ var completionProvider;
1503
+ var hoverProvider;
1504
+ var codeActionProvider;
1505
+ var disposables = [];
1506
+ var monacoInstance = null;
1507
+ var isRegistered = false;
1508
+ var pendingCreateReadModelCallback = null;
1509
+ var pendingEditReadModelCallback = null;
1510
+ function registerProjectionLanguage(monaco) {
1511
+ if (!isRegistered) {
1512
+ isRegistered = true;
1513
+ monacoInstance = monaco;
1514
+ monaco.languages.register({ id: languageId });
1515
+ monaco.languages.setLanguageConfiguration(languageId, configuration);
1516
+ monaco.languages.setMonarchTokensProvider(languageId, monarchLanguage);
1517
+ validator = new Validator();
1518
+ completionProvider = new CompletionProvider();
1519
+ hoverProvider = new HoverProvider();
1520
+ codeActionProvider = new CodeActionProvider();
1521
+ if (pendingCreateReadModelCallback) {
1522
+ codeActionProvider.setCreateReadModelCallback(pendingCreateReadModelCallback);
1523
+ pendingCreateReadModelCallback = null;
1524
+ }
1525
+ if (pendingEditReadModelCallback) {
1526
+ codeActionProvider.setEditReadModelCallback(pendingEditReadModelCallback);
1527
+ pendingEditReadModelCallback = null;
1528
+ }
1529
+ disposables.push(monaco.languages.registerCompletionItemProvider(languageId, {
1530
+ provideCompletionItems: completionProvider.provideCompletionItems.bind(completionProvider),
1531
+ triggerCharacters: [".", " ", "=", "\n", "$"]
1532
+ }));
1533
+ disposables.push(monaco.languages.registerHoverProvider(languageId, {
1534
+ provideHover: hoverProvider.provideHover.bind(hoverProvider)
1535
+ }));
1536
+ disposables.push(monaco.languages.registerCodeActionProvider(languageId, {
1537
+ provideCodeActions: codeActionProvider.provideCodeActions.bind(codeActionProvider)
1538
+ }, {
1539
+ providedCodeActionKinds: ["quickfix"]
1540
+ }));
1541
+ disposables.push(monaco.editor.registerCommand("pdl.createReadModel", (_accessor, readModelName) => {
1542
+ codeActionProvider?.invokeCreateReadModel(readModelName);
1543
+ }));
1544
+ disposables.push(monaco.editor.registerCommand("pdl.editReadModel", (_accessor, readModelName, currentSchema) => {
1545
+ codeActionProvider?.invokeEditReadModel(readModelName, currentSchema);
1546
+ }));
1547
+ disposables.push(monaco.editor.onDidCreateModel((model) => {
1548
+ if (model.getLanguageId() === languageId) {
1549
+ validateModel(monaco, model);
1550
+ disposables.push(model.onDidChangeContent(() => validateModel(monaco, model)));
1551
+ }
1552
+ }));
1553
+ monaco.editor.getModels().forEach((model) => {
1554
+ if (model.getLanguageId() === languageId) {
1555
+ validateModel(monaco, model);
1556
+ }
1557
+ });
1558
+ }
1559
+ return { dispose };
1560
+ }
1561
+ function dispose() {
1562
+ disposables.forEach((disposable) => disposable.dispose());
1563
+ disposables = [];
1564
+ isRegistered = false;
1565
+ monacoInstance = null;
1566
+ }
1567
+ function setReadModelSchema(schema) {
1568
+ validator?.setReadModelSchemas([schema]);
1569
+ completionProvider?.setReadModelSchemas([schema]);
1570
+ }
1571
+ function setReadModelSchemas(readModels) {
1572
+ validator?.setReadModels(readModels);
1573
+ completionProvider?.setReadModels(readModels);
1574
+ hoverProvider?.setReadModels(readModels);
1575
+ codeActionProvider?.setReadModels(readModels);
1576
+ revalidateAllModels();
1577
+ }
1578
+ function setCreateReadModelCallback(callback) {
1579
+ if (codeActionProvider) {
1580
+ codeActionProvider.setCreateReadModelCallback(callback);
1581
+ } else {
1582
+ pendingCreateReadModelCallback = callback;
1583
+ }
1584
+ }
1585
+ function setEditReadModelCallback(callback) {
1586
+ if (codeActionProvider) {
1587
+ codeActionProvider.setEditReadModelCallback(callback);
1588
+ } else {
1589
+ pendingEditReadModelCallback = callback;
1590
+ }
1591
+ }
1592
+ function setDraftReadModel(draft) {
1593
+ codeActionProvider?.setDraftReadModel(draft);
1594
+ validator?.setDraftReadModel(draft);
1595
+ hoverProvider?.setDraftReadModel(draft);
1596
+ revalidateAllModels();
1597
+ }
1598
+ function setEventSchemas(eventSchemas) {
1599
+ const normalize = (input) => {
1600
+ if (!input) return {};
1601
+ if (Array.isArray(input)) {
1602
+ const out = {};
1603
+ input.forEach((s, i) => {
1604
+ if (!s) return;
1605
+ const name = s.title || s.name || (typeof s.$id === "string" ? s.$id.split("/").pop() : void 0) || `Event${i + 1}`;
1606
+ out[name] = s;
1607
+ });
1608
+ return out;
1609
+ }
1610
+ return input;
1611
+ };
1612
+ const normalized = normalize(eventSchemas);
1613
+ validator?.setEventSchemas(normalized);
1614
+ completionProvider?.setEventSchemas(normalized);
1615
+ hoverProvider?.setEventSchemas(normalized);
1616
+ revalidateAllModels();
1617
+ }
1618
+ function setEventSequences(sequences) {
1619
+ completionProvider?.setEventSequences(sequences);
1620
+ }
1621
+ function validateModel(monaco, model) {
1622
+ if (validator && !model.isDisposed()) {
1623
+ const markers = validator.validate(model);
1624
+ monaco.editor.setModelMarkers(model, "pdl-validator", markers);
1625
+ }
1626
+ }
1627
+ function revalidateAllModels() {
1628
+ if (monacoInstance) {
1629
+ monacoInstance.editor.getModels().forEach((model) => {
1630
+ if (model.getLanguageId() === languageId) {
1631
+ validateModel(monacoInstance, model);
1632
+ }
1633
+ });
1634
+ }
1635
+ }
1636
+ export {
1637
+ languageId,
1638
+ registerProjectionLanguage,
1639
+ setCreateReadModelCallback,
1640
+ setDraftReadModel,
1641
+ setEditReadModelCallback,
1642
+ setEventSchemas,
1643
+ setEventSequences,
1644
+ setReadModelSchema,
1645
+ setReadModelSchemas
1646
+ };
1647
+ //# sourceMappingURL=index.js.map