@reactive-contracts/compiler 0.1.2-beta → 0.2.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.cjs +489 -20
- package/dist/index.cli.js +112 -45
- package/dist/index.d.cts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +458 -20
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,108 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var jiti = require('jiti');
|
|
4
|
+
var path2 = require('path');
|
|
5
|
+
var fs = require('fs');
|
|
3
6
|
var promises = require('fs/promises');
|
|
4
|
-
var
|
|
7
|
+
var glob = require('glob');
|
|
8
|
+
|
|
9
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
10
|
+
function _interopNamespace(e) {
|
|
11
|
+
if (e && e.__esModule) return e;
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n.default = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
|
|
5
29
|
|
|
6
30
|
// src/config/index.ts
|
|
31
|
+
var defaultConfig = {
|
|
32
|
+
contracts: "./contracts/**/*.contract.ts",
|
|
33
|
+
output: {
|
|
34
|
+
frontend: "./generated/frontend",
|
|
35
|
+
backend: "./generated/backend",
|
|
36
|
+
runtime: "./generated/runtime"
|
|
37
|
+
},
|
|
38
|
+
validation: {
|
|
39
|
+
strictLatency: false,
|
|
40
|
+
requireIntent: true,
|
|
41
|
+
maxComplexity: 10
|
|
42
|
+
},
|
|
43
|
+
optimization: {
|
|
44
|
+
bundleSplitting: false,
|
|
45
|
+
treeShaking: false,
|
|
46
|
+
precompute: []
|
|
47
|
+
}
|
|
48
|
+
};
|
|
7
49
|
function defineConfig(config) {
|
|
8
50
|
return config;
|
|
9
51
|
}
|
|
52
|
+
function resolveConfigPath(configPath, cwd = process.cwd()) {
|
|
53
|
+
if (configPath) {
|
|
54
|
+
const absolutePath = path2__namespace.isAbsolute(configPath) ? configPath : path2__namespace.join(cwd, configPath);
|
|
55
|
+
return fs.existsSync(absolutePath) ? absolutePath : null;
|
|
56
|
+
}
|
|
57
|
+
const configNames = [
|
|
58
|
+
"rcontracts.config.ts",
|
|
59
|
+
"rcontracts.config.js",
|
|
60
|
+
"rcontracts.config.mjs",
|
|
61
|
+
"reactive-contracts.config.ts",
|
|
62
|
+
"reactive-contracts.config.js"
|
|
63
|
+
];
|
|
64
|
+
for (const name of configNames) {
|
|
65
|
+
const fullPath = path2__namespace.join(cwd, name);
|
|
66
|
+
if (fs.existsSync(fullPath)) {
|
|
67
|
+
return fullPath;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
async function loadConfig(configPath, cwd = process.cwd()) {
|
|
73
|
+
const resolvedPath = resolveConfigPath(configPath, cwd);
|
|
74
|
+
if (!resolvedPath) {
|
|
75
|
+
return { ...defaultConfig };
|
|
76
|
+
}
|
|
77
|
+
const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)), {
|
|
78
|
+
interopDefault: true,
|
|
79
|
+
moduleCache: false
|
|
80
|
+
// Disable cache to get fresh config on reload
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
const module = await jiti$1.import(resolvedPath);
|
|
84
|
+
const config = module.default || module;
|
|
85
|
+
return {
|
|
86
|
+
...defaultConfig,
|
|
87
|
+
...config,
|
|
88
|
+
output: {
|
|
89
|
+
...defaultConfig.output,
|
|
90
|
+
...config.output || {}
|
|
91
|
+
},
|
|
92
|
+
validation: {
|
|
93
|
+
...defaultConfig.validation,
|
|
94
|
+
...config.validation || {}
|
|
95
|
+
},
|
|
96
|
+
optimization: {
|
|
97
|
+
...defaultConfig.optimization,
|
|
98
|
+
...config.optimization || {}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
103
|
+
throw new Error(`Failed to load config from ${resolvedPath}: ${errorMessage}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
10
106
|
|
|
11
107
|
// src/validator/index.ts
|
|
12
108
|
function validateContract(contract) {
|
|
@@ -53,17 +149,17 @@ function validateContract(contract) {
|
|
|
53
149
|
warnings
|
|
54
150
|
};
|
|
55
151
|
}
|
|
56
|
-
function validateShape(shape,
|
|
152
|
+
function validateShape(shape, path3, errors, warnings) {
|
|
57
153
|
if (!shape || typeof shape !== "object") {
|
|
58
|
-
errors.push(`Invalid shape at ${
|
|
154
|
+
errors.push(`Invalid shape at ${path3 || "root"}: must be an object`);
|
|
59
155
|
return;
|
|
60
156
|
}
|
|
61
157
|
const keys = Object.keys(shape);
|
|
62
158
|
if (keys.length === 0) {
|
|
63
|
-
warnings.push(`Shape at ${
|
|
159
|
+
warnings.push(`Shape at ${path3 || "root"} is empty`);
|
|
64
160
|
}
|
|
65
161
|
for (const key of keys) {
|
|
66
|
-
const fieldPath =
|
|
162
|
+
const fieldPath = path3 ? `${path3}.${key}` : key;
|
|
67
163
|
const value = shape[key];
|
|
68
164
|
if (!value) {
|
|
69
165
|
errors.push(`Field "${fieldPath}" has undefined value`);
|
|
@@ -77,46 +173,46 @@ function validateShape(shape, path, errors, warnings) {
|
|
|
77
173
|
validateTypeDefinition(value, fieldPath, errors, warnings);
|
|
78
174
|
}
|
|
79
175
|
}
|
|
80
|
-
function validateTypeDefinition(type,
|
|
176
|
+
function validateTypeDefinition(type, path3, errors, warnings) {
|
|
81
177
|
if (typeof type === "string") {
|
|
82
178
|
const validPrimitives = ["string", "number", "boolean", "Date", "null", "undefined"];
|
|
83
179
|
const isURL = type === "URL" || type.startsWith("URL<");
|
|
84
180
|
if (!validPrimitives.includes(type) && !isURL) {
|
|
85
|
-
errors.push(`Invalid type at ${
|
|
181
|
+
errors.push(`Invalid type at ${path3}: "${type}" is not a valid primitive or URL type`);
|
|
86
182
|
}
|
|
87
183
|
if (isURL && type !== "URL") {
|
|
88
184
|
const urlMatch = type.match(/^URL<(.+)>$/);
|
|
89
185
|
if (!urlMatch) {
|
|
90
|
-
errors.push(`Invalid URL type at ${
|
|
186
|
+
errors.push(`Invalid URL type at ${path3}: must be "URL" or "URL<options>"`);
|
|
91
187
|
}
|
|
92
188
|
}
|
|
93
189
|
} else if (typeof type === "object" && type !== null) {
|
|
94
190
|
if ("_brand" in type && type._brand === "DerivedField") {
|
|
95
191
|
validateDerivedField(
|
|
96
192
|
type,
|
|
97
|
-
|
|
193
|
+
path3,
|
|
98
194
|
errors);
|
|
99
195
|
} else {
|
|
100
|
-
validateShape(type,
|
|
196
|
+
validateShape(type, path3, errors, warnings);
|
|
101
197
|
}
|
|
102
198
|
} else {
|
|
103
|
-
errors.push(`Invalid type at ${
|
|
199
|
+
errors.push(`Invalid type at ${path3}: must be a string, object, or DerivedField`);
|
|
104
200
|
}
|
|
105
201
|
}
|
|
106
|
-
function validateDerivedField(field,
|
|
202
|
+
function validateDerivedField(field, path3, errors, _warnings) {
|
|
107
203
|
if (typeof field.derive !== "function") {
|
|
108
|
-
errors.push(`Derived field at ${
|
|
204
|
+
errors.push(`Derived field at ${path3} must have a derive function`);
|
|
109
205
|
}
|
|
110
206
|
if (field.dependencies && !Array.isArray(field.dependencies)) {
|
|
111
|
-
errors.push(`Derived field dependencies at ${
|
|
207
|
+
errors.push(`Derived field dependencies at ${path3} must be an array`);
|
|
112
208
|
}
|
|
113
209
|
if (field.preferredLayer && !["client", "edge", "origin"].includes(field.preferredLayer)) {
|
|
114
|
-
errors.push(`Derived field preferredLayer at ${
|
|
210
|
+
errors.push(`Derived field preferredLayer at ${path3} must be 'client', 'edge', or 'origin'`);
|
|
115
211
|
}
|
|
116
212
|
if (field.dependencies && Array.isArray(field.dependencies)) {
|
|
117
213
|
for (const dep of field.dependencies) {
|
|
118
214
|
if (typeof dep !== "string") {
|
|
119
|
-
errors.push(`Derived field dependency at ${
|
|
215
|
+
errors.push(`Derived field dependency at ${path3} must be a string`);
|
|
120
216
|
}
|
|
121
217
|
}
|
|
122
218
|
}
|
|
@@ -245,10 +341,10 @@ function validateVersioning(versioning, errors, warnings) {
|
|
|
245
341
|
}
|
|
246
342
|
function collectFieldPaths(shape, prefix, result) {
|
|
247
343
|
for (const [key, value] of Object.entries(shape)) {
|
|
248
|
-
const
|
|
249
|
-
result.add(
|
|
344
|
+
const path3 = prefix ? `${prefix}.${key}` : key;
|
|
345
|
+
result.add(path3);
|
|
250
346
|
if (typeof value === "object" && value !== null && !("_brand" in value) && typeof value !== "string") {
|
|
251
|
-
collectFieldPaths(value,
|
|
347
|
+
collectFieldPaths(value, path3, result);
|
|
252
348
|
}
|
|
253
349
|
}
|
|
254
350
|
}
|
|
@@ -413,7 +509,217 @@ export interface ${typeName}Result {
|
|
|
413
509
|
*/
|
|
414
510
|
export const ${typeName}Intent = '${definition.intent}' as const;
|
|
415
511
|
`;
|
|
416
|
-
await promises.mkdir(
|
|
512
|
+
await promises.mkdir(path2.dirname(outputPath), { recursive: true });
|
|
513
|
+
await promises.writeFile(outputPath, content, "utf-8");
|
|
514
|
+
}
|
|
515
|
+
async function generateBackendResolver(contract, outputPath) {
|
|
516
|
+
const { definition } = contract;
|
|
517
|
+
const typeName = definition.name;
|
|
518
|
+
const shapeType = generateTypeDefinitions(definition.shape, `${typeName}ResolverShape`);
|
|
519
|
+
const content = `/**
|
|
520
|
+
* Auto-generated resolver template for ${typeName} contract
|
|
521
|
+
* Generated by @reactive-contracts/compiler
|
|
522
|
+
*
|
|
523
|
+
* Intent: ${definition.intent}
|
|
524
|
+
*/
|
|
525
|
+
|
|
526
|
+
import { implementContract } from '@reactive-contracts/server';
|
|
527
|
+
import type { Contract } from '@reactive-contracts/core';
|
|
528
|
+
import type { ResolverContext } from '@reactive-contracts/server';
|
|
529
|
+
|
|
530
|
+
${shapeType}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Implement the ${typeName} contract resolver
|
|
534
|
+
*
|
|
535
|
+
* This function should return data matching the contract shape.
|
|
536
|
+
* Derived fields will be computed automatically - don't include them.
|
|
537
|
+
*/
|
|
538
|
+
export const ${typeName}Resolver = implementContract(
|
|
539
|
+
// Import your contract definition here
|
|
540
|
+
{} as Contract, // Replace with your contract
|
|
541
|
+
{
|
|
542
|
+
async resolve(params: Record<string, unknown>, context: ResolverContext): Promise<${typeName}ResolverShape> {
|
|
543
|
+
// TODO: Implement your data fetching logic here
|
|
544
|
+
|
|
545
|
+
// Example:
|
|
546
|
+
// const data = await db.query(...);
|
|
547
|
+
// return {
|
|
548
|
+
// // Map your data to match the contract shape
|
|
549
|
+
// };
|
|
550
|
+
|
|
551
|
+
throw new Error('${typeName}Resolver not implemented yet');
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// Optional: Configure caching
|
|
555
|
+
cache: {
|
|
556
|
+
ttl: '5m',
|
|
557
|
+
staleWhileRevalidate: '1h',
|
|
558
|
+
tags: (params) => [\`${typeName.toLowerCase()}:\${params.id}\`],
|
|
559
|
+
},
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
`;
|
|
563
|
+
await promises.mkdir(path2.dirname(outputPath), { recursive: true });
|
|
564
|
+
await promises.writeFile(outputPath, content, "utf-8");
|
|
565
|
+
}
|
|
566
|
+
async function generateRuntimeNegotiator(contract, outputPath) {
|
|
567
|
+
const { definition } = contract;
|
|
568
|
+
const typeName = definition.name;
|
|
569
|
+
const content = `/**
|
|
570
|
+
* Auto-generated runtime negotiator for ${typeName} contract
|
|
571
|
+
* DO NOT EDIT - This file is generated by @reactive-contracts/compiler
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
import type { Contract } from '@reactive-contracts/core';
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Runtime negotiator for ${typeName}
|
|
578
|
+
* Handles SLA monitoring, fallback logic, and performance tracking
|
|
579
|
+
*/
|
|
580
|
+
export class ${typeName}Negotiator {
|
|
581
|
+
private contract: Contract;
|
|
582
|
+
private metrics: {
|
|
583
|
+
executionTime: number[];
|
|
584
|
+
cacheHits: number;
|
|
585
|
+
cacheMisses: number;
|
|
586
|
+
} = {
|
|
587
|
+
executionTime: [],
|
|
588
|
+
cacheHits: 0,
|
|
589
|
+
cacheMisses: 0,
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
constructor(contract: Contract) {
|
|
593
|
+
this.contract = contract;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Execute contract with SLA monitoring
|
|
598
|
+
*/
|
|
599
|
+
async execute<TData>(
|
|
600
|
+
resolver: () => Promise<TData>,
|
|
601
|
+
options?: {
|
|
602
|
+
useCache?: boolean;
|
|
603
|
+
timeout?: number;
|
|
604
|
+
}
|
|
605
|
+
): Promise<{
|
|
606
|
+
data: TData;
|
|
607
|
+
status: {
|
|
608
|
+
latency: 'normal' | 'degraded' | 'violated';
|
|
609
|
+
freshness: 'fresh' | 'stale' | 'expired';
|
|
610
|
+
availability: 'available' | 'degraded' | 'unavailable';
|
|
611
|
+
};
|
|
612
|
+
metadata: {
|
|
613
|
+
executionTime: number;
|
|
614
|
+
cacheHit: boolean;
|
|
615
|
+
derivedAt: 'client' | 'edge' | 'origin';
|
|
616
|
+
};
|
|
617
|
+
}> {
|
|
618
|
+
const startTime = performance.now();
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
// Execute resolver
|
|
622
|
+
const data = await resolver();
|
|
623
|
+
const executionTime = performance.now() - startTime;
|
|
624
|
+
|
|
625
|
+
// Track metrics
|
|
626
|
+
this.metrics.executionTime.push(executionTime);
|
|
627
|
+
if (options?.useCache) {
|
|
628
|
+
this.metrics.cacheHits++;
|
|
629
|
+
} else {
|
|
630
|
+
this.metrics.cacheMisses++;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Determine latency status
|
|
634
|
+
const maxLatency = this.getMaxLatency();
|
|
635
|
+
const latencyStatus = this.evaluateLatency(executionTime, maxLatency);
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
data,
|
|
639
|
+
status: {
|
|
640
|
+
latency: latencyStatus,
|
|
641
|
+
freshness: 'fresh',
|
|
642
|
+
availability: 'available',
|
|
643
|
+
},
|
|
644
|
+
metadata: {
|
|
645
|
+
executionTime,
|
|
646
|
+
cacheHit: options?.useCache ?? false,
|
|
647
|
+
derivedAt: 'origin',
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
} catch (error) {
|
|
651
|
+
// Handle fallback based on contract constraints
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Get metrics for monitoring
|
|
658
|
+
*/
|
|
659
|
+
getMetrics() {
|
|
660
|
+
const avgExecutionTime =
|
|
661
|
+
this.metrics.executionTime.length > 0
|
|
662
|
+
? this.metrics.executionTime.reduce((a, b) => a + b, 0) / this.metrics.executionTime.length
|
|
663
|
+
: 0;
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
averageExecutionTime: avgExecutionTime,
|
|
667
|
+
p95ExecutionTime: this.calculateP95(),
|
|
668
|
+
cacheHitRate:
|
|
669
|
+
this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) || 0,
|
|
670
|
+
totalExecutions: this.metrics.executionTime.length,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private getMaxLatency(): number {
|
|
675
|
+
const latency = this.contract.definition.constraints?.latency?.max;
|
|
676
|
+
if (!latency) return Infinity;
|
|
677
|
+
|
|
678
|
+
// Simple parsing for MVP
|
|
679
|
+
const match = latency.match(/^(\\d+)(ms|s|m)$/);
|
|
680
|
+
if (!match) return Infinity;
|
|
681
|
+
|
|
682
|
+
const value = parseInt(match[1], 10);
|
|
683
|
+
const unit = match[2];
|
|
684
|
+
|
|
685
|
+
switch (unit) {
|
|
686
|
+
case 'ms': return value;
|
|
687
|
+
case 's': return value * 1000;
|
|
688
|
+
case 'm': return value * 60 * 1000;
|
|
689
|
+
default: return Infinity;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private evaluateLatency(
|
|
694
|
+
executionTime: number,
|
|
695
|
+
maxLatency: number
|
|
696
|
+
): 'normal' | 'degraded' | 'violated' {
|
|
697
|
+
if (executionTime <= maxLatency) {
|
|
698
|
+
return 'normal';
|
|
699
|
+
} else if (executionTime <= maxLatency * 1.5) {
|
|
700
|
+
return 'degraded';
|
|
701
|
+
} else {
|
|
702
|
+
return 'violated';
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private calculateP95(): number {
|
|
707
|
+
if (this.metrics.executionTime.length === 0) return 0;
|
|
708
|
+
|
|
709
|
+
const sorted = [...this.metrics.executionTime].sort((a, b) => a - b);
|
|
710
|
+
const index = Math.floor(sorted.length * 0.95);
|
|
711
|
+
return sorted[index];
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Create a new negotiator instance
|
|
717
|
+
*/
|
|
718
|
+
export function create${typeName}Negotiator(contract: Contract): ${typeName}Negotiator {
|
|
719
|
+
return new ${typeName}Negotiator(contract);
|
|
720
|
+
}
|
|
721
|
+
`;
|
|
722
|
+
await promises.mkdir(path2.dirname(outputPath), { recursive: true });
|
|
417
723
|
await promises.writeFile(outputPath, content, "utf-8");
|
|
418
724
|
}
|
|
419
725
|
function generateTypeDefinitions(shape, typeName) {
|
|
@@ -465,10 +771,173 @@ ${indentStr}}`;
|
|
|
465
771
|
}
|
|
466
772
|
return "any";
|
|
467
773
|
}
|
|
774
|
+
var silentLogger = {
|
|
775
|
+
info: () => {
|
|
776
|
+
},
|
|
777
|
+
success: () => {
|
|
778
|
+
},
|
|
779
|
+
warning: () => {
|
|
780
|
+
},
|
|
781
|
+
error: () => {
|
|
782
|
+
},
|
|
783
|
+
verbose: () => {
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
var consoleLogger = {
|
|
787
|
+
info: (msg) => console.log("[rcontracts]", msg),
|
|
788
|
+
success: (msg) => console.log("[rcontracts] \u2713", msg),
|
|
789
|
+
warning: (msg) => console.warn("[rcontracts] \u26A0", msg),
|
|
790
|
+
error: (msg) => console.error("[rcontracts] \u2717", msg),
|
|
791
|
+
verbose: (msg) => console.log("[rcontracts]", msg)
|
|
792
|
+
};
|
|
793
|
+
async function parseContractFile(filePath, logger = silentLogger) {
|
|
794
|
+
const contracts = [];
|
|
795
|
+
const errors = [];
|
|
796
|
+
const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)), {
|
|
797
|
+
interopDefault: true,
|
|
798
|
+
moduleCache: false
|
|
799
|
+
// Disable cache to avoid stale imports
|
|
800
|
+
});
|
|
801
|
+
try {
|
|
802
|
+
const module = await jiti$1.import(filePath);
|
|
803
|
+
const contractExports = Object.entries(module).filter(
|
|
804
|
+
([key, value]) => key.endsWith("Contract") && typeof value === "object" && value !== null && "_brand" in value && value._brand === "Contract"
|
|
805
|
+
);
|
|
806
|
+
if (contractExports.length === 0) {
|
|
807
|
+
errors.push(`No contract found in ${path2__namespace.basename(filePath)}`);
|
|
808
|
+
return { contracts, errors };
|
|
809
|
+
}
|
|
810
|
+
for (const [exportName, contractObj] of contractExports) {
|
|
811
|
+
contracts.push({
|
|
812
|
+
name: exportName,
|
|
813
|
+
contract: contractObj
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
} catch (err) {
|
|
817
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
818
|
+
errors.push(`Failed to parse ${path2__namespace.basename(filePath)}: ${errorMessage}`);
|
|
819
|
+
logger.error?.(errorMessage);
|
|
820
|
+
}
|
|
821
|
+
return { contracts, errors };
|
|
822
|
+
}
|
|
823
|
+
async function compileContract(contract, config, cwd = process.cwd(), logger = silentLogger) {
|
|
824
|
+
const validation = validateContract(contract);
|
|
825
|
+
const latency = analyzeLatency(contract);
|
|
826
|
+
const generated = {};
|
|
827
|
+
for (const warn of validation.warnings) {
|
|
828
|
+
logger.warning?.(warn);
|
|
829
|
+
}
|
|
830
|
+
if (!validation.valid) {
|
|
831
|
+
for (const err of validation.errors) {
|
|
832
|
+
logger.error?.(err);
|
|
833
|
+
}
|
|
834
|
+
return { contract, validation, latency, generated };
|
|
835
|
+
}
|
|
836
|
+
if (latency.status === "error") {
|
|
837
|
+
logger.error?.(latency.message || "Latency analysis failed");
|
|
838
|
+
if (config.validation?.strictLatency) {
|
|
839
|
+
return { contract, validation, latency, generated };
|
|
840
|
+
}
|
|
841
|
+
} else if (latency.status === "warning") {
|
|
842
|
+
logger.warning?.(latency.message || "Latency constraint may not be met");
|
|
843
|
+
}
|
|
844
|
+
if (latency.suggestions?.length) {
|
|
845
|
+
for (const suggestion of latency.suggestions) {
|
|
846
|
+
logger.info?.(`\u{1F4A1} ${suggestion}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const contractName = contract.definition.name;
|
|
850
|
+
try {
|
|
851
|
+
const frontendPath = path2__namespace.join(cwd, config.output.frontend, `${contractName}.ts`);
|
|
852
|
+
await generateFrontendTypes(contract, frontendPath);
|
|
853
|
+
generated.frontend = frontendPath;
|
|
854
|
+
const backendPath = path2__namespace.join(cwd, config.output.backend, `${contractName}.resolver.ts`);
|
|
855
|
+
await generateBackendResolver(contract, backendPath);
|
|
856
|
+
generated.backend = backendPath;
|
|
857
|
+
const runtimePath = path2__namespace.join(cwd, config.output.runtime, `${contractName}.negotiator.ts`);
|
|
858
|
+
await generateRuntimeNegotiator(contract, runtimePath);
|
|
859
|
+
generated.runtime = runtimePath;
|
|
860
|
+
logger.success?.(`Generated code for ${contractName}`);
|
|
861
|
+
} catch (err) {
|
|
862
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
863
|
+
logger.error?.(`Failed to generate code for ${contractName}: ${errorMessage}`);
|
|
864
|
+
validation.errors.push(errorMessage);
|
|
865
|
+
validation.valid = false;
|
|
866
|
+
}
|
|
867
|
+
return { contract, validation, latency, generated };
|
|
868
|
+
}
|
|
869
|
+
async function findContractFiles(config, cwd = process.cwd()) {
|
|
870
|
+
return glob.glob(config.contracts, {
|
|
871
|
+
cwd,
|
|
872
|
+
absolute: true
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
async function compileAll(options) {
|
|
876
|
+
const { config, cwd = process.cwd(), logger = silentLogger, file } = options;
|
|
877
|
+
const results = [];
|
|
878
|
+
const errors = [];
|
|
879
|
+
const warnings = [];
|
|
880
|
+
let contractFiles;
|
|
881
|
+
if (file) {
|
|
882
|
+
contractFiles = [path2__namespace.isAbsolute(file) ? file : path2__namespace.join(cwd, file)];
|
|
883
|
+
} else {
|
|
884
|
+
contractFiles = await findContractFiles(config, cwd);
|
|
885
|
+
}
|
|
886
|
+
if (contractFiles.length === 0) {
|
|
887
|
+
errors.push(`No contract files found matching pattern: ${config.contracts}`);
|
|
888
|
+
return { success: false, results, errors, warnings };
|
|
889
|
+
}
|
|
890
|
+
logger.info?.(`Found ${contractFiles.length} contract file(s)`);
|
|
891
|
+
for (const filePath of contractFiles) {
|
|
892
|
+
const fileName = path2__namespace.basename(filePath, ".contract.ts");
|
|
893
|
+
logger.verbose?.(`Processing ${fileName}...`);
|
|
894
|
+
const { contracts, errors: parseErrors } = await parseContractFile(filePath, logger);
|
|
895
|
+
if (parseErrors.length > 0) {
|
|
896
|
+
errors.push(...parseErrors);
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
for (const { name, contract } of contracts) {
|
|
900
|
+
const result = await compileContract(contract, config, cwd, logger);
|
|
901
|
+
results.push(result);
|
|
902
|
+
if (!result.validation.valid) {
|
|
903
|
+
errors.push(`Validation failed for ${name}`);
|
|
904
|
+
}
|
|
905
|
+
warnings.push(...result.validation.warnings);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const success = errors.length === 0;
|
|
909
|
+
if (success) {
|
|
910
|
+
logger.success?.(`Successfully compiled ${results.length} contract(s)`);
|
|
911
|
+
} else {
|
|
912
|
+
logger.error?.(`Compilation failed with ${errors.length} error(s)`);
|
|
913
|
+
}
|
|
914
|
+
return { success, results, errors, warnings };
|
|
915
|
+
}
|
|
916
|
+
function isContractFile(filePath) {
|
|
917
|
+
return filePath.endsWith(".contract.ts");
|
|
918
|
+
}
|
|
919
|
+
function getGeneratedFilesForContract(contractName, config, cwd = process.cwd()) {
|
|
920
|
+
return {
|
|
921
|
+
frontend: path2__namespace.join(cwd, config.output.frontend, `${contractName}.ts`),
|
|
922
|
+
backend: path2__namespace.join(cwd, config.output.backend, `${contractName}.resolver.ts`),
|
|
923
|
+
runtime: path2__namespace.join(cwd, config.output.runtime, `${contractName}.negotiator.ts`)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
468
926
|
|
|
469
927
|
exports.analyzeLatency = analyzeLatency;
|
|
928
|
+
exports.compileAll = compileAll;
|
|
929
|
+
exports.compileContract = compileContract;
|
|
930
|
+
exports.consoleLogger = consoleLogger;
|
|
470
931
|
exports.defineConfig = defineConfig;
|
|
932
|
+
exports.findContractFiles = findContractFiles;
|
|
933
|
+
exports.generateBackendResolver = generateBackendResolver;
|
|
471
934
|
exports.generateFrontendTypes = generateFrontendTypes;
|
|
935
|
+
exports.generateRuntimeNegotiator = generateRuntimeNegotiator;
|
|
936
|
+
exports.getGeneratedFilesForContract = getGeneratedFilesForContract;
|
|
937
|
+
exports.isContractFile = isContractFile;
|
|
938
|
+
exports.loadConfig = loadConfig;
|
|
939
|
+
exports.parseContractFile = parseContractFile;
|
|
940
|
+
exports.silentLogger = silentLogger;
|
|
472
941
|
exports.validateContract = validateContract;
|
|
473
942
|
//# sourceMappingURL=index.cjs.map
|
|
474
943
|
//# sourceMappingURL=index.cjs.map
|