@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.
- package/README.md +15 -0
- package/dist/TypeReferenceSymbol.d.ts +8 -0
- package/dist/TypeReferenceSymbol.d.ts.map +1 -0
- package/dist/bundles/chunk-GJX4NJYC.js +4836 -0
- package/dist/bundles/chunk-GJX4NJYC.js.map +7 -0
- package/dist/bundles/index.js +1417 -0
- package/dist/bundles/index.js.map +7 -0
- package/dist/bundles/sub-languages/capture/index.js +677 -0
- package/dist/bundles/sub-languages/capture/index.js.map +7 -0
- package/dist/bundles/sub-languages/projection/index.js +1647 -0
- package/dist/bundles/sub-languages/projection/index.js.map +7 -0
- package/dist/code-actions.d.ts +3 -0
- package/dist/code-actions.d.ts.map +1 -0
- package/dist/completion-items.d.ts +1 -0
- package/dist/completion-items.d.ts.map +1 -1
- package/dist/completion-planner.d.ts.map +1 -1
- package/dist/completions.d.ts.map +1 -1
- package/dist/diagnostic-codes.d.ts +3 -0
- package/dist/diagnostic-codes.d.ts.map +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/hover-content.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/keyword-docs.d.ts.map +1 -1
- package/dist/language.d.ts +1 -0
- package/dist/language.d.ts.map +1 -1
- package/dist/production-destinations.d.ts.map +1 -1
- package/dist/symbols.d.ts +4 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/validation.d.ts +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/package.json +13 -9
- package/DestinationHint.ts +0 -8
- package/ProductionSymbol.ts +0 -11
- package/completion-items.ts +0 -261
- package/completion-planner.ts +0 -167
- package/completions.ts +0 -156
- package/diagnostic-codes.ts +0 -43
- package/diagnostics.ts +0 -55
- package/dist/DestinationHint.js +0 -4
- package/dist/DestinationHint.js.map +0 -1
- package/dist/ProductionSymbol.js +0 -4
- package/dist/ProductionSymbol.js.map +0 -1
- package/dist/completion-items.js +0 -228
- package/dist/completion-items.js.map +0 -1
- package/dist/completion-planner.js +0 -143
- package/dist/completion-planner.js.map +0 -1
- package/dist/completions.js +0 -101
- package/dist/completions.js.map +0 -1
- package/dist/diagnostic-codes.js +0 -41
- package/dist/diagnostic-codes.js.map +0 -1
- package/dist/diagnostics.js +0 -50
- package/dist/diagnostics.js.map +0 -1
- package/dist/document-context.js +0 -85
- package/dist/document-context.js.map +0 -1
- package/dist/event-analysis-source.js +0 -19
- package/dist/event-analysis-source.js.map +0 -1
- package/dist/event-context-catalog.js +0 -116
- package/dist/event-context-catalog.js.map +0 -1
- package/dist/event-context.js +0 -88
- package/dist/event-context.js.map +0 -1
- package/dist/file-imports.js +0 -58
- package/dist/file-imports.js.map +0 -1
- package/dist/file-references.js +0 -70
- package/dist/file-references.js.map +0 -1
- package/dist/hover-content.js +0 -98
- package/dist/hover-content.js.map +0 -1
- package/dist/hover.js +0 -15
- package/dist/hover.js.map +0 -1
- package/dist/index.js +0 -61
- package/dist/index.js.map +0 -1
- package/dist/inlay-hints.js +0 -15
- package/dist/inlay-hints.js.map +0 -1
- package/dist/inline-event-validation.js +0 -131
- package/dist/inline-event-validation.js.map +0 -1
- package/dist/keyword-docs.js +0 -148
- package/dist/keyword-docs.js.map +0 -1
- package/dist/language.js +0 -196
- package/dist/language.js.map +0 -1
- package/dist/production-destinations.js +0 -32
- package/dist/production-destinations.js.map +0 -1
- package/dist/sub-language-registry.js +0 -18
- package/dist/sub-language-registry.js.map +0 -1
- package/dist/sub-languages/capture/CompletionProvider.js +0 -332
- package/dist/sub-languages/capture/CompletionProvider.js.map +0 -1
- package/dist/sub-languages/capture/HoverProvider.js +0 -87
- package/dist/sub-languages/capture/HoverProvider.js.map +0 -1
- package/dist/sub-languages/capture/Validator.js +0 -72
- package/dist/sub-languages/capture/Validator.js.map +0 -1
- package/dist/sub-languages/capture/index.js +0 -58
- package/dist/sub-languages/capture/index.js.map +0 -1
- package/dist/sub-languages/capture/language.js +0 -160
- package/dist/sub-languages/capture/language.js.map +0 -1
- package/dist/sub-languages/cdl.js +0 -74
- package/dist/sub-languages/cdl.js.map +0 -1
- package/dist/sub-languages/pdl.js +0 -73
- package/dist/sub-languages/pdl.js.map +0 -1
- package/dist/sub-languages/projection/CodeActionProvider.js +0 -93
- package/dist/sub-languages/projection/CodeActionProvider.js.map +0 -1
- package/dist/sub-languages/projection/CompletionProvider.js +0 -595
- package/dist/sub-languages/projection/CompletionProvider.js.map +0 -1
- package/dist/sub-languages/projection/HoverProvider.js +0 -333
- package/dist/sub-languages/projection/HoverProvider.js.map +0 -1
- package/dist/sub-languages/projection/Validator.js +0 -388
- package/dist/sub-languages/projection/Validator.js.map +0 -1
- package/dist/sub-languages/projection/index.js +0 -160
- package/dist/sub-languages/projection/index.js.map +0 -1
- package/dist/sub-languages/projection/language.js +0 -171
- package/dist/sub-languages/projection/language.js.map +0 -1
- package/dist/symbols.js +0 -275
- package/dist/symbols.js.map +0 -1
- package/dist/themes/screenplay-dark.js +0 -30
- package/dist/themes/screenplay-dark.js.map +0 -1
- package/dist/themes/screenplay-light.js +0 -30
- package/dist/themes/screenplay-light.js.map +0 -1
- package/dist/tokens.js +0 -147
- package/dist/tokens.js.map +0 -1
- package/dist/validation.js +0 -248
- package/dist/validation.js.map +0 -1
- package/document-context.ts +0 -93
- package/event-analysis-source.ts +0 -18
- package/event-context-catalog.ts +0 -135
- package/event-context.ts +0 -119
- package/file-imports.ts +0 -70
- package/file-references.ts +0 -87
- package/for_documentContext/when_stripping_comments.ts +0 -32
- package/for_eventContext/when_resolving_generated_catalog_vectors.ts +0 -15
- package/for_eventGenerations/when_editing_event_generations.ts +0 -57
- package/for_fileImports/when_finding_file_imports.ts +0 -38
- package/for_fileReferences/when_recognizing_an_absolute_path/with_paths_of_every_shape.ts +0 -26
- package/for_fileReferences/when_scanning_a_document/inside_a_code_fence.ts +0 -26
- package/for_fileReferences/when_scanning_a_document/with_a_bare_file_keyword.ts +0 -19
- package/for_fileReferences/when_scanning_a_document/with_a_file_directive.ts +0 -30
- package/for_fileReferences/when_scanning_a_document/with_a_property_named_file.ts +0 -25
- package/for_fileReferences/when_scanning_a_document/with_a_trailing_comment.ts +0 -25
- package/for_fileReferences/when_scanning_a_document/with_directives_on_several_constructs.ts +0 -41
- package/for_importablePaths/when_listing_what_a_document_can_import.ts +0 -34
- package/for_inlineEvents/when_analyzing_many_inline_events.ts +0 -59
- package/for_inlineEvents/when_authoring_inline_events.ts +0 -149
- package/for_inlineEvents/when_completing_many_events.ts +0 -49
- package/for_inlineEvents/when_indexing_command_production_ownership.ts +0 -79
- package/for_planCompletions/when_completing_a_file_import.ts +0 -26
- package/for_planCompletions/when_completing_a_specification_query.ts +0 -24
- package/for_planCompletions/when_completing_a_specification_step.ts +0 -42
- package/for_planCompletions/when_completing_an_absent_read_model.ts +0 -14
- package/for_planCompletions/when_completing_reaction_reads.ts +0 -36
- package/for_projectionCompletionProvider/when_completing_an_event_context_path.ts +0 -102
- package/for_projectionHoverProvider/when_hovering_an_event_context_path.ts +0 -44
- package/for_projectionValidator/when_validating_a_projection_with_variants.ts +0 -128
- package/for_projectionValidator/when_validating_event_context_paths.ts +0 -31
- package/for_scanDocument/when_scanning_a_query/with_a_dotted_return_type.ts +0 -34
- package/for_scanDocument/when_scanning_aliased_reads.ts +0 -33
- package/for_scanDocument/when_scanning_many_commands.ts +0 -47
- package/for_tokens/when_highlighting_a_file_import.ts +0 -18
- package/for_tokens/when_highlighting_event_metadata.ts +0 -63
- package/for_tokens/when_highlighting_specification_steps.ts +0 -28
- package/for_validateLines/when_validating_a_document/with_a_problem_of_every_coded_kind.ts +0 -77
- package/for_validateLines/when_validating_a_document/with_an_unknown_policy.ts +0 -33
- package/for_validateLines/when_validating_a_file_placed_by_an_import/with_names_declared_in_other_files.ts +0 -43
- package/for_validateLines/when_validating_event_context_paths/in_a_projection.ts +0 -97
- package/for_validateLines/when_validating_file_imports/at_every_level.ts +0 -36
- package/for_validateLines/when_validating_production_destinations/with_an_available_identifier.ts +0 -120
- package/hover-content.ts +0 -117
- package/hover.ts +0 -23
- package/index.ts +0 -125
- package/inlay-hints.ts +0 -17
- package/inline-event-validation.ts +0 -115
- package/keyword-docs.ts +0 -152
- package/language.ts +0 -212
- package/production-destinations.ts +0 -32
- package/sub-language-registry.ts +0 -42
- package/sub-languages/capture/CompletionProvider.ts +0 -365
- package/sub-languages/capture/HoverProvider.ts +0 -99
- package/sub-languages/capture/Validator.ts +0 -97
- package/sub-languages/capture/index.ts +0 -72
- package/sub-languages/capture/language.ts +0 -176
- package/sub-languages/cdl.ts +0 -76
- package/sub-languages/pdl.ts +0 -75
- package/sub-languages/projection/CodeActionProvider.ts +0 -123
- package/sub-languages/projection/CompletionProvider.ts +0 -660
- package/sub-languages/projection/HoverProvider.ts +0 -377
- package/sub-languages/projection/Validator.ts +0 -441
- package/sub-languages/projection/index.ts +0 -223
- package/sub-languages/projection/language.ts +0 -193
- package/symbols.ts +0 -374
- package/themes/screenplay-dark.ts +0 -33
- package/themes/screenplay-light.ts +0 -33
- package/tokens.ts +0 -174
- package/validation.ts +0 -443
- 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
|