atmx-cli 0.47.0 → 0.49.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/generators/model-generator.js +50 -49
- package/dist/generators/sdk-generator.js +31 -17
- package/dist/generators/utils.js +34 -35
- package/dist/index.js +15 -24
- package/package.json +3 -1
- package/src/generators/model-generator.ts +112 -133
- package/src/generators/sdk-generator.ts +36 -21
- package/src/generators/utils.ts +34 -45
- package/src/index.ts +19 -34
|
@@ -4,91 +4,92 @@ exports.generateModels = generateModels;
|
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
5
|
function generateModels(multiIr) {
|
|
6
6
|
const sections = [
|
|
7
|
-
`// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n
|
|
7
|
+
`// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n`,
|
|
8
8
|
];
|
|
9
|
-
// 1. Generate flat exports (NO namespaces)
|
|
10
9
|
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
enumsList
|
|
15
|
-
|
|
10
|
+
const camelNs = (0, utils_1.camelCase)(ns);
|
|
11
|
+
// ✨ FIX: Use proper TS namespaces
|
|
12
|
+
sections.push(`export namespace ${camelNs} {`);
|
|
13
|
+
const enumsList = Array.isArray(ir.enums)
|
|
14
|
+
? ir.enums
|
|
15
|
+
: Object.values(ir.enums || {});
|
|
16
|
+
const modelsList = Array.isArray(ir.models)
|
|
17
|
+
? ir.models
|
|
18
|
+
: Object.values(ir.models || {});
|
|
19
|
+
enumsList.forEach((en) => sections.push(generateEnum(en)));
|
|
20
|
+
modelsList.forEach((model) => sections.push(generateInterface(model, camelNs)));
|
|
21
|
+
sections.push(`}\n`);
|
|
16
22
|
}
|
|
17
|
-
// 2. Generate nested Mappers object
|
|
18
23
|
sections.push(generateMappers(multiIr));
|
|
19
|
-
return sections.join(
|
|
24
|
+
return sections.join("\n");
|
|
20
25
|
}
|
|
21
|
-
function generateEnum(en
|
|
22
|
-
const name =
|
|
23
|
-
const values = en.values.map(v => ` ${(0, utils_1.pascalCase)(v)}: "${v}"`).join(
|
|
26
|
+
function generateEnum(en) {
|
|
27
|
+
const name = (0, utils_1.pascalCase)(en.name);
|
|
28
|
+
const values = en.values.map((v) => ` ${(0, utils_1.pascalCase)(v)}: "${v}"`).join(",\n");
|
|
24
29
|
return `
|
|
25
|
-
export const ${name} = {
|
|
26
|
-
${values}
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
`;
|
|
30
|
+
export const ${name} = {
|
|
31
|
+
${values}
|
|
32
|
+
} as const;
|
|
33
|
+
export type ${name} = typeof ${name}[keyof typeof ${name}];
|
|
34
|
+
`;
|
|
31
35
|
}
|
|
32
36
|
function generateInterface(model, ns) {
|
|
33
|
-
const name =
|
|
34
|
-
const fields = model.fields
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const name = (0, utils_1.pascalCase)(model.name);
|
|
38
|
+
const fields = model.fields
|
|
39
|
+
.map((f) => {
|
|
40
|
+
const type = (0, utils_1.mapTypeToTs)(f.typeRef, ns);
|
|
41
|
+
return ` ${(0, utils_1.camelCase)(f.name)}${f.isOptional ? "?" : ""}: ${type};`;
|
|
42
|
+
})
|
|
43
|
+
.join("\n");
|
|
38
44
|
return `
|
|
39
|
-
export interface ${name} {
|
|
45
|
+
export interface ${name} {
|
|
40
46
|
${fields}
|
|
41
|
-
}
|
|
42
|
-
`;
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
43
49
|
}
|
|
44
50
|
function generateMappers(multiIr) {
|
|
45
51
|
const lines = [`export const Mappers: Record<string, any> = {`];
|
|
46
52
|
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
47
53
|
const camelNs = (0, utils_1.camelCase)(ns);
|
|
48
|
-
const pascalNs = (0, utils_1.pascalCase)(ns);
|
|
49
54
|
lines.push(` ${camelNs}: {`);
|
|
50
|
-
const modelsList = Array.isArray(ir.models)
|
|
55
|
+
const modelsList = Array.isArray(ir.models)
|
|
56
|
+
? ir.models
|
|
57
|
+
: Object.values(ir.models || {});
|
|
51
58
|
modelsList.forEach((model) => {
|
|
52
59
|
const name = (0, utils_1.pascalCase)(model.name);
|
|
53
|
-
const fullType = `${
|
|
60
|
+
const fullType = `${camelNs}.${name}`;
|
|
54
61
|
lines.push(` ${name}: {\n fromJson: (json: any): ${fullType} => ({`);
|
|
55
62
|
model.fields.forEach((f) => {
|
|
56
|
-
lines.push(` ${(0, utils_1.camelCase)(f.name)}: ${generateJsonLogic(f.typeRef, `json["${f.name}"]`, f.isOptional,
|
|
63
|
+
lines.push(` ${(0, utils_1.camelCase)(f.name)}: ${generateJsonLogic(f.typeRef, `json["${f.name}"]`, f.isOptional, "fromJson", camelNs)},`);
|
|
57
64
|
});
|
|
58
65
|
lines.push(` }),\n toJson: (obj: any): any => ({`);
|
|
59
66
|
model.fields.forEach((f) => {
|
|
60
|
-
lines.push(` "${f.name}": ${generateJsonLogic(f.typeRef, `obj.${(0, utils_1.camelCase)(f.name)}`, f.isOptional,
|
|
67
|
+
lines.push(` "${f.name}": ${generateJsonLogic(f.typeRef, `obj.${(0, utils_1.camelCase)(f.name)}`, f.isOptional, "toJson", camelNs)},`);
|
|
61
68
|
});
|
|
62
69
|
lines.push(` })\n },`);
|
|
63
70
|
});
|
|
64
71
|
lines.push(` },`);
|
|
65
72
|
}
|
|
66
73
|
lines.push(`};\n`);
|
|
67
|
-
return lines.join(
|
|
74
|
+
return lines.join("\n");
|
|
68
75
|
}
|
|
69
76
|
function generateJsonLogic(typeRef, access, isOpt, mode, ns) {
|
|
70
77
|
const wrap = (logic) => isOpt ? `(${access} == null ? undefined : ${logic})` : logic;
|
|
71
78
|
if (!typeRef || !typeRef.kind)
|
|
72
79
|
return access;
|
|
73
|
-
if (typeRef.kind ===
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
: wrap(`Array.from(${access})`);
|
|
83
|
-
}
|
|
84
|
-
return access;
|
|
85
|
-
}
|
|
86
|
-
if (typeRef.kind === 'named') {
|
|
80
|
+
if (typeRef.kind === "dateTime")
|
|
81
|
+
return mode === "fromJson"
|
|
82
|
+
? wrap(`new Date(${access})`)
|
|
83
|
+
: wrap(`${access}.toISOString()`);
|
|
84
|
+
if (typeRef.kind === "bytes")
|
|
85
|
+
return mode === "fromJson"
|
|
86
|
+
? wrap(`new Uint8Array(${access})`)
|
|
87
|
+
: wrap(`Array.from(${access})`);
|
|
88
|
+
if (typeRef.kind === "named") {
|
|
87
89
|
const name = (0, utils_1.pascalCase)(typeRef.value);
|
|
88
90
|
return wrap(`(Mappers.${ns}["${name}"] ? Mappers.${ns}["${name}"].${mode}(${access}) : ${access})`);
|
|
89
91
|
}
|
|
90
|
-
if (typeRef.kind ===
|
|
91
|
-
return wrap(`${access}.map((e: any) => ${generateJsonLogic(typeRef.value,
|
|
92
|
-
}
|
|
92
|
+
if (typeRef.kind === "list")
|
|
93
|
+
return wrap(`${access}.map((e: any) => ${generateJsonLogic(typeRef.value, "e", false, mode, ns)})`);
|
|
93
94
|
return access;
|
|
94
95
|
}
|
|
@@ -5,6 +5,7 @@ const utils_1 = require("./utils");
|
|
|
5
5
|
function generateSdk(multiIr, isReact = false) {
|
|
6
6
|
const lines = [
|
|
7
7
|
`// GENERATED CODE – DO NOT EDIT.`,
|
|
8
|
+
`/* eslint-disable @typescript-eslint/no-explicit-any */`,
|
|
8
9
|
`import * as models from './models';\n`,
|
|
9
10
|
];
|
|
10
11
|
if (isReact) {
|
|
@@ -12,34 +13,47 @@ function generateSdk(multiIr, isReact = false) {
|
|
|
12
13
|
lines.push(`import type { AxiomQueryDef } from 'atmx-react';\n`);
|
|
13
14
|
}
|
|
14
15
|
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
15
|
-
const
|
|
16
|
-
|
|
16
|
+
const camelNs = (0, utils_1.camelCase)(ns);
|
|
17
|
+
// ✨ FIX: Use an object literal instead of a Class to support React Hooks!
|
|
18
|
+
lines.push(`export const ${camelNs}Module = {`);
|
|
17
19
|
const endpointsMap = ir.endpoints || {};
|
|
18
20
|
const endpoints = Array.isArray(endpointsMap)
|
|
19
21
|
? endpointsMap
|
|
20
22
|
: Object.values(endpointsMap);
|
|
21
23
|
endpoints.forEach((ep) => {
|
|
22
|
-
lines.push(generateEndpointMethod(ep, ns,
|
|
24
|
+
lines.push(generateEndpointMethod(ep, ns, camelNs, isReact));
|
|
23
25
|
});
|
|
24
|
-
lines.push(`}
|
|
26
|
+
lines.push(`};\n`);
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
// Generate main SDK object
|
|
29
|
+
lines.push(`export const sdk = {`);
|
|
27
30
|
for (const ns of Object.keys(multiIr)) {
|
|
28
|
-
lines.push(`
|
|
31
|
+
lines.push(` ${(0, utils_1.camelCase)(ns)}: ${(0, utils_1.camelCase)(ns)}Module,`);
|
|
29
32
|
}
|
|
30
|
-
lines.push(`}
|
|
33
|
+
lines.push(`};\n`);
|
|
34
|
+
// Generate Config
|
|
35
|
+
lines.push(`export const AxiomDefaultConfig = {`);
|
|
36
|
+
lines.push(` contracts: {`);
|
|
37
|
+
for (const ns of Object.keys(multiIr)) {
|
|
38
|
+
lines.push(` "${ns}": {`);
|
|
39
|
+
lines.push(` contractUrl: "/${ns}.axiom",`);
|
|
40
|
+
lines.push(` baseUrl: "http://localhost:8000"`);
|
|
41
|
+
lines.push(` },`);
|
|
42
|
+
}
|
|
43
|
+
lines.push(` }`);
|
|
44
|
+
lines.push(`};\n`);
|
|
31
45
|
return lines.join("\n");
|
|
32
46
|
}
|
|
33
|
-
function generateEndpointMethod(ep, ns,
|
|
47
|
+
function generateEndpointMethod(ep, ns, camelNs, isReact) {
|
|
34
48
|
const rawParams = ep.parameters || [];
|
|
35
49
|
const params = Array.isArray(rawParams)
|
|
36
50
|
? rawParams
|
|
37
51
|
: Object.values(rawParams);
|
|
38
52
|
const argType = params.length > 0
|
|
39
|
-
? `{ ${params.map((p) => `${(0, utils_1.camelCase)(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels((0, utils_1.mapTypeToTs)(p.typeRef,
|
|
53
|
+
? `{ ${params.map((p) => `${(0, utils_1.camelCase)(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels((0, utils_1.mapTypeToTs)(p.typeRef, camelNs))}`).join(", ")} }`
|
|
40
54
|
: "void";
|
|
41
55
|
const isQuery = ep.method ? ep.method.toUpperCase() === "GET" : true;
|
|
42
|
-
const rawReturnType = (0, utils_1.mapTypeToTs)(ep.returnType,
|
|
56
|
+
const rawReturnType = (0, utils_1.mapTypeToTs)(ep.returnType, camelNs);
|
|
43
57
|
const returnType = rawReturnType === "void" || rawReturnType === "any"
|
|
44
58
|
? rawReturnType
|
|
45
59
|
: prefixModels(rawReturnType);
|
|
@@ -48,9 +62,9 @@ function generateEndpointMethod(ep, ns, pascalNs, isReact) {
|
|
|
48
62
|
const payloadLogic = bodyParam
|
|
49
63
|
? `const payload = (args as any)?.${(0, utils_1.camelCase)(bodyParam.name)};`
|
|
50
64
|
: `const payload = undefined;`;
|
|
51
|
-
const decLogic = generateLambda(ep.returnType, "fromJson",
|
|
65
|
+
const decLogic = generateLambda(ep.returnType, "fromJson", camelNs);
|
|
52
66
|
const serLogic = bodyParam
|
|
53
|
-
? generateLambda(bodyParam.typeRef, "toJson",
|
|
67
|
+
? generateLambda(bodyParam.typeRef, "toJson", camelNs)
|
|
54
68
|
: `(p: any) => p`;
|
|
55
69
|
return `
|
|
56
70
|
get${(0, utils_1.pascalCase)(ep.name)}Def(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): AxiomQueryDef<${returnType}> {
|
|
@@ -67,29 +81,29 @@ function generateEndpointMethod(ep, ns, pascalNs, isReact) {
|
|
|
67
81
|
serializer: ${serLogic},
|
|
68
82
|
isStream: ${ep.isStream === true}
|
|
69
83
|
};
|
|
70
|
-
}
|
|
84
|
+
},
|
|
71
85
|
|
|
72
86
|
use${(0, utils_1.pascalCase)(ep.name)}${!isQuery ? "Mutation" : ""}(${isQuery ? `args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}, options?: { enabled?: boolean }` : ""}) {
|
|
73
87
|
${isQuery
|
|
74
88
|
? `return useAxiomQuery<${returnType}>(this.get${(0, utils_1.pascalCase)(ep.name)}Def(args), options);`
|
|
75
89
|
: `return useAxiomMutation<${returnType}, ${argType === "void" ? "void | Record<string,any>" : argType}>((args) => this.get${(0, utils_1.pascalCase)(ep.name)}Def(args));`}
|
|
76
|
-
}
|
|
90
|
+
},`;
|
|
77
91
|
}
|
|
78
92
|
else {
|
|
79
93
|
return `
|
|
80
94
|
${(0, utils_1.camelCase)(ep.name)}(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): string {
|
|
81
95
|
const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';
|
|
82
96
|
return \`${ns}.${ep.name}(\${argsStr})\`;
|
|
83
|
-
}
|
|
97
|
+
},`;
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
function generateLambda(typeRef, mode, ns) {
|
|
87
101
|
if (!typeRef || !typeRef.kind || typeRef.kind === "void")
|
|
88
102
|
return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
|
|
89
103
|
if (typeRef.kind === "list" && typeRef.value?.kind === "named")
|
|
90
|
-
return `(data: any[]) => data.map(models.Mappers.${
|
|
104
|
+
return `(data: any[]) => data.map(models.Mappers.${ns}.${(0, utils_1.pascalCase)(typeRef.value.value)}.${mode})`;
|
|
91
105
|
if (typeRef.kind === "named")
|
|
92
|
-
return `models.Mappers.${
|
|
106
|
+
return `models.Mappers.${ns}.${(0, utils_1.pascalCase)(typeRef.value)}.${mode}`;
|
|
93
107
|
return `(data: any) => data`;
|
|
94
108
|
}
|
|
95
109
|
function prefixModels(type) {
|
package/dist/generators/utils.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// atmx-cli/src/generators/utils.ts
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.pascalCase = pascalCase;
|
|
5
4
|
exports.camelCase = camelCase;
|
|
6
5
|
exports.normalizeIr = normalizeIr;
|
|
7
6
|
exports.mapTypeToTs = mapTypeToTs;
|
|
7
|
+
// FILE: atmx-cli/src/generators/utils.ts
|
|
8
8
|
function pascalCase(str) {
|
|
9
9
|
if (!str)
|
|
10
10
|
return "";
|
|
@@ -18,17 +18,14 @@ function camelCase(str) {
|
|
|
18
18
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
19
19
|
}
|
|
20
20
|
function normalizeIr(obj) {
|
|
21
|
-
|
|
22
|
-
if (Array.isArray(obj)) {
|
|
21
|
+
if (Array.isArray(obj))
|
|
23
22
|
return obj.map(normalizeIr);
|
|
24
|
-
}
|
|
25
23
|
if (obj !== null && typeof obj === "object") {
|
|
26
24
|
const newObj = {};
|
|
27
25
|
for (const key of Object.keys(obj)) {
|
|
28
26
|
const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
29
27
|
newObj[camelKey] = normalizeIr(obj[key]);
|
|
30
28
|
}
|
|
31
|
-
// Convert common Maps to Arrays if they exist and are not already arrays
|
|
32
29
|
if (newObj.endpoints &&
|
|
33
30
|
typeof newObj.endpoints === "object" &&
|
|
34
31
|
!Array.isArray(newObj.endpoints)) {
|
|
@@ -44,7 +41,6 @@ function normalizeIr(obj) {
|
|
|
44
41
|
!Array.isArray(newObj.enums)) {
|
|
45
42
|
newObj.enums = Object.values(newObj.enums);
|
|
46
43
|
}
|
|
47
|
-
// Traverse down into models to normalize fields
|
|
48
44
|
if (Array.isArray(newObj.models)) {
|
|
49
45
|
newObj.models = newObj.models.map((model) => {
|
|
50
46
|
if (model.fields &&
|
|
@@ -59,36 +55,39 @@ function normalizeIr(obj) {
|
|
|
59
55
|
}
|
|
60
56
|
return obj;
|
|
61
57
|
}
|
|
62
|
-
|
|
63
|
-
* @param scopedNamespace If provided (e.g. 'Auth'), prefixes named types with 'models.Auth.'
|
|
64
|
-
*/
|
|
58
|
+
// ✨ FIX: Properly maps all Axiom primitives to TypeScript
|
|
65
59
|
function mapTypeToTs(typeRef, ns) {
|
|
66
|
-
if (!typeRef)
|
|
60
|
+
if (!typeRef || !typeRef.kind)
|
|
67
61
|
return "any";
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
62
|
+
switch (typeRef.kind) {
|
|
63
|
+
case "string":
|
|
64
|
+
return "string";
|
|
65
|
+
case "int32":
|
|
66
|
+
case "int64":
|
|
67
|
+
case "float32":
|
|
68
|
+
case "float64":
|
|
69
|
+
return "number";
|
|
70
|
+
case "bool":
|
|
71
|
+
return "boolean";
|
|
72
|
+
case "dateTime":
|
|
73
|
+
return "Date";
|
|
74
|
+
case "bytes":
|
|
75
|
+
return "Uint8Array";
|
|
76
|
+
case "json":
|
|
77
|
+
return "any";
|
|
78
|
+
case "void":
|
|
79
|
+
return "void";
|
|
80
|
+
case "named":
|
|
81
|
+
const name = pascalCase(typeRef.value);
|
|
82
|
+
return ns ? `${ns}.${name}` : name;
|
|
83
|
+
case "list":
|
|
84
|
+
return `${mapTypeToTs(typeRef.value, ns)}[]`;
|
|
85
|
+
case "map":
|
|
86
|
+
const valType = typeRef.value?.[1]
|
|
87
|
+
? mapTypeToTs(typeRef.value[1], ns)
|
|
88
|
+
: "any";
|
|
89
|
+
return `Record<string, ${valType}>`;
|
|
90
|
+
default:
|
|
91
|
+
return "any";
|
|
92
92
|
}
|
|
93
|
-
return "any";
|
|
94
93
|
}
|
package/dist/index.js
CHANGED
|
@@ -37,19 +37,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
const commander_1 = require("commander");
|
|
38
38
|
const fs = __importStar(require("fs-extra"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const toml = __importStar(require("@iarna/toml"));
|
|
40
41
|
const model_generator_1 = require("./generators/model-generator");
|
|
41
42
|
const sdk_generator_1 = require("./generators/sdk-generator");
|
|
42
43
|
const utils_1 = require("./generators/utils");
|
|
43
44
|
const program = new commander_1.Command();
|
|
44
45
|
program
|
|
45
46
|
.name("atmx")
|
|
46
|
-
.description("Generate TypeScript SDK from
|
|
47
|
+
.description("Generate TypeScript SDK from AxiomDeps.toml")
|
|
47
48
|
.version("0.2.0");
|
|
48
49
|
program
|
|
49
50
|
.command("generate")
|
|
50
|
-
.requiredOption("-c, --config <path>", "Path to
|
|
51
|
+
.requiredOption("-c, --config <path>", "Path to AxiomDeps.toml")
|
|
51
52
|
.requiredOption("-o, --output <dir>", "Output directory for generated files")
|
|
52
|
-
.option("-r, --react", "Generate React Hooks instead of Vanilla JS strings")
|
|
53
|
+
.option("-r, --react", "Generate React Hooks instead of Vanilla JS strings")
|
|
53
54
|
.action(async (options) => {
|
|
54
55
|
const configPath = path.resolve(options.config);
|
|
55
56
|
const outputDir = path.resolve(options.output);
|
|
@@ -57,42 +58,32 @@ program
|
|
|
57
58
|
console.error(`❌ Error: Config file not found at ${configPath}`);
|
|
58
59
|
process.exit(1);
|
|
59
60
|
}
|
|
60
|
-
// 1. Read
|
|
61
|
-
const
|
|
61
|
+
// 1. Read and Parse TOML
|
|
62
|
+
const tomlString = await fs.readFile(configPath, "utf-8");
|
|
63
|
+
const rawConfig = toml.parse(tomlString);
|
|
62
64
|
if (!rawConfig.contracts || Object.keys(rawConfig.contracts).length === 0) {
|
|
63
|
-
console.error("❌ Error:
|
|
65
|
+
console.error("❌ Error: No contracts defined in AxiomDeps.toml.");
|
|
64
66
|
process.exit(1);
|
|
65
67
|
}
|
|
66
|
-
const configDir = path.dirname(configPath);
|
|
67
68
|
const multiIr = {};
|
|
68
|
-
|
|
69
|
+
const projectRoot = path.dirname(configPath); // Frontend project root
|
|
70
|
+
// 2. Loop through contracts
|
|
69
71
|
for (const [namespace, contract] of Object.entries(rawConfig.contracts)) {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
const axiomFilePath = path.resolve(configDir, contract.file);
|
|
72
|
+
// Rust CLI safely copies files to `public/[namespace].axiom`
|
|
73
|
+
const axiomFilePath = path.resolve(projectRoot, `public/${namespace}.axiom`);
|
|
73
74
|
if (!fs.existsSync(axiomFilePath)) {
|
|
74
|
-
console.warn(`⚠️ Warning: Contract file not found
|
|
75
|
+
console.warn(`⚠️ Warning: Contract file not found at ${axiomFilePath}. Skipping...`);
|
|
75
76
|
continue;
|
|
76
77
|
}
|
|
77
78
|
const rawFile = await fs.readJSON(axiomFilePath);
|
|
78
|
-
if (!rawFile.ir)
|
|
79
|
-
console.warn(`⚠️ Warning: Invalid .axiom file for namespace '${namespace}'. Missing 'ir' property. Skipping...`);
|
|
79
|
+
if (!rawFile.ir)
|
|
80
80
|
continue;
|
|
81
|
-
}
|
|
82
|
-
// Normalize the IR (snake_case -> camelCase) and store it by namespace
|
|
83
81
|
multiIr[namespace] = (0, utils_1.normalizeIr)(rawFile.ir);
|
|
84
|
-
console.log(`✅ Loaded contract: [${namespace}] -> ${
|
|
85
|
-
}
|
|
86
|
-
if (Object.keys(multiIr).length === 0) {
|
|
87
|
-
console.error("❌ Error: No valid contracts were loaded. Aborting generation.");
|
|
88
|
-
process.exit(1);
|
|
82
|
+
console.log(`✅ Loaded contract: [${namespace}] -> ${axiomFilePath}`);
|
|
89
83
|
}
|
|
90
|
-
// Ensure output directory exists
|
|
91
84
|
await fs.ensureDir(outputDir);
|
|
92
|
-
// 3. Pass the MultiIR Map to the generators
|
|
93
85
|
const modelsContent = (0, model_generator_1.generateModels)(multiIr);
|
|
94
86
|
await fs.writeFile(path.join(outputDir, "models.ts"), modelsContent);
|
|
95
|
-
// ✨ NEW: Pass the react flag down
|
|
96
87
|
const sdkContent = (0, sdk_generator_1.generateSdk)(multiIr, options.react);
|
|
97
88
|
await fs.writeFile(path.join(outputDir, "sdk.ts"), sdkContent);
|
|
98
89
|
console.log(`\n🎉 ATMX Multi-Contract SDK generated successfully in ${outputDir}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atmx-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
"license": "ISC",
|
|
15
15
|
"type": "commonjs",
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@iarna/toml": "^2.2.5",
|
|
17
18
|
"commander": "^14.0.3",
|
|
18
19
|
"fs-extra": "^11.3.4"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@types/fs-extra": "^11.0.4",
|
|
23
|
+
"@types/iarna__toml": "^2.0.5",
|
|
22
24
|
"@types/node": "^25.5.0",
|
|
23
25
|
"ts-node": "^10.9.2",
|
|
24
26
|
"typescript": "^5.9.3"
|
|
@@ -1,148 +1,127 @@
|
|
|
1
|
-
// atmx-cli/src/generators/model-generator.ts
|
|
2
|
-
import { AxiomEnum, AxiomModel, MultiIR } from
|
|
3
|
-
import { pascalCase, camelCase, mapTypeToTs } from
|
|
1
|
+
// FILE: atmx-cli/src/generators/model-generator.ts
|
|
2
|
+
import { AxiomEnum, AxiomModel, MultiIR } from "../types";
|
|
3
|
+
import { pascalCase, camelCase, mapTypeToTs } from "./utils";
|
|
4
4
|
|
|
5
5
|
export function generateModels(multiIr: MultiIR): string {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
sections.push(
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
const sections: string[] = [
|
|
7
|
+
`// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n`,
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
11
|
+
const camelNs = camelCase(ns);
|
|
12
|
+
// ✨ FIX: Use proper TS namespaces
|
|
13
|
+
sections.push(`export namespace ${camelNs} {`);
|
|
14
|
+
|
|
15
|
+
const enumsList = Array.isArray(ir.enums)
|
|
16
|
+
? ir.enums
|
|
17
|
+
: Object.values(ir.enums || {});
|
|
18
|
+
const modelsList = Array.isArray(ir.models)
|
|
19
|
+
? ir.models
|
|
20
|
+
: Object.values(ir.models || {});
|
|
21
|
+
|
|
22
|
+
enumsList.forEach((en: any) => sections.push(generateEnum(en)));
|
|
23
|
+
modelsList.forEach((model: any) =>
|
|
24
|
+
sections.push(generateInterface(model, camelNs)),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
sections.push(`}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
sections.push(generateMappers(multiIr));
|
|
31
|
+
return sections.join("\n");
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
function generateEnum(en: AxiomEnum
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type ${name} = typeof ${name}[keyof typeof ${name}];
|
|
37
|
-
`;
|
|
34
|
+
function generateEnum(en: AxiomEnum): string {
|
|
35
|
+
const name = pascalCase(en.name);
|
|
36
|
+
const values = en.values.map((v) => ` ${pascalCase(v)}: "${v}"`).join(",\n");
|
|
37
|
+
return `
|
|
38
|
+
export const ${name} = {
|
|
39
|
+
${values}
|
|
40
|
+
} as const;
|
|
41
|
+
export type ${name} = typeof ${name}[keyof typeof ${name}];
|
|
42
|
+
`;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function generateInterface(model: AxiomModel, ns: string): string {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const name = pascalCase(model.name);
|
|
47
|
+
const fields = model.fields
|
|
48
|
+
.map((f) => {
|
|
49
|
+
const type = mapTypeToTs(f.typeRef, ns);
|
|
50
|
+
return ` ${camelCase(f.name)}${f.isOptional ? "?" : ""}: ${type};`;
|
|
51
|
+
})
|
|
52
|
+
.join("\n");
|
|
53
|
+
|
|
54
|
+
return `
|
|
55
|
+
export interface ${name} {
|
|
50
56
|
${fields}
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
function generateMappers(multiIr: MultiIR): string {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`obj.${camelCase(f.name)}`,
|
|
91
|
-
f.isOptional,
|
|
92
|
-
'toJson',
|
|
93
|
-
camelNs
|
|
94
|
-
)},`
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
lines.push(` })\n },`);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
lines.push(` },`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
lines.push(`};\n`);
|
|
105
|
-
return lines.join('\n');
|
|
62
|
+
const lines: string[] = [`export const Mappers: Record<string, any> = {`];
|
|
63
|
+
|
|
64
|
+
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
65
|
+
const camelNs = camelCase(ns);
|
|
66
|
+
lines.push(` ${camelNs}: {`);
|
|
67
|
+
|
|
68
|
+
const modelsList = Array.isArray(ir.models)
|
|
69
|
+
? ir.models
|
|
70
|
+
: Object.values(ir.models || {});
|
|
71
|
+
|
|
72
|
+
modelsList.forEach((model: any) => {
|
|
73
|
+
const name = pascalCase(model.name);
|
|
74
|
+
const fullType = `${camelNs}.${name}`;
|
|
75
|
+
|
|
76
|
+
lines.push(
|
|
77
|
+
` ${name}: {\n fromJson: (json: any): ${fullType} => ({`,
|
|
78
|
+
);
|
|
79
|
+
model.fields.forEach((f: any) => {
|
|
80
|
+
lines.push(
|
|
81
|
+
` ${camelCase(f.name)}: ${generateJsonLogic(f.typeRef, `json["${f.name}"]`, f.isOptional, "fromJson", camelNs)},`,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
lines.push(` }),\n toJson: (obj: any): any => ({`);
|
|
85
|
+
model.fields.forEach((f: any) => {
|
|
86
|
+
lines.push(
|
|
87
|
+
` "${f.name}": ${generateJsonLogic(f.typeRef, `obj.${camelCase(f.name)}`, f.isOptional, "toJson", camelNs)},`,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
lines.push(` })\n },`);
|
|
91
|
+
});
|
|
92
|
+
lines.push(` },`);
|
|
93
|
+
}
|
|
94
|
+
lines.push(`};\n`);
|
|
95
|
+
return lines.join("\n");
|
|
106
96
|
}
|
|
107
97
|
|
|
108
98
|
function generateJsonLogic(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
99
|
+
typeRef: any,
|
|
100
|
+
access: string,
|
|
101
|
+
isOpt: boolean,
|
|
102
|
+
mode: "fromJson" | "toJson",
|
|
103
|
+
ns: string,
|
|
114
104
|
): string {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (typeRef.kind === 'list') {
|
|
142
|
-
return wrap(
|
|
143
|
-
`${access}.map((e: any) => ${generateJsonLogic(typeRef.value, 'e', false, mode, ns)})`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return access;
|
|
148
|
-
}
|
|
105
|
+
const wrap = (logic: string) =>
|
|
106
|
+
isOpt ? `(${access} == null ? undefined : ${logic})` : logic;
|
|
107
|
+
if (!typeRef || !typeRef.kind) return access;
|
|
108
|
+
if (typeRef.kind === "dateTime")
|
|
109
|
+
return mode === "fromJson"
|
|
110
|
+
? wrap(`new Date(${access})`)
|
|
111
|
+
: wrap(`${access}.toISOString()`);
|
|
112
|
+
if (typeRef.kind === "bytes")
|
|
113
|
+
return mode === "fromJson"
|
|
114
|
+
? wrap(`new Uint8Array(${access})`)
|
|
115
|
+
: wrap(`Array.from(${access})`);
|
|
116
|
+
if (typeRef.kind === "named") {
|
|
117
|
+
const name = pascalCase(typeRef.value);
|
|
118
|
+
return wrap(
|
|
119
|
+
`(Mappers.${ns}["${name}"] ? Mappers.${ns}["${name}"].${mode}(${access}) : ${access})`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (typeRef.kind === "list")
|
|
123
|
+
return wrap(
|
|
124
|
+
`${access}.map((e: any) => ${generateJsonLogic(typeRef.value, "e", false, mode, ns)})`,
|
|
125
|
+
);
|
|
126
|
+
return access;
|
|
127
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FILE: atmx-cli/src/generators/sdk-generator.ts
|
|
2
|
-
import { AxiomEndpoint, MultiIR } from "../types";
|
|
2
|
+
import { AxiomEndpoint, AxiomParameter, MultiIR } from "../types";
|
|
3
3
|
import { pascalCase, camelCase, mapTypeToTs } from "./utils";
|
|
4
4
|
|
|
5
5
|
export function generateSdk(
|
|
@@ -8,6 +8,7 @@ export function generateSdk(
|
|
|
8
8
|
): string {
|
|
9
9
|
const lines: string[] = [
|
|
10
10
|
`// GENERATED CODE – DO NOT EDIT.`,
|
|
11
|
+
`/* eslint-disable @typescript-eslint/no-explicit-any */`,
|
|
11
12
|
`import * as models from './models';\n`,
|
|
12
13
|
];
|
|
13
14
|
|
|
@@ -17,8 +18,9 @@ export function generateSdk(
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
const camelNs = camelCase(ns);
|
|
22
|
+
// ✨ FIX: Use an object literal instead of a Class to support React Hooks!
|
|
23
|
+
lines.push(`export const ${camelNs}Module = {`);
|
|
22
24
|
|
|
23
25
|
const endpointsMap = ir.endpoints || {};
|
|
24
26
|
const endpoints = Array.isArray(endpointsMap)
|
|
@@ -26,18 +28,29 @@ export function generateSdk(
|
|
|
26
28
|
: Object.values(endpointsMap);
|
|
27
29
|
|
|
28
30
|
endpoints.forEach((ep: any) => {
|
|
29
|
-
lines.push(generateEndpointMethod(ep, ns,
|
|
31
|
+
lines.push(generateEndpointMethod(ep, ns, camelNs, isReact));
|
|
30
32
|
});
|
|
31
|
-
lines.push(`}
|
|
33
|
+
lines.push(`};\n`);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
// Generate main SDK object
|
|
37
|
+
lines.push(`export const sdk = {`);
|
|
35
38
|
for (const ns of Object.keys(multiIr)) {
|
|
36
|
-
lines.push(
|
|
37
|
-
` public readonly ${camelCase(ns)} = new ${pascalCase(ns)}Module();`,
|
|
38
|
-
);
|
|
39
|
+
lines.push(` ${camelCase(ns)}: ${camelCase(ns)}Module,`);
|
|
39
40
|
}
|
|
40
|
-
lines.push(`}
|
|
41
|
+
lines.push(`};\n`);
|
|
42
|
+
|
|
43
|
+
// Generate Config
|
|
44
|
+
lines.push(`export const AxiomDefaultConfig = {`);
|
|
45
|
+
lines.push(` contracts: {`);
|
|
46
|
+
for (const ns of Object.keys(multiIr)) {
|
|
47
|
+
lines.push(` "${ns}": {`);
|
|
48
|
+
lines.push(` contractUrl: "/${ns}.axiom",`);
|
|
49
|
+
lines.push(` baseUrl: "http://localhost:8000"`);
|
|
50
|
+
lines.push(` },`);
|
|
51
|
+
}
|
|
52
|
+
lines.push(` }`);
|
|
53
|
+
lines.push(`};\n`);
|
|
41
54
|
|
|
42
55
|
return lines.join("\n");
|
|
43
56
|
}
|
|
@@ -45,7 +58,7 @@ export function generateSdk(
|
|
|
45
58
|
function generateEndpointMethod(
|
|
46
59
|
ep: AxiomEndpoint,
|
|
47
60
|
ns: string,
|
|
48
|
-
|
|
61
|
+
camelNs: string,
|
|
49
62
|
isReact: boolean,
|
|
50
63
|
): string {
|
|
51
64
|
const rawParams = ep.parameters || [];
|
|
@@ -55,24 +68,26 @@ function generateEndpointMethod(
|
|
|
55
68
|
|
|
56
69
|
const argType =
|
|
57
70
|
params.length > 0
|
|
58
|
-
? `{ ${params.map((p: any) => `${camelCase(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels(mapTypeToTs(p.typeRef,
|
|
71
|
+
? `{ ${params.map((p: any) => `${camelCase(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels(mapTypeToTs(p.typeRef, camelNs))}`).join(", ")} }`
|
|
59
72
|
: "void";
|
|
60
73
|
|
|
61
74
|
const isQuery = ep.method ? ep.method.toUpperCase() === "GET" : true;
|
|
62
|
-
const rawReturnType = mapTypeToTs(ep.returnType,
|
|
75
|
+
const rawReturnType = mapTypeToTs(ep.returnType, camelNs);
|
|
63
76
|
const returnType =
|
|
64
77
|
rawReturnType === "void" || rawReturnType === "any"
|
|
65
78
|
? rawReturnType
|
|
66
79
|
: prefixModels(rawReturnType);
|
|
67
80
|
|
|
68
81
|
if (isReact) {
|
|
69
|
-
const bodyParam = params.find(
|
|
82
|
+
const bodyParam = params.find(
|
|
83
|
+
(p: any) => p.source === "body",
|
|
84
|
+
) as AxiomParameter;
|
|
70
85
|
const payloadLogic = bodyParam
|
|
71
86
|
? `const payload = (args as any)?.${camelCase(bodyParam.name)};`
|
|
72
87
|
: `const payload = undefined;`;
|
|
73
|
-
const decLogic = generateLambda(ep.returnType, "fromJson",
|
|
88
|
+
const decLogic = generateLambda(ep.returnType, "fromJson", camelNs);
|
|
74
89
|
const serLogic = bodyParam
|
|
75
|
-
? generateLambda(bodyParam.typeRef, "toJson",
|
|
90
|
+
? generateLambda(bodyParam.typeRef, "toJson", camelNs)
|
|
76
91
|
: `(p: any) => p`;
|
|
77
92
|
|
|
78
93
|
return `
|
|
@@ -90,7 +105,7 @@ function generateEndpointMethod(
|
|
|
90
105
|
serializer: ${serLogic},
|
|
91
106
|
isStream: ${ep.isStream === true}
|
|
92
107
|
};
|
|
93
|
-
}
|
|
108
|
+
},
|
|
94
109
|
|
|
95
110
|
use${pascalCase(ep.name)}${!isQuery ? "Mutation" : ""}(${isQuery ? `args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}, options?: { enabled?: boolean }` : ""}) {
|
|
96
111
|
${
|
|
@@ -98,13 +113,13 @@ function generateEndpointMethod(
|
|
|
98
113
|
? `return useAxiomQuery<${returnType}>(this.get${pascalCase(ep.name)}Def(args), options);`
|
|
99
114
|
: `return useAxiomMutation<${returnType}, ${argType === "void" ? "void | Record<string,any>" : argType}>((args) => this.get${pascalCase(ep.name)}Def(args));`
|
|
100
115
|
}
|
|
101
|
-
}
|
|
116
|
+
},`;
|
|
102
117
|
} else {
|
|
103
118
|
return `
|
|
104
119
|
${camelCase(ep.name)}(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): string {
|
|
105
120
|
const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';
|
|
106
121
|
return \`${ns}.${ep.name}(\${argsStr})\`;
|
|
107
|
-
}
|
|
122
|
+
},`;
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
|
|
@@ -116,9 +131,9 @@ function generateLambda(
|
|
|
116
131
|
if (!typeRef || !typeRef.kind || typeRef.kind === "void")
|
|
117
132
|
return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
|
|
118
133
|
if (typeRef.kind === "list" && typeRef.value?.kind === "named")
|
|
119
|
-
return `(data: any[]) => data.map(models.Mappers.${
|
|
134
|
+
return `(data: any[]) => data.map(models.Mappers.${ns}.${pascalCase(typeRef.value.value)}.${mode})`;
|
|
120
135
|
if (typeRef.kind === "named")
|
|
121
|
-
return `models.Mappers.${
|
|
136
|
+
return `models.Mappers.${ns}.${pascalCase(typeRef.value)}.${mode}`;
|
|
122
137
|
return `(data: any) => data`;
|
|
123
138
|
}
|
|
124
139
|
|
package/src/generators/utils.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// atmx-cli/src/generators/utils.ts
|
|
2
|
-
|
|
1
|
+
// FILE: atmx-cli/src/generators/utils.ts
|
|
3
2
|
export function pascalCase(str: string): string {
|
|
4
3
|
if (!str) return "";
|
|
5
4
|
return str
|
|
@@ -14,19 +13,13 @@ export function camelCase(str: string): string {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export function normalizeIr(obj: any): any {
|
|
17
|
-
|
|
18
|
-
if (Array.isArray(obj)) {
|
|
19
|
-
return obj.map(normalizeIr);
|
|
20
|
-
}
|
|
21
|
-
|
|
16
|
+
if (Array.isArray(obj)) return obj.map(normalizeIr);
|
|
22
17
|
if (obj !== null && typeof obj === "object") {
|
|
23
18
|
const newObj: any = {};
|
|
24
19
|
for (const key of Object.keys(obj)) {
|
|
25
20
|
const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
26
21
|
newObj[camelKey] = normalizeIr(obj[key]);
|
|
27
22
|
}
|
|
28
|
-
|
|
29
|
-
// Convert common Maps to Arrays if they exist and are not already arrays
|
|
30
23
|
if (
|
|
31
24
|
newObj.endpoints &&
|
|
32
25
|
typeof newObj.endpoints === "object" &&
|
|
@@ -48,8 +41,6 @@ export function normalizeIr(obj: any): any {
|
|
|
48
41
|
) {
|
|
49
42
|
newObj.enums = Object.values(newObj.enums);
|
|
50
43
|
}
|
|
51
|
-
|
|
52
|
-
// Traverse down into models to normalize fields
|
|
53
44
|
if (Array.isArray(newObj.models)) {
|
|
54
45
|
newObj.models = newObj.models.map((model: any) => {
|
|
55
46
|
if (
|
|
@@ -62,46 +53,44 @@ export function normalizeIr(obj: any): any {
|
|
|
62
53
|
return model;
|
|
63
54
|
});
|
|
64
55
|
}
|
|
65
|
-
|
|
66
56
|
return newObj;
|
|
67
57
|
}
|
|
68
|
-
|
|
69
58
|
return obj;
|
|
70
59
|
}
|
|
71
60
|
|
|
72
|
-
|
|
73
|
-
* @param scopedNamespace If provided (e.g. 'Auth'), prefixes named types with 'models.Auth.'
|
|
74
|
-
*/
|
|
61
|
+
// ✨ FIX: Properly maps all Axiom primitives to TypeScript
|
|
75
62
|
export function mapTypeToTs(typeRef: any, ns?: string): string {
|
|
76
|
-
if (!typeRef) return "any";
|
|
63
|
+
if (!typeRef || !typeRef.kind) return "any";
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
65
|
+
switch (typeRef.kind) {
|
|
66
|
+
case "string":
|
|
67
|
+
return "string";
|
|
68
|
+
case "int32":
|
|
69
|
+
case "int64":
|
|
70
|
+
case "float32":
|
|
71
|
+
case "float64":
|
|
72
|
+
return "number";
|
|
73
|
+
case "bool":
|
|
74
|
+
return "boolean";
|
|
75
|
+
case "dateTime":
|
|
76
|
+
return "Date";
|
|
77
|
+
case "bytes":
|
|
78
|
+
return "Uint8Array";
|
|
79
|
+
case "json":
|
|
80
|
+
return "any";
|
|
81
|
+
case "void":
|
|
82
|
+
return "void";
|
|
83
|
+
case "named":
|
|
84
|
+
const name = pascalCase(typeRef.value);
|
|
85
|
+
return ns ? `${ns}.${name}` : name;
|
|
86
|
+
case "list":
|
|
87
|
+
return `${mapTypeToTs(typeRef.value, ns)}[]`;
|
|
88
|
+
case "map":
|
|
89
|
+
const valType = typeRef.value?.[1]
|
|
90
|
+
? mapTypeToTs(typeRef.value[1], ns)
|
|
91
|
+
: "any";
|
|
92
|
+
return `Record<string, ${valType}>`;
|
|
93
|
+
default:
|
|
94
|
+
return "any";
|
|
100
95
|
}
|
|
101
|
-
|
|
102
|
-
if (typeRef.kind === "list") {
|
|
103
|
-
return `${mapTypeToTs(typeRef.value, ns)}[]`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return "any";
|
|
107
96
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import * as fs from "fs-extra";
|
|
4
4
|
import * as path from "path";
|
|
5
|
-
import
|
|
5
|
+
import * as toml from "@iarna/toml";
|
|
6
|
+
import { MultiIR } from "./types";
|
|
6
7
|
import { generateModels } from "./generators/model-generator";
|
|
7
8
|
import { generateSdk } from "./generators/sdk-generator";
|
|
8
9
|
import { normalizeIr } from "./generators/utils";
|
|
@@ -11,14 +12,14 @@ const program = new Command();
|
|
|
11
12
|
|
|
12
13
|
program
|
|
13
14
|
.name("atmx")
|
|
14
|
-
.description("Generate TypeScript SDK from
|
|
15
|
+
.description("Generate TypeScript SDK from AxiomDeps.toml")
|
|
15
16
|
.version("0.2.0");
|
|
16
17
|
|
|
17
18
|
program
|
|
18
19
|
.command("generate")
|
|
19
|
-
.requiredOption("-c, --config <path>", "Path to
|
|
20
|
+
.requiredOption("-c, --config <path>", "Path to AxiomDeps.toml")
|
|
20
21
|
.requiredOption("-o, --output <dir>", "Output directory for generated files")
|
|
21
|
-
.option("-r, --react", "Generate React Hooks instead of Vanilla JS strings")
|
|
22
|
+
.option("-r, --react", "Generate React Hooks instead of Vanilla JS strings")
|
|
22
23
|
.action(async (options) => {
|
|
23
24
|
const configPath = path.resolve(options.config);
|
|
24
25
|
const outputDir = path.resolve(options.output);
|
|
@@ -28,61 +29,45 @@ program
|
|
|
28
29
|
process.exit(1);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
// 1. Read
|
|
32
|
-
const
|
|
32
|
+
// 1. Read and Parse TOML
|
|
33
|
+
const tomlString = await fs.readFile(configPath, "utf-8");
|
|
34
|
+
const rawConfig = toml.parse(tomlString) as any;
|
|
33
35
|
|
|
34
36
|
if (!rawConfig.contracts || Object.keys(rawConfig.contracts).length === 0) {
|
|
35
|
-
console.error(
|
|
36
|
-
"❌ Error: Invalid config file. Missing 'contracts' dictionary.",
|
|
37
|
-
);
|
|
37
|
+
console.error("❌ Error: No contracts defined in AxiomDeps.toml.");
|
|
38
38
|
process.exit(1);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const configDir = path.dirname(configPath);
|
|
42
41
|
const multiIr: MultiIR = {};
|
|
42
|
+
const projectRoot = path.dirname(configPath); // Frontend project root
|
|
43
43
|
|
|
44
|
-
// 2. Loop through
|
|
44
|
+
// 2. Loop through contracts
|
|
45
45
|
for (const [namespace, contract] of Object.entries(rawConfig.contracts)) {
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Rust CLI safely copies files to `public/[namespace].axiom`
|
|
47
|
+
const axiomFilePath = path.resolve(
|
|
48
|
+
projectRoot,
|
|
49
|
+
`public/${namespace}.axiom`,
|
|
50
|
+
);
|
|
49
51
|
|
|
50
52
|
if (!fs.existsSync(axiomFilePath)) {
|
|
51
53
|
console.warn(
|
|
52
|
-
`⚠️ Warning: Contract file not found
|
|
54
|
+
`⚠️ Warning: Contract file not found at ${axiomFilePath}. Skipping...`,
|
|
53
55
|
);
|
|
54
56
|
continue;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const rawFile = await fs.readJSON(axiomFilePath);
|
|
60
|
+
if (!rawFile.ir) continue;
|
|
58
61
|
|
|
59
|
-
if (!rawFile.ir) {
|
|
60
|
-
console.warn(
|
|
61
|
-
`⚠️ Warning: Invalid .axiom file for namespace '${namespace}'. Missing 'ir' property. Skipping...`,
|
|
62
|
-
);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Normalize the IR (snake_case -> camelCase) and store it by namespace
|
|
67
62
|
multiIr[namespace] = normalizeIr(rawFile.ir);
|
|
68
|
-
console.log(`✅ Loaded contract: [${namespace}] -> ${
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (Object.keys(multiIr).length === 0) {
|
|
72
|
-
console.error(
|
|
73
|
-
"❌ Error: No valid contracts were loaded. Aborting generation.",
|
|
74
|
-
);
|
|
75
|
-
process.exit(1);
|
|
63
|
+
console.log(`✅ Loaded contract: [${namespace}] -> ${axiomFilePath}`);
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
// Ensure output directory exists
|
|
79
66
|
await fs.ensureDir(outputDir);
|
|
80
67
|
|
|
81
|
-
// 3. Pass the MultiIR Map to the generators
|
|
82
68
|
const modelsContent = generateModels(multiIr);
|
|
83
69
|
await fs.writeFile(path.join(outputDir, "models.ts"), modelsContent);
|
|
84
70
|
|
|
85
|
-
// ✨ NEW: Pass the react flag down
|
|
86
71
|
const sdkContent = generateSdk(multiIr, options.react);
|
|
87
72
|
await fs.writeFile(path.join(outputDir, "sdk.ts"), sdkContent);
|
|
88
73
|
|