@cratis/screenplay-language 0.1.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 (147) hide show
  1. package/README.md +52 -0
  2. package/completion-items.ts +128 -0
  3. package/completion-planner.ts +111 -0
  4. package/completions.ts +112 -0
  5. package/diagnostics.ts +52 -0
  6. package/dist/completion-items.d.ts +23 -0
  7. package/dist/completion-items.d.ts.map +1 -0
  8. package/dist/completion-items.js +105 -0
  9. package/dist/completion-items.js.map +1 -0
  10. package/dist/completion-planner.d.ts +25 -0
  11. package/dist/completion-planner.d.ts.map +1 -0
  12. package/dist/completion-planner.js +90 -0
  13. package/dist/completion-planner.js.map +1 -0
  14. package/dist/completions.d.ts +4 -0
  15. package/dist/completions.d.ts.map +1 -0
  16. package/dist/completions.js +78 -0
  17. package/dist/completions.js.map +1 -0
  18. package/dist/diagnostics.d.ts +5 -0
  19. package/dist/diagnostics.d.ts.map +1 -0
  20. package/dist/diagnostics.js +47 -0
  21. package/dist/diagnostics.js.map +1 -0
  22. package/dist/document-context.d.ts +5 -0
  23. package/dist/document-context.d.ts.map +1 -0
  24. package/dist/document-context.js +45 -0
  25. package/dist/document-context.js.map +1 -0
  26. package/dist/hover-content.d.ts +2 -0
  27. package/dist/hover-content.d.ts.map +1 -0
  28. package/dist/hover-content.js +64 -0
  29. package/dist/hover-content.js.map +1 -0
  30. package/dist/hover.d.ts +3 -0
  31. package/dist/hover.d.ts.map +1 -0
  32. package/dist/hover.js +15 -0
  33. package/dist/hover.js.map +1 -0
  34. package/dist/index.d.ts +23 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +54 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/keyword-docs.d.ts +4 -0
  39. package/dist/keyword-docs.d.ts.map +1 -0
  40. package/dist/keyword-docs.js +100 -0
  41. package/dist/keyword-docs.js.map +1 -0
  42. package/dist/language.d.ts +12 -0
  43. package/dist/language.d.ts.map +1 -0
  44. package/dist/language.js +117 -0
  45. package/dist/language.js.map +1 -0
  46. package/dist/sub-language-registry.d.ts +20 -0
  47. package/dist/sub-language-registry.d.ts.map +1 -0
  48. package/dist/sub-language-registry.js +18 -0
  49. package/dist/sub-language-registry.js.map +1 -0
  50. package/dist/sub-languages/capture/CompletionProvider.d.ts +32 -0
  51. package/dist/sub-languages/capture/CompletionProvider.d.ts.map +1 -0
  52. package/dist/sub-languages/capture/CompletionProvider.js +332 -0
  53. package/dist/sub-languages/capture/CompletionProvider.js.map +1 -0
  54. package/dist/sub-languages/capture/HoverProvider.d.ts +8 -0
  55. package/dist/sub-languages/capture/HoverProvider.d.ts.map +1 -0
  56. package/dist/sub-languages/capture/HoverProvider.js +87 -0
  57. package/dist/sub-languages/capture/HoverProvider.js.map +1 -0
  58. package/dist/sub-languages/capture/Validator.d.ts +6 -0
  59. package/dist/sub-languages/capture/Validator.d.ts.map +1 -0
  60. package/dist/sub-languages/capture/Validator.js +72 -0
  61. package/dist/sub-languages/capture/Validator.js.map +1 -0
  62. package/dist/sub-languages/capture/index.d.ts +6 -0
  63. package/dist/sub-languages/capture/index.d.ts.map +1 -0
  64. package/dist/sub-languages/capture/index.js +58 -0
  65. package/dist/sub-languages/capture/index.js.map +1 -0
  66. package/dist/sub-languages/capture/language.d.ts +6 -0
  67. package/dist/sub-languages/capture/language.d.ts.map +1 -0
  68. package/dist/sub-languages/capture/language.js +160 -0
  69. package/dist/sub-languages/capture/language.js.map +1 -0
  70. package/dist/sub-languages/cdl.d.ts +3 -0
  71. package/dist/sub-languages/cdl.d.ts.map +1 -0
  72. package/dist/sub-languages/cdl.js +74 -0
  73. package/dist/sub-languages/cdl.js.map +1 -0
  74. package/dist/sub-languages/pdl.d.ts +3 -0
  75. package/dist/sub-languages/pdl.d.ts.map +1 -0
  76. package/dist/sub-languages/pdl.js +67 -0
  77. package/dist/sub-languages/pdl.js.map +1 -0
  78. package/dist/sub-languages/projection/CodeActionProvider.d.ts +18 -0
  79. package/dist/sub-languages/projection/CodeActionProvider.d.ts.map +1 -0
  80. package/dist/sub-languages/projection/CodeActionProvider.js +93 -0
  81. package/dist/sub-languages/projection/CodeActionProvider.js.map +1 -0
  82. package/dist/sub-languages/projection/CompletionProvider.d.ts +41 -0
  83. package/dist/sub-languages/projection/CompletionProvider.d.ts.map +1 -0
  84. package/dist/sub-languages/projection/CompletionProvider.js +591 -0
  85. package/dist/sub-languages/projection/CompletionProvider.js.map +1 -0
  86. package/dist/sub-languages/projection/HoverProvider.d.ts +24 -0
  87. package/dist/sub-languages/projection/HoverProvider.d.ts.map +1 -0
  88. package/dist/sub-languages/projection/HoverProvider.js +298 -0
  89. package/dist/sub-languages/projection/HoverProvider.js.map +1 -0
  90. package/dist/sub-languages/projection/Validator.d.ts +22 -0
  91. package/dist/sub-languages/projection/Validator.d.ts.map +1 -0
  92. package/dist/sub-languages/projection/Validator.js +339 -0
  93. package/dist/sub-languages/projection/Validator.js.map +1 -0
  94. package/dist/sub-languages/projection/index.d.ts +49 -0
  95. package/dist/sub-languages/projection/index.d.ts.map +1 -0
  96. package/dist/sub-languages/projection/index.js +160 -0
  97. package/dist/sub-languages/projection/index.js.map +1 -0
  98. package/dist/sub-languages/projection/language.d.ts +5 -0
  99. package/dist/sub-languages/projection/language.d.ts.map +1 -0
  100. package/dist/sub-languages/projection/language.js +171 -0
  101. package/dist/sub-languages/projection/language.js.map +1 -0
  102. package/dist/symbols.d.ts +45 -0
  103. package/dist/symbols.d.ts.map +1 -0
  104. package/dist/symbols.js +100 -0
  105. package/dist/symbols.js.map +1 -0
  106. package/dist/themes/screenplay-dark.d.ts +4 -0
  107. package/dist/themes/screenplay-dark.d.ts.map +1 -0
  108. package/dist/themes/screenplay-dark.js +29 -0
  109. package/dist/themes/screenplay-dark.js.map +1 -0
  110. package/dist/themes/screenplay-light.d.ts +4 -0
  111. package/dist/themes/screenplay-light.d.ts.map +1 -0
  112. package/dist/themes/screenplay-light.js +29 -0
  113. package/dist/themes/screenplay-light.js.map +1 -0
  114. package/dist/tokens.d.ts +5 -0
  115. package/dist/tokens.d.ts.map +1 -0
  116. package/dist/tokens.js +121 -0
  117. package/dist/tokens.js.map +1 -0
  118. package/dist/validation.d.ts +10 -0
  119. package/dist/validation.d.ts.map +1 -0
  120. package/dist/validation.js +101 -0
  121. package/dist/validation.js.map +1 -0
  122. package/document-context.ts +51 -0
  123. package/hover-content.ts +76 -0
  124. package/hover.ts +23 -0
  125. package/index.ts +98 -0
  126. package/keyword-docs.ts +102 -0
  127. package/language.ts +130 -0
  128. package/package.json +44 -0
  129. package/sub-language-registry.ts +42 -0
  130. package/sub-languages/capture/CompletionProvider.ts +365 -0
  131. package/sub-languages/capture/HoverProvider.ts +99 -0
  132. package/sub-languages/capture/Validator.ts +97 -0
  133. package/sub-languages/capture/index.ts +72 -0
  134. package/sub-languages/capture/language.ts +176 -0
  135. package/sub-languages/cdl.ts +76 -0
  136. package/sub-languages/pdl.ts +69 -0
  137. package/sub-languages/projection/CodeActionProvider.ts +123 -0
  138. package/sub-languages/projection/CompletionProvider.ts +653 -0
  139. package/sub-languages/projection/HoverProvider.ts +338 -0
  140. package/sub-languages/projection/Validator.ts +387 -0
  141. package/sub-languages/projection/index.ts +223 -0
  142. package/sub-languages/projection/language.ts +193 -0
  143. package/symbols.ts +161 -0
  144. package/themes/screenplay-dark.ts +32 -0
  145. package/themes/screenplay-light.ts +32 -0
  146. package/tokens.ts +145 -0
  147. package/validation.ts +145 -0
@@ -0,0 +1,338 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type { editor, languages, Position } from 'monaco-editor';
5
+ import type { JsonSchema, JsonSchemaProperty, ReadModelInfo, DraftReadModelInfo } from './index';
6
+
7
+ export class HoverProvider implements languages.HoverProvider {
8
+ private readModels: ReadModelInfo[] = [];
9
+ private eventSchemas: Record<string, JsonSchema> = {};
10
+ private draftReadModel: DraftReadModelInfo | null = null;
11
+
12
+ setReadModels(readModels: ReadModelInfo[]): void {
13
+ this.readModels = readModels || [];
14
+ }
15
+
16
+ // Keep for backwards compatibility
17
+ setReadModelSchemas(schemas: JsonSchema[]): void {
18
+ this.readModels = schemas.map(schema => ({
19
+ identifier: this.getSchemaName(schema) || '',
20
+ displayName: this.getSchemaName(schema) || '',
21
+ schema
22
+ }));
23
+ }
24
+
25
+ setEventSchemas(schemas: Record<string, JsonSchema> | JsonSchema[]): void {
26
+ if (!schemas) {
27
+ this.eventSchemas = {};
28
+ return;
29
+ }
30
+
31
+ if (Array.isArray(schemas)) {
32
+ const map: Record<string, JsonSchema> = {};
33
+ schemas.forEach((s, i) => {
34
+ if (!s) return;
35
+ const name = this.getSchemaName(s) || `Event${i + 1}`;
36
+ map[name] = s;
37
+ });
38
+ this.eventSchemas = map;
39
+ return;
40
+ }
41
+
42
+ this.eventSchemas = schemas;
43
+ }
44
+
45
+ setDraftReadModel(draft: DraftReadModelInfo | null): void {
46
+ this.draftReadModel = draft;
47
+ }
48
+
49
+ provideHover(model: editor.ITextModel, position: Position): languages.ProviderResult<languages.Hover> {
50
+ const word = model.getWordAtPosition(position);
51
+ if (!word) return null;
52
+
53
+ const wordText = word.word;
54
+
55
+ // Check if we're on the projection name (before '=>')
56
+ const lineContent = model.getLineContent(position.lineNumber);
57
+ const projectionNameMatch = this.getProjectionNameAtPosition(lineContent, position.column);
58
+ if (projectionNameMatch) {
59
+ return {
60
+ contents: [{ value: 'The name of the projection' }],
61
+ range: {
62
+ startLineNumber: position.lineNumber,
63
+ startColumn: word.startColumn,
64
+ endLineNumber: position.lineNumber,
65
+ endColumn: word.endColumn,
66
+ },
67
+ };
68
+ }
69
+
70
+ // Check if it's a keyword
71
+ const keywordInfo = this.getKeywordInfo(wordText);
72
+ if (keywordInfo) {
73
+ return {
74
+ contents: [{ value: keywordInfo }],
75
+ range: {
76
+ startLineNumber: position.lineNumber,
77
+ startColumn: word.startColumn,
78
+ endLineNumber: position.lineNumber,
79
+ endColumn: word.endColumn,
80
+ },
81
+ };
82
+ }
83
+
84
+ // Check if it's a built-in expression
85
+ const expressionInfo = this.getExpressionInfo(wordText);
86
+ if (expressionInfo) {
87
+ return {
88
+ contents: [{ value: expressionInfo }],
89
+ range: {
90
+ startLineNumber: position.lineNumber,
91
+ startColumn: word.startColumn,
92
+ endLineNumber: position.lineNumber,
93
+ endColumn: word.endColumn,
94
+ },
95
+ };
96
+ }
97
+
98
+ // Check if it's an event type
99
+ if (this.eventSchemas[wordText]) {
100
+ const schema = this.eventSchemas[wordText];
101
+ const description = this.getSchemaDescription(schema);
102
+ const properties = this.getSchemaProperties(schema);
103
+
104
+ let content = `**Event Type:** \`${wordText}\`\n\n`;
105
+ if (description) {
106
+ content += `${description}\n\n`;
107
+ }
108
+ if (properties.length > 0) {
109
+ content += `**Properties:**\n\n${properties.map(p => `- \`${p.name}\`: ${p.type}`).join('\n')}`;
110
+ }
111
+
112
+ return {
113
+ contents: [{ value: content }],
114
+ range: {
115
+ startLineNumber: position.lineNumber,
116
+ startColumn: word.startColumn,
117
+ endLineNumber: position.lineNumber,
118
+ endColumn: word.endColumn,
119
+ },
120
+ };
121
+ }
122
+
123
+ // Check if it's a draft read model
124
+ if (this.isDraftReadModel(wordText)) {
125
+ const description = this.getSchemaDescription(this.draftReadModel!.schema);
126
+ const properties = this.getSchemaProperties(this.draftReadModel!.schema);
127
+ const identifier = this.draftReadModel!.identifier;
128
+ const displayName = this.draftReadModel!.displayName;
129
+
130
+ let content = `**Read Model (Draft):** \`${displayName}\`\n\n**Identifier:** \`${identifier}\`\n\n`;
131
+ if (description) {
132
+ content += `${description}\n\n`;
133
+ }
134
+ if (properties.length > 0) {
135
+ content += `**Properties:**\n\n${properties.map(p => `- \`${p.name}\`: ${p.type}`).join('\n')}`;
136
+ }
137
+
138
+ return {
139
+ contents: [{ value: content }],
140
+ range: {
141
+ startLineNumber: position.lineNumber,
142
+ startColumn: word.startColumn,
143
+ endLineNumber: position.lineNumber,
144
+ endColumn: word.endColumn,
145
+ },
146
+ };
147
+ }
148
+
149
+ // Check if it's a read model
150
+ const readModel = this.resolveReadModelInfo(wordText);
151
+ if (readModel) {
152
+ const description = this.getSchemaDescription(readModel.schema);
153
+ const properties = this.getSchemaProperties(readModel.schema);
154
+
155
+ let content = `**Read Model:** \`${readModel.displayName}\`\n\n**Identifier:** \`${readModel.identifier}\`\n\n`;
156
+ if (description) {
157
+ content += `${description}\n\n`;
158
+ }
159
+ if (properties.length > 0) {
160
+ content += `**Properties:**\n\n${properties.map(p => `- \`${p.name}\`: ${p.type}`).join('\n')}`;
161
+ }
162
+
163
+ return {
164
+ contents: [{ value: content }],
165
+ range: {
166
+ startLineNumber: position.lineNumber,
167
+ startColumn: word.startColumn,
168
+ endLineNumber: position.lineNumber,
169
+ endColumn: word.endColumn,
170
+ },
171
+ };
172
+ }
173
+
174
+ // Check if it's a property in the current context
175
+ const propertyInfo = this.getPropertyInfo(model, position, wordText);
176
+ if (propertyInfo) {
177
+ return {
178
+ contents: [{ value: propertyInfo }],
179
+ range: {
180
+ startLineNumber: position.lineNumber,
181
+ startColumn: word.startColumn,
182
+ endLineNumber: position.lineNumber,
183
+ endColumn: word.endColumn,
184
+ },
185
+ };
186
+ }
187
+
188
+ return null;
189
+ }
190
+
191
+ private getKeywordInfo(word: string): string | null {
192
+ const keywords: Record<string, string> = {
193
+ 'projection': '**projection** - Defines a projection that transforms events into a read model',
194
+ 'from': '**from** *<EventType>*\n\nHandles specific event types and maps their properties to the read model.',
195
+ '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.',
196
+ 'every': '**every**\n\nHandles all events the projection **explicitly subscribes to** through `from` blocks, with automatic property mapping.',
197
+ 'key': '**key** *<expression>*\n\nDefines the projection key or composite key type.',
198
+ 'parent': '**parent** *<expression>*\n\nDefines a parent relationship for hierarchical projections.',
199
+ 'join': '**join** *<collection>* **on** *<property>*\n\nJoins with a child collection based on a property.',
200
+ 'children': '**children** *<collection>* **id** *<expression>*\n\nDefines operations on child collections.',
201
+ 'remove': '**remove with** *<EventType>* or **remove via join on** *<EventType>*\n\nRemoves items from the read model based on events.',
202
+ 'automap': '**automap** or **no automap**\n\nControls automatic property mapping from events to the read model.',
203
+ 'increment': '**increment** *<property>*\n\nIncrements a numeric property by 1.',
204
+ 'decrement': '**decrement** *<property>*\n\nDecrements a numeric property by 1.',
205
+ 'count': '**count** *<property>*\n\nCounts occurrences and stores in a property.',
206
+ 'add': '**add** *<property>* **by** *<expression>*\n\nAdds a value to a property.',
207
+ 'subtract': '**subtract** *<property>* **by** *<expression>*\n\nSubtracts a value from a property.',
208
+ 'exclude': '**exclude children**\n\nExcludes child collections from automatic mapping.',
209
+ 'events': '**events** *<EventType>*, ...\n\nSpecifies which event types to include in a join.',
210
+ 'via': 'Used with **remove via join on** to remove items via a join relationship.',
211
+ 'with': 'Used with **remove with** to specify which event type triggers removal.',
212
+ 'on': 'Used with **join** to specify the join property.',
213
+ 'by': 'Used with **add** or **subtract** to specify the value.',
214
+ 'id': 'Used with **children** to specify the child identifier expression.',
215
+ 'no': 'Used with **no automap** to disable automatic property mapping.',
216
+ };
217
+
218
+ return keywords[word] || null;
219
+ }
220
+
221
+ private getExpressionInfo(word: string): string | null {
222
+ const expressions: Record<string, string> = {
223
+ '$eventSourceId': '**$eventSourceId**\n\nThe identifier of the event source (aggregate/entity) that generated the event.',
224
+ '$causedBy': '**$causedBy**\n\nInformation about who/what caused the event.\n\n**Properties:**\n- `subject` - The subject identifier\n- `name` - The display name\n- `userName` - The username',
225
+ '$eventContext': '**$eventContext**\n\nAccess to event context properties.\n\n**Properties:**\n- `sequenceNumber` - The event sequence number\n- `occurred` - When the event occurred\n- `eventSourceId` - The event source identifier\n- `namespace` - The event namespace',
226
+ '$occurred': '**$occurred**\n\nThe timestamp when the event occurred.',
227
+ '$namespace': '**$namespace**\n\nThe namespace of the event.',
228
+ };
229
+
230
+ return expressions[word] || null;
231
+ }
232
+
233
+ private getPropertyInfo(model: editor.ITextModel, position: Position, propertyName: string): string | null {
234
+ // Try to find the active read model
235
+ const firstLine = model.getLineContent(1).trim();
236
+ const match = firstLine.match(/^projection\s+[\w.]+\s*=>\s*([\w.]+)/);
237
+ if (!match) return null;
238
+
239
+ const readModelName = match[1];
240
+ const draftMatch = this.isDraftReadModel(readModelName) ? this.draftReadModel : null;
241
+ const resolvedReadModel = draftMatch ? null : this.resolveReadModelInfo(readModelName);
242
+ const activeSchema = draftMatch?.schema || resolvedReadModel?.schema;
243
+
244
+ if (activeSchema?.properties && activeSchema.properties[propertyName]) {
245
+ const prop = activeSchema.properties[propertyName];
246
+ const type = this.getPropertyTypeDescription(prop);
247
+ const description = prop.description;
248
+
249
+ let content = `**Property:** \`${propertyName}\`\n\n**Type:** \`${type}\``;
250
+ if (description) {
251
+ content += `\n\n${description}`;
252
+ }
253
+ return content;
254
+ }
255
+
256
+ // Check if it's an event property
257
+ const eventType = this.getCurrentEventType(model, position.lineNumber);
258
+ if (eventType && this.eventSchemas[eventType]) {
259
+ const eventSchema = this.eventSchemas[eventType];
260
+ if (eventSchema.properties && eventSchema.properties[propertyName]) {
261
+ const prop = eventSchema.properties[propertyName];
262
+ const type = this.getPropertyTypeDescription(prop);
263
+ const description = prop.description;
264
+
265
+ let content = `**Event Property:** \`${propertyName}\`\n\n**Type:** \`${type}\``;
266
+ if (description) {
267
+ content += `\n\n${description}`;
268
+ }
269
+ return content;
270
+ }
271
+ }
272
+
273
+ return null;
274
+ }
275
+
276
+ private resolveReadModelInfo(readModelToken: string): ReadModelInfo | null {
277
+ return this.readModels.find(rm => rm.identifier === readModelToken || rm.displayName === readModelToken) || null;
278
+ }
279
+
280
+ private isDraftReadModel(readModelToken: string): boolean {
281
+ return !!this.draftReadModel && (this.draftReadModel.identifier === readModelToken || this.draftReadModel.displayName === readModelToken);
282
+ }
283
+
284
+ private getCurrentEventType(model: editor.ITextModel, lineNumber: number): string | null {
285
+ for (let i = lineNumber; i >= 1; i--) {
286
+ const line = model.getLineContent(i).trim();
287
+ const fromMatch = line.match(/^from\s+([\w-]+)/);
288
+ if (fromMatch) {
289
+ return fromMatch[1];
290
+ }
291
+ }
292
+ return null;
293
+ }
294
+
295
+ private getProjectionNameAtPosition(lineContent: string, column: number): boolean {
296
+ // Check if we're on line 1 (projection declaration line)
297
+ const projectionMatch = lineContent.match(/^\s*projection\s+(\S+)\s*=>/);
298
+ if (!projectionMatch) return false;
299
+
300
+ const projectionKeywordEnd = lineContent.indexOf('projection') + 'projection'.length;
301
+ const arrowStart = lineContent.indexOf('=>');
302
+
303
+ // Check if cursor is between 'projection' keyword and '=>'
304
+ // and is on the projection name itself
305
+ return column > projectionKeywordEnd && column <= arrowStart;
306
+ }
307
+
308
+ private getSchemaName(schema: JsonSchema): string | undefined {
309
+ if (schema.name) return schema.name;
310
+ if (schema.title) return schema.title;
311
+ if (typeof schema.$id === 'string') {
312
+ const parts = schema.$id.split('/');
313
+ return parts[parts.length - 1] || schema.$id;
314
+ }
315
+ return undefined;
316
+ }
317
+
318
+ private getSchemaDescription(schema: JsonSchema): string | undefined {
319
+ return schema.description || undefined;
320
+ }
321
+
322
+ private getSchemaProperties(schema: JsonSchema): Array<{ name: string; type: string }> {
323
+ if (!schema.properties) return [];
324
+
325
+ return Object.entries(schema.properties).map(([name, prop]) => ({
326
+ name,
327
+ type: this.getPropertyTypeDescription(prop),
328
+ }));
329
+ }
330
+
331
+ private getPropertyTypeDescription(propSchema: JsonSchemaProperty): string {
332
+ if (propSchema.type === 'array' && propSchema.items) {
333
+ const itemType = propSchema.items.type || propSchema.items.$ref?.split('/').pop() || 'unknown';
334
+ return `${itemType}[]`;
335
+ }
336
+ return propSchema.type || propSchema.$ref?.split('/').pop() || 'unknown';
337
+ }
338
+ }