@living-architecture/riviere-extract-config-published-language 0.7.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/dist/index.js ADDED
@@ -0,0 +1,1102 @@
1
+ // src/published-language/validated-configuration.ts
2
+ var REQUIRED_FIELDS = {
3
+ api: ["apiType"],
4
+ useCase: [],
5
+ domainOp: ["operationName"],
6
+ event: ["eventName"],
7
+ eventHandler: ["subscribedEvents"],
8
+ ui: ["route"]
9
+ };
10
+ var COMPONENT_TYPES = [
11
+ "api",
12
+ "useCase",
13
+ "domainOp",
14
+ "event",
15
+ "eventHandler",
16
+ "ui"
17
+ ];
18
+ var ValidatedModule = class _ValidatedModule {
19
+ #values;
20
+ constructor(values) {
21
+ this.#values = values;
22
+ }
23
+ static parse(input) {
24
+ const errors = validateModule(input);
25
+ if (errors.length > 0) {
26
+ return { success: false, errors };
27
+ }
28
+ return { success: true, data: new _ValidatedModule(input) };
29
+ }
30
+ get name() {
31
+ return this.#values.name;
32
+ }
33
+ get domain() {
34
+ return this.#values.domain;
35
+ }
36
+ get path() {
37
+ return this.#values.path;
38
+ }
39
+ get glob() {
40
+ return this.#values.glob;
41
+ }
42
+ get modules() {
43
+ return this.#values.modules;
44
+ }
45
+ get api() {
46
+ return this.#values.api;
47
+ }
48
+ get useCase() {
49
+ return this.#values.useCase;
50
+ }
51
+ get domainOp() {
52
+ return this.#values.domainOp;
53
+ }
54
+ get event() {
55
+ return this.#values.event;
56
+ }
57
+ get eventHandler() {
58
+ return this.#values.eventHandler;
59
+ }
60
+ get ui() {
61
+ return this.#values.ui;
62
+ }
63
+ get customTypes() {
64
+ return this.#values.customTypes;
65
+ }
66
+ ruleFor(componentType) {
67
+ return this.#values[componentType];
68
+ }
69
+ };
70
+ var ValidatedConfiguration = class _ValidatedConfiguration {
71
+ constructor(modules, connections, schema) {
72
+ this.connections = connections;
73
+ this.schema = schema;
74
+ this.#modules = modules;
75
+ }
76
+ #modules;
77
+ static parse(input) {
78
+ const modules = [];
79
+ const moduleErrors = [];
80
+ input.modules.forEach((module, index) => {
81
+ const result = ValidatedModule.parse(module);
82
+ if (result.success) {
83
+ modules.push(result.data);
84
+ return;
85
+ }
86
+ moduleErrors.push(
87
+ ...result.errors.map((error) => ({
88
+ ...error,
89
+ path: `/modules/${index}${error.path}`
90
+ }))
91
+ );
92
+ });
93
+ const connectionErrors = validateConnections(input);
94
+ const errors = [...moduleErrors, ...connectionErrors];
95
+ if (errors.length > 0) {
96
+ return { success: false, errors };
97
+ }
98
+ return {
99
+ success: true,
100
+ data: new _ValidatedConfiguration(modules, input.connections, input.$schema)
101
+ };
102
+ }
103
+ get modules() {
104
+ return this.#modules;
105
+ }
106
+ };
107
+ function validateModule(module) {
108
+ return COMPONENT_TYPES.flatMap((componentType) => {
109
+ const rule = module[componentType];
110
+ if ("notUsed" in rule) {
111
+ return [];
112
+ }
113
+ const extractedFields = new Set(Object.keys(rule.extract ?? {}));
114
+ const missingFields = REQUIRED_FIELDS[componentType].filter(
115
+ (field) => !extractedFields.has(field)
116
+ );
117
+ return missingFields.length === 0 ? [] : [
118
+ {
119
+ path: `/${componentType}`,
120
+ message: `Missing required extraction rules: ${missingFields.join(", ")}. Add extraction rules to the 'extract' block or use 'notUsed: true' if not extracting ${componentType} components.`
121
+ }
122
+ ];
123
+ });
124
+ }
125
+ function validateConnections(input) {
126
+ if (input.connections === void 0) return [];
127
+ const customTypeFields = /* @__PURE__ */ new Map();
128
+ for (const module of input.modules) {
129
+ for (const [typeName, rule] of Object.entries(module.customTypes ?? {})) {
130
+ const fields = customTypeFields.get(typeName) ?? /* @__PURE__ */ new Set();
131
+ for (const field of Object.keys(rule.extract ?? {})) fields.add(field);
132
+ customTypeFields.set(typeName, fields);
133
+ }
134
+ }
135
+ return [
136
+ ...validateEventPublishers(input.connections, customTypeFields),
137
+ ...validateHttpLinks(input.connections, customTypeFields)
138
+ ];
139
+ }
140
+ function validateEventPublishers(connections, customTypeFields) {
141
+ return (connections.eventPublishers ?? []).flatMap((publisher, index) => {
142
+ const fields = customTypeFields.get(publisher.fromType);
143
+ if (fields === void 0) {
144
+ return [
145
+ {
146
+ path: `/connections/eventPublishers/${index}/fromType`,
147
+ message: `"${publisher.fromType}" is not defined as a customType in any module.`
148
+ }
149
+ ];
150
+ }
151
+ return fields.has(publisher.metadataKey) ? [] : [
152
+ {
153
+ path: `/connections/eventPublishers/${index}/metadataKey`,
154
+ message: `customType "${publisher.fromType}" does not extract "${publisher.metadataKey}".`
155
+ }
156
+ ];
157
+ });
158
+ }
159
+ function validateHttpLinks(connections, customTypeFields) {
160
+ return (connections.httpLinks ?? []).flatMap((httpLink, index) => {
161
+ const fields = customTypeFields.get(httpLink.fromCustomType);
162
+ if (fields === void 0) {
163
+ return [
164
+ {
165
+ path: `/connections/httpLinks/${index}/fromCustomType`,
166
+ message: `"${httpLink.fromCustomType}" is not defined as a customType in any module.`
167
+ }
168
+ ];
169
+ }
170
+ const requiredFields = [httpLink.matchDomainBy, ...httpLink.matchApiBy];
171
+ return requiredFields.flatMap(
172
+ (field) => fields.has(field) ? [] : [
173
+ {
174
+ path: `/connections/httpLinks/${index}`,
175
+ message: `customType "${httpLink.fromCustomType}" does not extract "${field}".`
176
+ }
177
+ ]
178
+ );
179
+ });
180
+ }
181
+
182
+ // src/published-language/validation.ts
183
+ import Ajv from "ajv";
184
+ import addFormats from "ajv-formats";
185
+
186
+ // extraction-config.schema.json
187
+ var extraction_config_schema_default = {
188
+ $schema: "http://json-schema.org/draft-07/schema#",
189
+ $id: "https://living-architecture.dev/schemas/extraction-config.schema.json",
190
+ title: "Extraction Config",
191
+ description: "Configuration for extracting architectural components from source code",
192
+ type: "object",
193
+ required: ["modules"],
194
+ additionalProperties: false,
195
+ properties: {
196
+ $schema: {
197
+ type: "string",
198
+ description: "JSON Schema reference"
199
+ },
200
+ modules: {
201
+ type: "array",
202
+ description: "Module definitions for component extraction",
203
+ minItems: 1,
204
+ items: {
205
+ oneOf: [{ $ref: "#/$defs/module" }, { $ref: "#/$defs/moduleRef" }]
206
+ }
207
+ },
208
+ connections: {
209
+ $ref: "#/$defs/connectionsConfig",
210
+ description: "Connection detection configuration"
211
+ }
212
+ },
213
+ $defs: {
214
+ moduleRef: {
215
+ type: "object",
216
+ description: "Reference to an external module definition file",
217
+ required: ["$ref"],
218
+ additionalProperties: false,
219
+ properties: {
220
+ $ref: {
221
+ type: "string",
222
+ description: "File path to a module definition (relative to this config file)",
223
+ minLength: 1
224
+ }
225
+ }
226
+ },
227
+ module: {
228
+ type: "object",
229
+ description: "A module defines extraction rules for a path pattern",
230
+ required: ["name", "domain", "path", "glob"],
231
+ additionalProperties: false,
232
+ properties: {
233
+ name: {
234
+ type: "string",
235
+ description: "Extraction config module identifier",
236
+ minLength: 1
237
+ },
238
+ domain: {
239
+ type: "string",
240
+ description: "Riviere graph domain this module belongs to",
241
+ minLength: 1
242
+ },
243
+ path: {
244
+ type: "string",
245
+ description: "Module root directory relative to config file",
246
+ minLength: 1
247
+ },
248
+ modules: {
249
+ type: "string",
250
+ description: "Path pattern with {module} placeholder for resolving module names from file paths",
251
+ pattern: "\\{module\\}"
252
+ },
253
+ glob: {
254
+ type: "string",
255
+ description: "Glob pattern for source files within the module directory",
256
+ minLength: 1
257
+ },
258
+ extends: {
259
+ type: "string",
260
+ description: "Package name or file path to inherit component rules from",
261
+ minLength: 1
262
+ },
263
+ api: {
264
+ $ref: "#/$defs/componentRule",
265
+ description: "Detection rule for API components"
266
+ },
267
+ useCase: {
268
+ $ref: "#/$defs/componentRule",
269
+ description: "Detection rule for UseCase components"
270
+ },
271
+ domainOp: {
272
+ $ref: "#/$defs/componentRule",
273
+ description: "Detection rule for DomainOp components"
274
+ },
275
+ event: {
276
+ $ref: "#/$defs/componentRule",
277
+ description: "Detection rule for Event components"
278
+ },
279
+ eventHandler: {
280
+ $ref: "#/$defs/componentRule",
281
+ description: "Detection rule for EventHandler components"
282
+ },
283
+ ui: {
284
+ $ref: "#/$defs/componentRule",
285
+ description: "Detection rule for UI components"
286
+ },
287
+ customTypes: {
288
+ type: "object",
289
+ description: "User-defined component types with their detection rules",
290
+ additionalProperties: {
291
+ $ref: "#/$defs/detectionRule"
292
+ }
293
+ }
294
+ },
295
+ if: {
296
+ not: {
297
+ required: ["extends"]
298
+ }
299
+ },
300
+ then: {
301
+ required: ["api", "useCase", "domainOp", "event", "eventHandler", "ui"]
302
+ }
303
+ },
304
+ componentRule: {
305
+ oneOf: [
306
+ {
307
+ $ref: "#/$defs/notUsed"
308
+ },
309
+ {
310
+ $ref: "#/$defs/detectionRule"
311
+ }
312
+ ]
313
+ },
314
+ notUsed: {
315
+ type: "object",
316
+ description: "Marks this component type as not used in the module",
317
+ required: ["notUsed"],
318
+ additionalProperties: false,
319
+ properties: {
320
+ notUsed: {
321
+ type: "boolean",
322
+ const: true
323
+ }
324
+ }
325
+ },
326
+ detectionRule: {
327
+ type: "object",
328
+ description: "Rule for detecting components of this type",
329
+ required: ["find", "where"],
330
+ additionalProperties: false,
331
+ properties: {
332
+ find: {
333
+ $ref: "#/$defs/findTarget"
334
+ },
335
+ where: {
336
+ $ref: "#/$defs/predicate"
337
+ },
338
+ extract: {
339
+ $ref: "#/$defs/extractBlock",
340
+ description: "Extraction rules for metadata fields"
341
+ }
342
+ }
343
+ },
344
+ extractBlock: {
345
+ type: "object",
346
+ description: "Extraction rules mapping field names to extraction rules",
347
+ additionalProperties: {
348
+ $ref: "#/$defs/extractionRule"
349
+ }
350
+ },
351
+ extractionRule: {
352
+ oneOf: [
353
+ { $ref: "#/$defs/literalExtractionRule" },
354
+ { $ref: "#/$defs/fromClassNameExtractionRule" },
355
+ { $ref: "#/$defs/fromMethodNameExtractionRule" },
356
+ { $ref: "#/$defs/fromFilePathExtractionRule" },
357
+ { $ref: "#/$defs/fromPropertyExtractionRule" },
358
+ { $ref: "#/$defs/fromDecoratorArgExtractionRule" },
359
+ { $ref: "#/$defs/fromClassDecoratorArgExtractionRule" },
360
+ { $ref: "#/$defs/fromDecoratorNameExtractionRule" },
361
+ { $ref: "#/$defs/fromGenericArgExtractionRule" },
362
+ { $ref: "#/$defs/fromMethodSignatureExtractionRule" },
363
+ { $ref: "#/$defs/fromConstructorParamsExtractionRule" },
364
+ { $ref: "#/$defs/fromParameterTypeExtractionRule" }
365
+ ]
366
+ },
367
+ fromMethodNameExtractionRule: {
368
+ type: "object",
369
+ description: "Extracts value from the method name",
370
+ required: ["fromMethodName"],
371
+ additionalProperties: false,
372
+ properties: {
373
+ fromMethodName: {
374
+ oneOf: [
375
+ { type: "boolean", const: true },
376
+ {
377
+ type: "object",
378
+ additionalProperties: false,
379
+ properties: {
380
+ transform: { $ref: "#/$defs/transform" }
381
+ }
382
+ }
383
+ ]
384
+ }
385
+ }
386
+ },
387
+ fromFilePathExtractionRule: {
388
+ type: "object",
389
+ description: "Extracts value from the file path using regex capture",
390
+ required: ["fromFilePath"],
391
+ additionalProperties: false,
392
+ properties: {
393
+ fromFilePath: {
394
+ type: "object",
395
+ required: ["pattern", "capture"],
396
+ additionalProperties: false,
397
+ properties: {
398
+ pattern: { type: "string", minLength: 1 },
399
+ capture: { type: "integer", minimum: 0 },
400
+ transform: { $ref: "#/$defs/transform" }
401
+ }
402
+ }
403
+ }
404
+ },
405
+ fromPropertyExtractionRule: {
406
+ type: "object",
407
+ description: "Extracts value from a class property",
408
+ required: ["fromProperty"],
409
+ additionalProperties: false,
410
+ properties: {
411
+ fromProperty: {
412
+ type: "object",
413
+ required: ["name", "kind"],
414
+ additionalProperties: false,
415
+ properties: {
416
+ name: { type: "string", minLength: 1 },
417
+ kind: { type: "string", enum: ["static", "instance"] },
418
+ transform: { $ref: "#/$defs/transform" }
419
+ }
420
+ }
421
+ }
422
+ },
423
+ fromDecoratorArgExtractionRule: {
424
+ type: "object",
425
+ description: "Extracts value from decorator argument",
426
+ required: ["fromDecoratorArg"],
427
+ additionalProperties: false,
428
+ properties: {
429
+ fromDecoratorArg: {
430
+ type: "object",
431
+ additionalProperties: false,
432
+ minProperties: 1,
433
+ properties: {
434
+ decorator: { type: "string", minLength: 1 },
435
+ position: { type: "integer", minimum: 0 },
436
+ name: { type: "string", minLength: 1 },
437
+ transform: { $ref: "#/$defs/transform" }
438
+ }
439
+ }
440
+ }
441
+ },
442
+ fromClassDecoratorArgExtractionRule: {
443
+ type: "object",
444
+ description: "Extracts value from decorator argument on the containing class",
445
+ required: ["fromClassDecoratorArg"],
446
+ additionalProperties: false,
447
+ properties: {
448
+ fromClassDecoratorArg: {
449
+ type: "object",
450
+ required: ["decorator"],
451
+ additionalProperties: false,
452
+ properties: {
453
+ decorator: { type: "string", minLength: 1 },
454
+ position: { type: "integer", minimum: 0 },
455
+ name: { type: "string", minLength: 1 },
456
+ transform: { $ref: "#/$defs/transform" }
457
+ },
458
+ oneOf: [
459
+ {
460
+ required: ["position"],
461
+ not: { required: ["name"] }
462
+ },
463
+ {
464
+ required: ["name"],
465
+ not: { required: ["position"] }
466
+ }
467
+ ]
468
+ }
469
+ }
470
+ },
471
+ fromDecoratorNameExtractionRule: {
472
+ type: "object",
473
+ description: "Extracts value from the decorator name itself",
474
+ required: ["fromDecoratorName"],
475
+ additionalProperties: false,
476
+ properties: {
477
+ fromDecoratorName: {
478
+ oneOf: [
479
+ { type: "boolean", const: true },
480
+ {
481
+ type: "object",
482
+ additionalProperties: false,
483
+ properties: {
484
+ mapping: {
485
+ type: "object",
486
+ additionalProperties: { type: "string" },
487
+ minProperties: 1
488
+ },
489
+ transform: { $ref: "#/$defs/transform" }
490
+ }
491
+ }
492
+ ]
493
+ }
494
+ }
495
+ },
496
+ fromGenericArgExtractionRule: {
497
+ type: "object",
498
+ description: "Extracts value from generic type argument",
499
+ required: ["fromGenericArg"],
500
+ additionalProperties: false,
501
+ properties: {
502
+ fromGenericArg: {
503
+ type: "object",
504
+ required: ["interface", "position"],
505
+ additionalProperties: false,
506
+ properties: {
507
+ interface: { type: "string", minLength: 1 },
508
+ position: { type: "integer", minimum: 0 },
509
+ transform: { $ref: "#/$defs/transform" }
510
+ }
511
+ }
512
+ }
513
+ },
514
+ fromMethodSignatureExtractionRule: {
515
+ type: "object",
516
+ description: "Extracts method parameters and return type",
517
+ required: ["fromMethodSignature"],
518
+ additionalProperties: false,
519
+ properties: {
520
+ fromMethodSignature: {
521
+ type: "boolean",
522
+ const: true
523
+ }
524
+ }
525
+ },
526
+ fromConstructorParamsExtractionRule: {
527
+ type: "object",
528
+ description: "Extracts constructor parameter names and types",
529
+ required: ["fromConstructorParams"],
530
+ additionalProperties: false,
531
+ properties: {
532
+ fromConstructorParams: {
533
+ type: "boolean",
534
+ const: true
535
+ }
536
+ }
537
+ },
538
+ fromParameterTypeExtractionRule: {
539
+ type: "object",
540
+ description: "Extracts type name of parameter at position",
541
+ required: ["fromParameterType"],
542
+ additionalProperties: false,
543
+ properties: {
544
+ fromParameterType: {
545
+ type: "object",
546
+ required: ["position"],
547
+ additionalProperties: false,
548
+ properties: {
549
+ position: { type: "integer", minimum: 0 },
550
+ transform: { $ref: "#/$defs/transform" }
551
+ }
552
+ }
553
+ }
554
+ },
555
+ fromClassNameExtractionRule: {
556
+ type: "object",
557
+ description: "Extracts value from the class name",
558
+ required: ["fromClassName"],
559
+ additionalProperties: false,
560
+ properties: {
561
+ fromClassName: {
562
+ oneOf: [
563
+ { type: "boolean", const: true },
564
+ {
565
+ type: "object",
566
+ additionalProperties: false,
567
+ properties: {
568
+ transform: { $ref: "#/$defs/transform" }
569
+ }
570
+ }
571
+ ],
572
+ description: "Extract from class name, optionally with transform"
573
+ }
574
+ }
575
+ },
576
+ transform: {
577
+ type: "object",
578
+ description: "Transform operations to apply to extracted value",
579
+ additionalProperties: false,
580
+ minProperties: 1,
581
+ properties: {
582
+ stripSuffix: { type: "string", minLength: 1 },
583
+ stripPrefix: { type: "string", minLength: 1 },
584
+ toLowerCase: { type: "boolean", const: true },
585
+ toUpperCase: { type: "boolean", const: true },
586
+ kebabToPascal: { type: "boolean", const: true },
587
+ pascalToKebab: { type: "boolean", const: true }
588
+ }
589
+ },
590
+ literalExtractionRule: {
591
+ type: "object",
592
+ description: "Extracts a hardcoded literal value",
593
+ required: ["literal"],
594
+ additionalProperties: false,
595
+ properties: {
596
+ literal: {
597
+ oneOf: [
598
+ { type: "string", minLength: 1 },
599
+ { type: "boolean" },
600
+ { type: "number" }
601
+ ],
602
+ description: "Literal value to use for this field"
603
+ }
604
+ }
605
+ },
606
+ findTarget: {
607
+ type: "string",
608
+ description: "The code construct to search for",
609
+ enum: ["classes", "methods", "functions"]
610
+ },
611
+ predicate: {
612
+ oneOf: [
613
+ {
614
+ $ref: "#/$defs/hasDecoratorPredicate"
615
+ },
616
+ {
617
+ $ref: "#/$defs/hasJSDocPredicate"
618
+ },
619
+ {
620
+ $ref: "#/$defs/extendsClassPredicate"
621
+ },
622
+ {
623
+ $ref: "#/$defs/implementsInterfacePredicate"
624
+ },
625
+ {
626
+ $ref: "#/$defs/nameEndsWithPredicate"
627
+ },
628
+ {
629
+ $ref: "#/$defs/nameMatchesPredicate"
630
+ },
631
+ {
632
+ $ref: "#/$defs/inClassWithPredicate"
633
+ },
634
+ {
635
+ $ref: "#/$defs/andPredicate"
636
+ },
637
+ {
638
+ $ref: "#/$defs/orPredicate"
639
+ }
640
+ ]
641
+ },
642
+ hasDecoratorPredicate: {
643
+ type: "object",
644
+ description: "Matches if the target has a specified decorator",
645
+ required: ["hasDecorator"],
646
+ additionalProperties: false,
647
+ properties: {
648
+ hasDecorator: {
649
+ type: "object",
650
+ required: ["name"],
651
+ additionalProperties: false,
652
+ properties: {
653
+ name: {
654
+ oneOf: [
655
+ {
656
+ type: "string",
657
+ minLength: 1
658
+ },
659
+ {
660
+ type: "array",
661
+ items: {
662
+ type: "string",
663
+ minLength: 1
664
+ },
665
+ minItems: 1
666
+ }
667
+ ],
668
+ description: "Decorator name(s) to match"
669
+ },
670
+ from: {
671
+ type: "string",
672
+ minLength: 1,
673
+ description: "Package the decorator is imported from"
674
+ }
675
+ }
676
+ }
677
+ }
678
+ },
679
+ hasJSDocPredicate: {
680
+ type: "object",
681
+ description: "Matches if the target has a specified JSDoc tag",
682
+ required: ["hasJSDoc"],
683
+ additionalProperties: false,
684
+ properties: {
685
+ hasJSDoc: {
686
+ type: "object",
687
+ required: ["tag"],
688
+ additionalProperties: false,
689
+ properties: {
690
+ tag: {
691
+ type: "string",
692
+ minLength: 1,
693
+ description: "JSDoc tag to match (without @)"
694
+ }
695
+ }
696
+ }
697
+ }
698
+ },
699
+ extendsClassPredicate: {
700
+ type: "object",
701
+ description: "Matches if the class extends a specified base class",
702
+ required: ["extendsClass"],
703
+ additionalProperties: false,
704
+ properties: {
705
+ extendsClass: {
706
+ type: "object",
707
+ required: ["name"],
708
+ additionalProperties: false,
709
+ properties: {
710
+ name: {
711
+ type: "string",
712
+ minLength: 1,
713
+ description: "Base class name to match"
714
+ }
715
+ }
716
+ }
717
+ }
718
+ },
719
+ implementsInterfacePredicate: {
720
+ type: "object",
721
+ description: "Matches if the class implements a specified interface",
722
+ required: ["implementsInterface"],
723
+ additionalProperties: false,
724
+ properties: {
725
+ implementsInterface: {
726
+ type: "object",
727
+ required: ["name"],
728
+ additionalProperties: false,
729
+ properties: {
730
+ name: {
731
+ type: "string",
732
+ minLength: 1,
733
+ description: "Interface name to match"
734
+ }
735
+ }
736
+ }
737
+ }
738
+ },
739
+ nameEndsWithPredicate: {
740
+ type: "object",
741
+ description: "Matches if the target name ends with a specified suffix",
742
+ required: ["nameEndsWith"],
743
+ additionalProperties: false,
744
+ properties: {
745
+ nameEndsWith: {
746
+ type: "object",
747
+ required: ["suffix"],
748
+ additionalProperties: false,
749
+ properties: {
750
+ suffix: {
751
+ type: "string",
752
+ minLength: 1,
753
+ description: "Suffix to match"
754
+ }
755
+ }
756
+ }
757
+ }
758
+ },
759
+ nameMatchesPredicate: {
760
+ type: "object",
761
+ description: "Matches if the target name matches a regex pattern",
762
+ required: ["nameMatches"],
763
+ additionalProperties: false,
764
+ properties: {
765
+ nameMatches: {
766
+ type: "object",
767
+ required: ["pattern"],
768
+ additionalProperties: false,
769
+ properties: {
770
+ pattern: {
771
+ type: "string",
772
+ minLength: 1,
773
+ description: "Regex pattern to match against the name"
774
+ }
775
+ }
776
+ }
777
+ }
778
+ },
779
+ inClassWithPredicate: {
780
+ type: "object",
781
+ description: "Matches if the method is in a class matching the predicate",
782
+ required: ["inClassWith"],
783
+ additionalProperties: false,
784
+ properties: {
785
+ inClassWith: {
786
+ $ref: "#/$defs/predicate",
787
+ description: "Predicate the containing class must satisfy"
788
+ }
789
+ }
790
+ },
791
+ andPredicate: {
792
+ type: "object",
793
+ description: "Matches if all predicates match",
794
+ required: ["and"],
795
+ additionalProperties: false,
796
+ properties: {
797
+ and: {
798
+ type: "array",
799
+ items: {
800
+ $ref: "#/$defs/predicate"
801
+ },
802
+ minItems: 2,
803
+ description: "All predicates must match"
804
+ }
805
+ }
806
+ },
807
+ orPredicate: {
808
+ type: "object",
809
+ description: "Matches if any predicate matches",
810
+ required: ["or"],
811
+ additionalProperties: false,
812
+ properties: {
813
+ or: {
814
+ type: "array",
815
+ items: {
816
+ $ref: "#/$defs/predicate"
817
+ },
818
+ minItems: 2,
819
+ description: "Any predicate must match"
820
+ }
821
+ }
822
+ },
823
+ connectionsConfig: {
824
+ type: "object",
825
+ description: "Connection detection configuration",
826
+ additionalProperties: false,
827
+ properties: {
828
+ eventPublishers: {
829
+ type: "array",
830
+ description: "Declares which custom component types publish events and how to detect the connections",
831
+ minItems: 1,
832
+ items: {
833
+ $ref: "#/$defs/eventPublisherConfig"
834
+ }
835
+ },
836
+ httpLinks: {
837
+ type: "array",
838
+ description: "Declares how to resolve HTTP client calls into cross-domain Links",
839
+ minItems: 1,
840
+ items: {
841
+ $ref: "#/$defs/httpLinkConfig"
842
+ }
843
+ }
844
+ }
845
+ },
846
+ httpLinkConfig: {
847
+ type: "object",
848
+ description: "Declares how to resolve HTTP client calls into cross-domain Links",
849
+ required: ["fromCustomType", "matchDomainBy", "matchApiBy"],
850
+ additionalProperties: false,
851
+ properties: {
852
+ fromCustomType: {
853
+ type: "string",
854
+ description: "Custom component type name for HTTP clients \u2014 must be defined in customTypes in at least one module",
855
+ minLength: 1
856
+ },
857
+ matchDomainBy: {
858
+ type: "string",
859
+ description: "Metadata key whose value identifies the target domain",
860
+ minLength: 1
861
+ },
862
+ matchApiBy: {
863
+ type: "array",
864
+ description: "Metadata keys used to match the target API component",
865
+ items: {
866
+ type: "string",
867
+ minLength: 1
868
+ },
869
+ minItems: 1
870
+ }
871
+ }
872
+ },
873
+ eventPublisherConfig: {
874
+ type: "object",
875
+ description: "Declares a custom component type as an event publisher",
876
+ required: ["fromType", "metadataKey"],
877
+ additionalProperties: false,
878
+ properties: {
879
+ fromType: {
880
+ type: "string",
881
+ description: "The custom component type name \u2014 must be defined in customTypes in at least one module",
882
+ minLength: 1
883
+ },
884
+ metadataKey: {
885
+ type: "string",
886
+ description: "The metadata key on this component type that holds the published event type name",
887
+ minLength: 1
888
+ }
889
+ }
890
+ }
891
+ }
892
+ };
893
+
894
+ // src/published-language/validation.ts
895
+ var REQUIRED_FIELDS2 = {
896
+ api: ["apiType"],
897
+ event: ["eventName"],
898
+ eventHandler: ["subscribedEvents"],
899
+ domainOp: ["operationName"],
900
+ ui: ["route"],
901
+ useCase: []
902
+ };
903
+ var COMPONENT_TYPES2 = [
904
+ "api",
905
+ "useCase",
906
+ "domainOp",
907
+ "event",
908
+ "eventHandler",
909
+ "ui"
910
+ ];
911
+ var ajv = new Ajv({ allErrors: true });
912
+ addFormats(ajv);
913
+ var validate = ajv.compile(extraction_config_schema_default);
914
+ function isValidExtractionConfig(data) {
915
+ return validate(data) === true;
916
+ }
917
+ function mapAjvErrors(errors) {
918
+ if (!errors) {
919
+ return [];
920
+ }
921
+ return errors.map((e) => ({
922
+ path: e.instancePath || "/",
923
+ message: e.message ?? "unknown error"
924
+ }));
925
+ }
926
+ function isNotUsed(rule) {
927
+ return rule !== void 0 && "notUsed" in rule && rule.notUsed === true;
928
+ }
929
+ function hasDetectionRule(rule) {
930
+ return rule !== void 0 && "find" in rule && "where" in rule;
931
+ }
932
+ function getExtractedFields(rule) {
933
+ if (!rule || !("extract" in rule) || !rule.extract) {
934
+ return [];
935
+ }
936
+ return Object.keys(rule.extract);
937
+ }
938
+ function validateModuleExtractionRules(module, moduleIndex) {
939
+ const errors = [];
940
+ for (const componentType of COMPONENT_TYPES2) {
941
+ const rule = module[componentType];
942
+ if (isNotUsed(rule)) {
943
+ continue;
944
+ }
945
+ if (!hasDetectionRule(rule)) {
946
+ continue;
947
+ }
948
+ const requiredFields = REQUIRED_FIELDS2[componentType];
949
+ if (requiredFields.length === 0) {
950
+ continue;
951
+ }
952
+ const extractedFields = getExtractedFields(rule);
953
+ const missingFields = requiredFields.filter((field) => !extractedFields.includes(field));
954
+ if (missingFields.length > 0) {
955
+ errors.push({
956
+ path: `/modules/${moduleIndex}/${componentType}`,
957
+ message: `Missing required extraction rules: ${missingFields.join(", ")}. Add extraction rules to the 'extract' block or use 'notUsed: true' if not extracting ${componentType} components.`
958
+ });
959
+ }
960
+ }
961
+ return errors;
962
+ }
963
+ function validateAllExtractionRules(config) {
964
+ return config.modules.flatMap((module, index) => {
965
+ if ("$ref" in module) {
966
+ return [];
967
+ }
968
+ return validateModuleExtractionRules(module, index);
969
+ });
970
+ }
971
+ function collectCustomTypeExtractedFields(config) {
972
+ const fieldsByType = /* @__PURE__ */ new Map();
973
+ for (const module of config.modules) {
974
+ if ("$ref" in module || module.customTypes === void 0) {
975
+ continue;
976
+ }
977
+ for (const [typeName, rule] of Object.entries(module.customTypes)) {
978
+ const existing = fieldsByType.get(typeName) ?? /* @__PURE__ */ new Set();
979
+ for (const key of Object.keys(rule.extract ?? {})) {
980
+ existing.add(key);
981
+ }
982
+ fieldsByType.set(typeName, existing);
983
+ }
984
+ }
985
+ return fieldsByType;
986
+ }
987
+ function validateEventPublishers2(connections, customTypeFields) {
988
+ if (connections.eventPublishers === void 0) {
989
+ return [];
990
+ }
991
+ return connections.eventPublishers.flatMap((publisher, index) => {
992
+ const extractedFields = customTypeFields.get(publisher.fromType);
993
+ if (extractedFields === void 0) {
994
+ return [
995
+ {
996
+ path: `/connections/eventPublishers/${index}/fromType`,
997
+ message: `"${publisher.fromType}" is not defined as a customType in any module. Add a customType named "${publisher.fromType}" to at least one module.`
998
+ }
999
+ ];
1000
+ }
1001
+ if (!extractedFields.has(publisher.metadataKey)) {
1002
+ return [
1003
+ {
1004
+ path: `/connections/eventPublishers/${index}/fromType`,
1005
+ message: `customType "${publisher.fromType}" does not extract "${publisher.metadataKey}". Add extract["${publisher.metadataKey}"] to that custom type.`
1006
+ }
1007
+ ];
1008
+ }
1009
+ return [];
1010
+ });
1011
+ }
1012
+ function validateHttpLinks2(connections, customTypeFields) {
1013
+ if (connections.httpLinks === void 0) {
1014
+ return [];
1015
+ }
1016
+ return connections.httpLinks.flatMap((httpLink, index) => {
1017
+ const extractedFields = customTypeFields.get(httpLink.fromCustomType);
1018
+ if (extractedFields === void 0) {
1019
+ return [
1020
+ {
1021
+ path: `/connections/httpLinks/${index}/fromCustomType`,
1022
+ message: `"${httpLink.fromCustomType}" is not defined as a customType in any module. Add a customType named "${httpLink.fromCustomType}" to at least one module.`
1023
+ }
1024
+ ];
1025
+ }
1026
+ const errors = [];
1027
+ if (!extractedFields.has(httpLink.matchDomainBy)) {
1028
+ errors.push({
1029
+ path: `/connections/httpLinks/${index}/matchDomainBy`,
1030
+ message: `customType "${httpLink.fromCustomType}" does not extract "${httpLink.matchDomainBy}". Add extract["${httpLink.matchDomainBy}"] to that custom type.`
1031
+ });
1032
+ }
1033
+ for (const field of httpLink.matchApiBy) {
1034
+ if (!extractedFields.has(field)) {
1035
+ errors.push({
1036
+ path: `/connections/httpLinks/${index}/matchApiBy`,
1037
+ message: `customType "${httpLink.fromCustomType}" does not extract "${field}". Add extract["${field}"] to that custom type.`
1038
+ });
1039
+ }
1040
+ }
1041
+ return errors;
1042
+ });
1043
+ }
1044
+ function validateConnectionsConfig(config) {
1045
+ if (config.connections === void 0) {
1046
+ return [];
1047
+ }
1048
+ const customTypeFields = collectCustomTypeExtractedFields(config);
1049
+ return [
1050
+ ...validateEventPublishers2(config.connections, customTypeFields),
1051
+ ...validateHttpLinks2(config.connections, customTypeFields)
1052
+ ];
1053
+ }
1054
+ function validateExtractionConfigSchema(data) {
1055
+ const schemaValid = validate(data) === true;
1056
+ if (!schemaValid) {
1057
+ return {
1058
+ valid: false,
1059
+ errors: mapAjvErrors(validate.errors)
1060
+ };
1061
+ }
1062
+ return {
1063
+ valid: true,
1064
+ errors: []
1065
+ };
1066
+ }
1067
+ function validateExtractionConfig(data) {
1068
+ if (!isValidExtractionConfig(data)) {
1069
+ return validateExtractionConfigSchema(data);
1070
+ }
1071
+ const semanticErrors = [...validateAllExtractionRules(data), ...validateConnectionsConfig(data)];
1072
+ if (semanticErrors.length > 0) {
1073
+ return {
1074
+ valid: false,
1075
+ errors: semanticErrors
1076
+ };
1077
+ }
1078
+ return {
1079
+ valid: true,
1080
+ errors: []
1081
+ };
1082
+ }
1083
+ function parseExtractionConfigSchema(data) {
1084
+ const result = validateExtractionConfigSchema(data);
1085
+ if (result.valid && isValidExtractionConfig(data)) {
1086
+ return { success: true, configuration: data };
1087
+ }
1088
+ return { success: false, errors: result.errors };
1089
+ }
1090
+ function parseExtractionConfig(data) {
1091
+ const result = validateExtractionConfig(data);
1092
+ if (result.valid && isValidExtractionConfig(data)) {
1093
+ return { success: true, configuration: data };
1094
+ }
1095
+ return { success: false, errors: result.errors };
1096
+ }
1097
+ export {
1098
+ ValidatedConfiguration,
1099
+ ValidatedModule,
1100
+ parseExtractionConfig,
1101
+ parseExtractionConfigSchema
1102
+ };