@lokascript/framework 2.3.1 → 2.5.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 (54) hide show
  1. package/dist/api/index.js +29 -516
  2. package/dist/api/index.js.map +1 -1
  3. package/dist/core/index.js +20 -128
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/pattern-matching/index.js +19 -12
  6. package/dist/core/pattern-matching/index.js.map +1 -1
  7. package/dist/core/tokenization/index.js.map +1 -1
  8. package/dist/core/types.d.ts +7 -354
  9. package/dist/core/types.d.ts.map +1 -1
  10. package/dist/core/types.js +21 -129
  11. package/dist/core/types.js.map +1 -1
  12. package/dist/generation/diagnostics.d.ts +5 -115
  13. package/dist/generation/diagnostics.d.ts.map +1 -1
  14. package/dist/generation/index.js +20 -77
  15. package/dist/generation/index.js.map +1 -1
  16. package/dist/index.cjs +189 -1458
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +108 -1347
  19. package/dist/index.js.map +1 -1
  20. package/dist/interfaces/value-extractor.d.ts +20 -0
  21. package/dist/interfaces/value-extractor.d.ts.map +1 -1
  22. package/dist/ir/explicit-parser.d.ts +2 -83
  23. package/dist/ir/explicit-parser.d.ts.map +1 -1
  24. package/dist/ir/explicit-renderer.d.ts +2 -31
  25. package/dist/ir/explicit-renderer.d.ts.map +1 -1
  26. package/dist/ir/index.js +89 -1260
  27. package/dist/ir/index.js.map +1 -1
  28. package/dist/ir/protocol-json.d.ts +2 -70
  29. package/dist/ir/protocol-json.d.ts.map +1 -1
  30. package/dist/ir/references.d.ts +2 -16
  31. package/dist/ir/references.d.ts.map +1 -1
  32. package/dist/ir/types.d.ts +2 -148
  33. package/dist/ir/types.d.ts.map +1 -1
  34. package/dist/parsing/index.js +19 -12
  35. package/dist/parsing/index.js.map +1 -1
  36. package/dist/schema/command-schema.d.ts +3 -92
  37. package/dist/schema/command-schema.d.ts.map +1 -1
  38. package/dist/schema/index.js +3 -20
  39. package/dist/schema/index.js.map +1 -1
  40. package/dist/testing/index.js +1066 -712
  41. package/dist/testing/index.js.map +1 -1
  42. package/package.json +6 -2
  43. package/src/core/types.ts +71 -600
  44. package/src/generation/diagnostics.ts +12 -233
  45. package/src/interfaces/value-extractor.ts +32 -0
  46. package/src/ir/explicit-parser.ts +12 -792
  47. package/src/ir/explicit-renderer.ts +2 -195
  48. package/src/ir/from-interchange.test.ts +91 -4
  49. package/src/ir/from-interchange.ts +59 -0
  50. package/src/ir/protocol-json.test.ts +16 -7
  51. package/src/ir/protocol-json.ts +9 -748
  52. package/src/ir/references.ts +2 -30
  53. package/src/ir/types.ts +18 -190
  54. package/src/schema/command-schema.ts +3 -134
package/src/core/types.ts CHANGED
@@ -1,272 +1,76 @@
1
1
  /**
2
- * Generic Semantic Types for Multilingual DSLs
2
+ * Core Types — re-exported from @lokascript/intent + framework-specific extensions
3
3
  *
4
- * This module defines the canonical semantic representation that all languages
5
- * parse to and render from. The semantic layer is language-neutral - it captures
6
- * the MEANING of DSL commands independent of surface syntax.
4
+ * The universal semantic types (SemanticNode, SemanticValue, etc.) now live in
5
+ * @lokascript/intent and are re-exported here for backward compatibility.
7
6
  *
8
- * These types are domain-agnostic - they work for any DSL (SQL, animations, etc.)
9
- */
10
-
11
- import type { Diagnostic } from '../generation/diagnostics';
12
-
13
- // =============================================================================
14
- // Action Types (Generic)
15
- // =============================================================================
16
-
17
- /**
18
- * Action type represents the command/operation in a DSL.
19
- * This is generic - each DSL defines its own set of actions.
20
- *
21
- * Examples:
22
- * - Hyperscript: 'toggle', 'add', 'remove', 'fetch'
23
- * - SQL: 'select', 'insert', 'update', 'delete'
24
- * - Animation: 'animate', 'fade', 'slide', 'rotate'
25
- */
26
- export type ActionType = string;
27
-
28
- /**
29
- * Semantic role represents the grammatical function of a part of a command.
30
- * This is generic - each DSL can define its own roles, though common ones include:
31
- *
32
- * Common roles across DSLs:
33
- * - action: The verb/command
34
- * - patient: What is being acted upon
35
- * - destination: Where something goes
36
- * - source: Where something comes from
37
- * - condition: Boolean expressions
38
- * - quantity: Numeric amounts
39
- * - duration: Time spans
40
- *
41
- * Examples:
42
- * - Hyperscript: 'patient', 'destination', 'source', 'event', 'style'
43
- * - SQL: 'columns', 'table', 'condition', 'orderBy', 'limit'
44
- * - Animation: 'target', 'duration', 'easing', 'delay'
45
- */
46
- export type SemanticRole = string;
47
-
48
- // =============================================================================
49
- // Semantic Values
50
- // =============================================================================
51
-
52
- /**
53
- * A semantic value represents a typed piece of data in a semantic node.
54
- * Values are language-neutral - they capture what something IS, not how it's written.
55
- */
56
- export type SemanticValue =
57
- | LiteralValue
58
- | SelectorValue
59
- | ReferenceValue
60
- | PropertyPathValue
61
- | ExpressionValue
62
- | FlagValue;
63
-
64
- /**
65
- * Expected value types for role tokens.
66
- * Shared between RoleSpec (command-schemas) and RolePatternToken.
67
- */
68
- export type ExpectedType = SemanticValue['type'];
69
-
70
- export interface LiteralValue {
71
- readonly type: 'literal';
72
- readonly value: string | number | boolean;
73
- readonly dataType?: 'string' | 'number' | 'boolean' | 'duration';
74
- }
75
-
76
- export interface SelectorValue {
77
- readonly type: 'selector';
78
- readonly value: string; // The selector: #id, .class, [attr], table-name, etc.
79
- readonly selectorKind?: 'id' | 'class' | 'attribute' | 'element' | 'complex' | 'identifier';
80
- }
81
-
82
- export interface ReferenceValue {
83
- readonly type: 'reference';
84
- readonly value: string; // Generic reference name (DSL-specific)
85
- }
86
-
87
- export interface PropertyPathValue {
88
- readonly type: 'property-path';
89
- readonly object: SemanticValue;
90
- readonly property: string;
91
- }
92
-
93
- export interface ExpressionValue {
94
- readonly type: 'expression';
95
- /** Raw expression string for complex expressions that need further parsing */
96
- readonly raw: string;
97
- }
98
-
99
- /**
100
- * A boolean flag value — present (+flag) or negated (~flag).
101
- * Used in declarative domains for no-value attributes like primary-key, not-null.
102
- */
103
- export interface FlagValue {
104
- readonly type: 'flag';
105
- readonly name: string;
106
- readonly enabled: boolean;
107
- }
108
-
109
- // =============================================================================
110
- // Annotations & Diagnostics (v1.2)
111
- // =============================================================================
112
-
113
- /** A metadata annotation on a node (v1.2). */
114
- export interface Annotation {
115
- readonly name: string;
116
- readonly value?: string;
117
- }
118
-
119
- /**
120
- * A type constraint diagnostic attached to a node (v1.2).
121
- *
122
- * Named `ProtocolDiagnostic` to avoid collision with the framework's own
123
- * `Diagnostic` type (which has `severity: 'error' | 'warning' | 'info'`
124
- * and `suggestion?: string`).
125
- */
126
- export interface ProtocolDiagnostic {
127
- readonly level: 'error' | 'warning';
128
- readonly role: string;
129
- readonly message: string;
130
- readonly code: string;
131
- }
132
-
133
- /** Async coordination variant (v1.2). */
134
- export type AsyncVariant = 'all' | 'race';
135
-
136
- /** A single arm in a match command (v1.2). */
137
- export interface MatchArm {
138
- readonly pattern: SemanticValue;
139
- readonly body: SemanticNode[];
140
- }
141
-
142
- // =============================================================================
143
- // Semantic Nodes
144
- // =============================================================================
145
-
146
- /**
147
- * Base interface for all semantic nodes.
148
- * Semantic nodes capture the MEANING of DSL constructs.
149
- */
150
- export interface SemanticNode {
151
- readonly kind: 'command' | 'event-handler' | 'conditional' | 'compound' | 'loop';
152
- readonly action: ActionType;
153
- readonly roles: ReadonlyMap<SemanticRole, SemanticValue>;
154
- readonly metadata?: SemanticMetadata;
155
- /** Metadata annotations (v1.2). */
156
- readonly annotations?: readonly Annotation[];
157
- /** Diagnostics from parsing, validation, and schema checks (v1.2, extended v1.2.1). */
158
- readonly diagnostics?: readonly Diagnostic[];
159
- }
160
-
161
- /**
162
- * Metadata about the source of a semantic node.
163
- * Useful for debugging, error messages, and round-trip conversion.
164
- */
165
- export interface SemanticMetadata {
166
- readonly sourceLanguage?: string;
167
- readonly sourceText?: string;
168
- readonly sourcePosition?: SourcePosition;
169
- readonly patternId?: string;
170
- /**
171
- * Confidence score for the parse (0-1).
172
- * Higher values indicate more certain matches.
173
- * - 1.0: Exact match with all roles captured
174
- * - 0.8-0.99: High confidence with minor uncertainty
175
- * - 0.6-0.8: Medium confidence (normalization, defaults applied)
176
- * - <0.6: Low confidence (may need fallback)
177
- */
178
- readonly confidence?: number;
179
- }
180
-
181
- export interface SourcePosition {
182
- readonly start: number;
183
- readonly end: number;
184
- readonly line?: number;
185
- readonly column?: number;
186
- }
187
-
188
- /**
189
- * A command semantic node - represents a single DSL command.
190
- *
191
- * v1.2 adds optional fields for try/catch/finally, async coordination (all/race),
192
- * and pattern matching (match/arms). These are encoded as fields on command nodes
193
- * (matching the protocol wire format) rather than new node kinds.
194
- */
195
- export interface CommandSemanticNode extends SemanticNode {
196
- readonly kind: 'command';
197
- /** try body — commands to execute in the try block (v1.2). */
198
- readonly body?: readonly SemanticNode[];
199
- /** catch branch — commands to execute on error (v1.2). */
200
- readonly catchBranch?: readonly SemanticNode[];
201
- /** finally branch — cleanup commands that always execute (v1.2). */
202
- readonly finallyBranch?: readonly SemanticNode[];
203
- /** Async coordination variant: all (wait for all) or race (first wins) (v1.2). */
204
- readonly asyncVariant?: AsyncVariant;
205
- /** Async body — concurrent commands for all/race (v1.2). */
206
- readonly asyncBody?: readonly SemanticNode[];
207
- /** Match arms — pattern/body pairs for match command (v1.2). */
208
- readonly arms?: readonly MatchArm[];
209
- /** Default arm — executed when no match arm matches (v1.2). */
210
- readonly defaultArm?: readonly SemanticNode[];
211
- }
212
-
213
- /**
214
- * An event handler semantic node - represents trigger-based commands.
215
- * E.g., "on click [commands]" in hyperscript, or "when condition [commands]" in other DSLs.
216
- */
217
- export interface EventHandlerSemanticNode extends SemanticNode {
218
- readonly kind: 'event-handler';
219
- readonly body: SemanticNode[];
220
- readonly eventModifiers?: EventModifiers;
221
- readonly additionalEvents?: readonly SemanticValue[];
222
- readonly parameterNames?: readonly string[];
223
- }
224
-
225
- export interface EventModifiers {
226
- readonly once?: boolean;
227
- readonly debounce?: number;
228
- readonly throttle?: number;
229
- readonly queue?: 'first' | 'last' | 'all' | 'none';
230
- readonly from?: SemanticValue;
231
- }
232
-
233
- /**
234
- * A conditional semantic node - represents "if [condition] then [body] else [body]".
235
- */
236
- export interface ConditionalSemanticNode extends SemanticNode {
237
- readonly kind: 'conditional';
238
- readonly thenBranch: SemanticNode[];
239
- readonly elseBranch?: SemanticNode[];
240
- }
241
-
242
- /**
243
- * A compound semantic node - represents multiple chained statements.
244
- */
245
- export interface CompoundSemanticNode extends SemanticNode {
246
- readonly kind: 'compound';
247
- readonly statements: SemanticNode[];
248
- readonly chainType: 'then' | 'and' | 'async' | 'sequential' | 'pipe';
249
- }
250
-
251
- /**
252
- * Loop variant discriminant for different loop types.
253
- */
254
- export type LoopVariant = 'forever' | 'times' | 'for' | 'while' | 'until';
255
-
256
- /**
257
- * A loop semantic node - represents repeat/for/while loops.
258
- */
259
- export interface LoopSemanticNode extends SemanticNode {
260
- readonly kind: 'loop';
261
- readonly loopVariant: LoopVariant;
262
- readonly body: SemanticNode[];
263
- readonly loopVariable?: string;
264
- readonly indexVariable?: string;
265
- }
266
-
267
- // =============================================================================
268
- // Tokenization Types
269
- // =============================================================================
7
+ * Framework-specific types (tokenization, pattern matching) are defined below.
8
+ */
9
+
10
+ // =============================================================================
11
+ // Re-export everything from @lokascript/intent
12
+ // =============================================================================
13
+
14
+ export type {
15
+ ActionType,
16
+ SemanticRole,
17
+ SemanticValue,
18
+ ExpectedType,
19
+ LiteralValue,
20
+ SelectorValue,
21
+ ReferenceValue,
22
+ PropertyPathValue,
23
+ ExpressionValue,
24
+ FlagValue,
25
+ Annotation,
26
+ ProtocolDiagnostic,
27
+ AsyncVariant,
28
+ MatchArm,
29
+ SemanticNode,
30
+ SemanticMetadata,
31
+ SourcePosition,
32
+ CommandSemanticNode,
33
+ EventHandlerSemanticNode,
34
+ EventModifiers,
35
+ ConditionalSemanticNode,
36
+ CompoundSemanticNode,
37
+ LoopVariant,
38
+ LoopSemanticNode,
39
+ LSEEnvelope,
40
+ } from '@lokascript/intent';
41
+
42
+ export {
43
+ createLiteral,
44
+ createSelector,
45
+ createReference,
46
+ createPropertyPath,
47
+ createExpression,
48
+ createFlag,
49
+ createCommandNode,
50
+ createEventHandlerNode,
51
+ createConditionalNode,
52
+ createCompoundNode,
53
+ createLoopNode,
54
+ createTryNode,
55
+ createAsyncNode,
56
+ createMatchNode,
57
+ extractValue,
58
+ extractRoleValue,
59
+ getRoleValue,
60
+ } from '@lokascript/intent';
61
+
62
+ // =============================================================================
63
+ // Framework-specific types (multilingual DSL infrastructure)
64
+ // =============================================================================
65
+
66
+ import type {
67
+ ActionType,
68
+ SemanticRole,
69
+ SemanticValue,
70
+ SelectorValue,
71
+ ExpectedType,
72
+ SourcePosition,
73
+ } from '@lokascript/intent';
270
74
 
271
75
  /**
272
76
  * Token kind - categorizes what type of token this is.
@@ -290,13 +94,9 @@ export interface LanguageToken {
290
94
  readonly value: string;
291
95
  readonly kind: TokenKind;
292
96
  readonly position: SourcePosition;
293
- /** Normalized form from explicit keyword map */
294
97
  readonly normalized?: string;
295
- /** Morphologically normalized stem */
296
98
  readonly stem?: string;
297
- /** Confidence in the morphological stem (0.0-1.0) */
298
99
  readonly stemConfidence?: number;
299
- /** Additional metadata for specific token types */
300
100
  readonly metadata?: Record<string, unknown>;
301
101
  }
302
102
 
@@ -306,29 +106,14 @@ export interface LanguageToken {
306
106
  export interface TokenStream {
307
107
  readonly tokens: readonly LanguageToken[];
308
108
  readonly language: string;
309
-
310
- /** Look at token at current position + offset without consuming */
311
109
  peek(offset?: number): LanguageToken | null;
312
-
313
- /** Consume and return current token, advance position */
314
110
  advance(): LanguageToken;
315
-
316
- /** Check if we've consumed all tokens */
317
111
  isAtEnd(): boolean;
318
-
319
- /** Save current position for backtracking */
320
112
  mark(): StreamMark;
321
-
322
- /** Restore to a saved position */
323
113
  reset(mark: StreamMark): void;
324
-
325
- /** Get current position */
326
114
  position(): number;
327
115
  }
328
116
 
329
- /**
330
- * Stream mark for backtracking during parsing.
331
- */
332
117
  export interface StreamMark {
333
118
  readonly position: number;
334
119
  }
@@ -339,11 +124,7 @@ export interface StreamMark {
339
124
  export interface LanguageTokenizer {
340
125
  readonly language: string;
341
126
  readonly direction: 'ltr' | 'rtl';
342
-
343
- /** Convert input string to token stream */
344
127
  tokenize(input: string): TokenStream;
345
-
346
- /** Classify a single token */
347
128
  classifyToken(token: string): TokenKind;
348
129
  }
349
130
 
@@ -351,45 +132,26 @@ export interface LanguageTokenizer {
351
132
  // Pattern Matching Types
352
133
  // =============================================================================
353
134
 
354
- /**
355
- * A language pattern defines how a semantic structure appears in a specific language.
356
- */
357
135
  export interface LanguagePattern {
358
- /** Unique identifier for this pattern */
359
136
  readonly id: string;
360
- /** ISO 639-1 language code */
361
137
  readonly language: string;
362
- /** Which command this pattern matches */
363
138
  readonly command: ActionType;
364
- /** Priority for disambiguation (higher = checked first) */
365
139
  readonly priority: number;
366
- /** The pattern template with role placeholders */
367
140
  readonly template: PatternTemplate;
368
- /** Rules for extracting semantic roles from matched tokens */
369
141
  readonly extraction: ExtractionRules;
370
- /** Optional constraints on when this pattern applies */
371
142
  readonly constraints?: PatternConstraints;
372
143
  }
373
144
 
374
- /**
375
- * Pattern template - defines expected token sequence.
376
- */
377
145
  export interface PatternTemplate {
378
- /** Human-readable template string */
379
146
  readonly format: string;
380
- /** Parsed token sequence for matching */
381
147
  readonly tokens: PatternToken[];
382
148
  }
383
149
 
384
- /**
385
- * Pattern token - literal, role placeholder, or group.
386
- */
387
150
  export type PatternToken = LiteralPatternToken | RolePatternToken | GroupPatternToken;
388
151
 
389
152
  export interface LiteralPatternToken {
390
153
  readonly type: 'literal';
391
154
  readonly value: string;
392
- /** Alternative spellings/forms that also match */
393
155
  readonly alternatives?: string[];
394
156
  }
395
157
 
@@ -397,9 +159,7 @@ export interface RolePatternToken {
397
159
  readonly type: 'role';
398
160
  readonly role: SemanticRole;
399
161
  readonly optional?: boolean;
400
- /** Expected value types (for validation) */
401
162
  readonly expectedTypes?: Array<ExpectedType>;
402
- /** When true, captures all remaining tokens until next marker or end */
403
163
  readonly greedy?: boolean;
404
164
  }
405
165
 
@@ -409,326 +169,37 @@ export interface GroupPatternToken {
409
169
  readonly optional?: boolean;
410
170
  }
411
171
 
412
- /**
413
- * Rules for extracting semantic values from matched tokens.
414
- */
415
172
  export interface ExtractionRules {
416
173
  readonly [role: string]: ExtractionRule;
417
174
  }
418
175
 
419
176
  export interface ExtractionRule {
420
- /** Position-based extraction (0-indexed from pattern start) */
421
177
  readonly position?: number;
422
- /** Marker-based extraction (find value after this marker) */
423
178
  readonly marker?: string;
424
- /** Alternative markers that also work */
425
179
  readonly markerAlternatives?: string[];
426
- /** Transform the extracted value */
427
180
  readonly transform?: (raw: string) => SemanticValue;
428
- /** Default value if not found (for optional roles) */
429
181
  readonly default?: SemanticValue;
430
- /** Static value extraction */
431
182
  readonly value?: string;
432
- /** Extract value from a pattern role by name */
433
183
  readonly fromRole?: string;
434
184
  }
435
185
 
436
- /**
437
- * Additional constraints on pattern applicability.
438
- */
439
186
  export interface PatternConstraints {
440
- /** Required roles that must be present */
441
187
  readonly requiredRoles?: SemanticRole[];
442
- /** Roles that must NOT be present */
443
188
  readonly forbiddenRoles?: SemanticRole[];
444
- /** Valid selector types for the patient role */
445
189
  readonly validPatientTypes?: Array<SelectorValue['selectorKind']>;
446
- /** Pattern IDs this conflicts with */
447
190
  readonly conflictsWith?: string[];
448
191
  }
449
192
 
450
- /**
451
- * Result of matching a pattern against tokens.
452
- */
453
193
  export interface PatternMatchResult {
454
194
  readonly pattern: LanguagePattern;
455
195
  readonly captured: ReadonlyMap<SemanticRole, SemanticValue>;
456
196
  readonly consumedTokens: number;
457
- readonly confidence: number; // 0-1, how well the pattern matched
197
+ readonly confidence: number;
458
198
  }
459
199
 
460
- /**
461
- * Error when pattern matching fails.
462
- */
463
200
  export interface PatternMatchError {
464
201
  readonly message: string;
465
202
  readonly position: SourcePosition;
466
203
  readonly expectedPatterns?: string[];
467
204
  readonly partialMatch?: Partial<PatternMatchResult>;
468
205
  }
469
-
470
- // =============================================================================
471
- // Helper Functions
472
- // =============================================================================
473
-
474
- /**
475
- * Create a literal value
476
- */
477
- export function createLiteral(
478
- value: string | number | boolean,
479
- dataType?: 'string' | 'number' | 'boolean' | 'duration'
480
- ): LiteralValue {
481
- return dataType ? { type: 'literal', value, dataType } : { type: 'literal', value };
482
- }
483
-
484
- /**
485
- * Create a selector value
486
- */
487
- export function createSelector(
488
- value: string,
489
- selectorKind?: SelectorValue['selectorKind']
490
- ): SelectorValue {
491
- return selectorKind ? { type: 'selector', value, selectorKind } : { type: 'selector', value };
492
- }
493
-
494
- /**
495
- * Create a reference value
496
- */
497
- export function createReference(value: string): ReferenceValue {
498
- return { type: 'reference', value };
499
- }
500
-
501
- /**
502
- * Create a property path value
503
- */
504
- export function createPropertyPath(object: SemanticValue, property: string): PropertyPathValue {
505
- return { type: 'property-path', object, property };
506
- }
507
-
508
- /**
509
- * Create an expression value
510
- */
511
- export function createExpression(raw: string): ExpressionValue {
512
- return { type: 'expression', raw };
513
- }
514
-
515
- /**
516
- * Create a boolean flag value (+flag or ~flag)
517
- */
518
- export function createFlag(name: string, enabled: boolean = true): FlagValue {
519
- return { type: 'flag', name, enabled };
520
- }
521
-
522
- /**
523
- * Create a command semantic node
524
- */
525
- export function createCommandNode(
526
- action: ActionType,
527
- roles: Record<SemanticRole, SemanticValue> | Map<SemanticRole, SemanticValue>,
528
- metadata?: SemanticMetadata
529
- ): CommandSemanticNode {
530
- const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
531
- const node: CommandSemanticNode = {
532
- kind: 'command',
533
- action,
534
- roles: rolesMap,
535
- };
536
- if (metadata) {
537
- return { ...node, metadata };
538
- }
539
- return node;
540
- }
541
-
542
- /**
543
- * Create an event handler semantic node
544
- */
545
- export function createEventHandlerNode(
546
- action: ActionType,
547
- roles: Record<SemanticRole, SemanticValue> | Map<SemanticRole, SemanticValue>,
548
- body: SemanticNode[],
549
- metadata?: SemanticMetadata,
550
- eventModifiers?: EventModifiers
551
- ): EventHandlerSemanticNode {
552
- const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
553
- const base = {
554
- kind: 'event-handler' as const,
555
- action,
556
- roles: rolesMap,
557
- body,
558
- };
559
- return {
560
- ...base,
561
- ...(eventModifiers && { eventModifiers }),
562
- ...(metadata && { metadata }),
563
- };
564
- }
565
-
566
- /**
567
- * Create a conditional semantic node
568
- */
569
- export function createConditionalNode(
570
- action: ActionType,
571
- roles: Record<SemanticRole, SemanticValue> | Map<SemanticRole, SemanticValue>,
572
- thenBranch: SemanticNode[],
573
- elseBranch?: SemanticNode[],
574
- metadata?: SemanticMetadata
575
- ): ConditionalSemanticNode {
576
- const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
577
- const base = {
578
- kind: 'conditional' as const,
579
- action,
580
- roles: rolesMap,
581
- thenBranch,
582
- };
583
- return {
584
- ...base,
585
- ...(elseBranch && { elseBranch }),
586
- ...(metadata && { metadata }),
587
- };
588
- }
589
-
590
- /**
591
- * Create a compound semantic node
592
- */
593
- export function createCompoundNode(
594
- statements: SemanticNode[],
595
- chainType: CompoundSemanticNode['chainType'] = 'sequential',
596
- metadata?: SemanticMetadata
597
- ): CompoundSemanticNode {
598
- const base = {
599
- kind: 'compound' as const,
600
- action: 'compound' as const,
601
- roles: new Map(),
602
- statements,
603
- chainType,
604
- };
605
- return metadata ? { ...base, metadata } : base;
606
- }
607
-
608
- /**
609
- * Extract a string value from a SemanticValue.
610
- *
611
- * Handles all value types in the SemanticValue union:
612
- * - ExpressionValue: returns `raw`
613
- * - LiteralValue/SelectorValue/ReferenceValue: returns `value` as string
614
- * - PropertyPathValue: returns `object.property` path
615
- *
616
- * This is the standard way to get a display string from any semantic value,
617
- * eliminating the need for `as any` casts in domain code generators.
618
- */
619
- export function extractValue(value: SemanticValue): string {
620
- if (value.type === 'flag') return value.name;
621
- if ('raw' in value && value.raw !== undefined) return String(value.raw);
622
- if ('value' in value && value.value !== undefined) return String(value.value);
623
- if (value.type === 'property-path') return `${extractValue(value.object)}.${value.property}`;
624
- return '';
625
- }
626
-
627
- /**
628
- * Extract a string value from a named role on a SemanticNode.
629
- *
630
- * Convenience wrapper: looks up the role, returns empty string if missing.
631
- * Eliminates the common `const x = node.roles.get('role'); const val = x ? extractValue(x as any) : '';` pattern.
632
- */
633
- export function extractRoleValue(node: SemanticNode, role: string): string {
634
- const value = node.roles.get(role);
635
- if (!value) return '';
636
- return extractValue(value);
637
- }
638
-
639
- /**
640
- * Create a loop semantic node
641
- */
642
- export function createLoopNode(
643
- action: ActionType,
644
- roles: Record<SemanticRole, SemanticValue> | Map<SemanticRole, SemanticValue>,
645
- loopVariant: LoopVariant,
646
- body: SemanticNode[],
647
- loopVariable?: string,
648
- indexVariable?: string,
649
- metadata?: SemanticMetadata
650
- ): LoopSemanticNode {
651
- const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
652
- const base = {
653
- kind: 'loop' as const,
654
- action,
655
- roles: rolesMap,
656
- loopVariant,
657
- body,
658
- };
659
- return {
660
- ...base,
661
- ...(loopVariable && { loopVariable }),
662
- ...(indexVariable && { indexVariable }),
663
- ...(metadata && { metadata }),
664
- };
665
- }
666
-
667
- // =============================================================================
668
- // v1.2 Factory Helpers
669
- // =============================================================================
670
-
671
- /**
672
- * Create a try/catch/finally command node (v1.2).
673
- */
674
- export function createTryNode(
675
- body: SemanticNode[],
676
- catchBranch?: SemanticNode[],
677
- finallyBranch?: SemanticNode[],
678
- metadata?: SemanticMetadata
679
- ): CommandSemanticNode {
680
- return {
681
- kind: 'command',
682
- action: 'try',
683
- roles: new Map(),
684
- body,
685
- ...(catchBranch && catchBranch.length > 0 ? { catchBranch } : {}),
686
- ...(finallyBranch && finallyBranch.length > 0 ? { finallyBranch } : {}),
687
- ...(metadata ? { metadata } : {}),
688
- };
689
- }
690
-
691
- /**
692
- * Create an async coordination (all/race) command node (v1.2).
693
- */
694
- export function createAsyncNode(
695
- variant: AsyncVariant,
696
- asyncBody: SemanticNode[],
697
- metadata?: SemanticMetadata
698
- ): CommandSemanticNode {
699
- return {
700
- kind: 'command',
701
- action: variant,
702
- roles: new Map(),
703
- asyncVariant: variant,
704
- asyncBody,
705
- ...(metadata ? { metadata } : {}),
706
- };
707
- }
708
-
709
- /**
710
- * Create a match command node with pattern arms (v1.2).
711
- */
712
- export function createMatchNode(
713
- roles: Record<SemanticRole, SemanticValue> | Map<SemanticRole, SemanticValue>,
714
- arms: MatchArm[],
715
- defaultArm?: SemanticNode[],
716
- metadata?: SemanticMetadata
717
- ): CommandSemanticNode {
718
- const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
719
- return {
720
- kind: 'command',
721
- action: 'match',
722
- roles: rolesMap,
723
- arms,
724
- ...(defaultArm && defaultArm.length > 0 ? { defaultArm } : {}),
725
- ...(metadata ? { metadata } : {}),
726
- };
727
- }
728
-
729
- /** Wire format envelope with version metadata (v1.2). */
730
- export interface LSEEnvelope {
731
- readonly lseVersion: string;
732
- readonly features?: readonly string[];
733
- readonly nodes: readonly SemanticNode[];
734
- }