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.
@@ -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 pascalNs = (0, utils_1.pascalCase)(ns);
12
- const enumsList = Array.isArray(ir.enums) ? ir.enums : Object.values(ir.enums || {});
13
- const modelsList = Array.isArray(ir.models) ? ir.models : Object.values(ir.models || {});
14
- enumsList.forEach((en) => sections.push(generateEnum(en, pascalNs)));
15
- modelsList.forEach((model) => sections.push(generateInterface(model, pascalNs)));
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('\n');
24
+ return sections.join("\n");
20
25
  }
21
- function generateEnum(en, ns) {
22
- const name = `${ns}${(0, utils_1.pascalCase)(en.name)}`;
23
- const values = en.values.map(v => ` ${(0, utils_1.pascalCase)(v)}: "${v}"`).join(',\n');
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
- export type ${name} = typeof ${name}[keyof typeof ${name}];
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 = `${ns}${(0, utils_1.pascalCase)(model.name)}`;
34
- const fields = model.fields.map(f => {
35
- const type = (0, utils_1.mapTypeToTs)(f.typeRef, ns); // ✅ pass namespace
36
- return ` ${(0, utils_1.camelCase)(f.name)}${f.isOptional ? '?' : ''}: ${type};`;
37
- }).join('\n');
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) ? ir.models : Object.values(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 = `${pascalNs}${name}`; // no namespace now
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, 'fromJson', camelNs)},`);
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, 'toJson', camelNs)},`);
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('\n');
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 === 'primitive') {
74
- if (typeRef.value === 'dateTime') {
75
- return mode === 'fromJson'
76
- ? wrap(`new Date(${access})`)
77
- : wrap(`${access}.toISOString()`);
78
- }
79
- if (typeRef.value === 'bytes') {
80
- return mode === 'fromJson'
81
- ? wrap(`new Uint8Array(${access})`)
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 === 'list') {
91
- return wrap(`${access}.map((e: any) => ${generateJsonLogic(typeRef.value, 'e', false, mode, ns)})`);
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 pascalNs = (0, utils_1.pascalCase)(ns);
16
- lines.push(`export class ${pascalNs}Module {`);
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, pascalNs, isReact));
24
+ lines.push(generateEndpointMethod(ep, ns, camelNs, isReact));
23
25
  });
24
- lines.push(`}\n`);
26
+ lines.push(`};\n`);
25
27
  }
26
- lines.push(`export class AxiomSdk {`);
28
+ // Generate main SDK object
29
+ lines.push(`export const sdk = {`);
27
30
  for (const ns of Object.keys(multiIr)) {
28
- lines.push(` public readonly ${(0, utils_1.camelCase)(ns)} = new ${(0, utils_1.pascalCase)(ns)}Module();`);
31
+ lines.push(` ${(0, utils_1.camelCase)(ns)}: ${(0, utils_1.camelCase)(ns)}Module,`);
29
32
  }
30
- lines.push(`}\nexport const sdk = new AxiomSdk();`);
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, pascalNs, isReact) {
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, pascalNs))}`).join(", ")} }`
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, pascalNs);
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", pascalNs);
65
+ const decLogic = generateLambda(ep.returnType, "fromJson", camelNs);
52
66
  const serLogic = bodyParam
53
- ? generateLambda(bodyParam.typeRef, "toJson", pascalNs)
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
- }\n`;
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
- }\n`;
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.${(0, utils_1.camelCase)(ns)}.${(0, utils_1.pascalCase)(typeRef.value.value)}.${mode})`;
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.${(0, utils_1.camelCase)(ns)}.${(0, utils_1.pascalCase)(typeRef.value)}.${mode}`;
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) {
@@ -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
- // ✨ FIX: Arrays in JavaScript are Objects! We must check Array.isArray FIRST.
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
- if (typeRef.kind === "primitive") {
69
- switch (typeRef.value) {
70
- case "string":
71
- return "string";
72
- case "int":
73
- case "float":
74
- case "double":
75
- return "number";
76
- case "boolean":
77
- return "boolean";
78
- case "dateTime":
79
- return "Date";
80
- case "bytes":
81
- return "Uint8Array";
82
- default:
83
- return "any";
84
- }
85
- }
86
- if (typeRef.kind === "named") {
87
- const name = pascalCase(typeRef.value);
88
- return ns ? `${ns}${name}` : name;
89
- }
90
- if (typeRef.kind === "list") {
91
- return `${mapTypeToTs(typeRef.value, ns)}[]`;
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 an ATMX Multi-Contract Config")
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 the atmx.config.json file")
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") // ✨ NEW
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 the Config File
61
- const rawConfig = await fs.readJSON(configPath);
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: Invalid config file. Missing 'contracts' dictionary.");
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
- // 2. Loop through the contracts and parse the local .axiom files
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
- // Resolve the .axiom file path relative to where the config file is located
71
- // (e.g., if config is in /public, and file is "./auth.axiom", it looks in /public/auth.axiom)
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 for namespace '${namespace}' at ${axiomFilePath}. Skipping...`);
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}] -> ${contract.file}`);
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.47.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 '../types';
3
- import { pascalCase, camelCase, mapTypeToTs } from './utils';
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
- const sections: string[] = [
7
- `// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n`
8
- ];
9
-
10
- // 1. Generate flat exports (NO namespaces)
11
- for (const [ns, ir] of Object.entries(multiIr)) {
12
- const pascalNs = pascalCase(ns);
13
-
14
- const enumsList = Array.isArray(ir.enums) ? ir.enums : Object.values(ir.enums || {});
15
- const modelsList = Array.isArray(ir.models) ? ir.models : Object.values(ir.models || {});
16
-
17
- enumsList.forEach((en: any) => sections.push(generateEnum(en, pascalNs)));
18
- modelsList.forEach((model: any) => sections.push(generateInterface(model, pascalNs)));
19
- }
20
-
21
- // 2. Generate nested Mappers object
22
- sections.push(generateMappers(multiIr));
23
-
24
- return sections.join('\n');
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, ns: string): string {
28
- const name = `${ns}${pascalCase(en.name)}`;
29
- const values = en.values.map(v => ` ${pascalCase(v)}: "${v}"`).join(',\n');
30
-
31
- return `
32
- export const ${name} = {
33
- ${values}
34
- } as const;
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
- const name = `${ns}${pascalCase(model.name)}`;
42
-
43
- const fields = model.fields.map(f => {
44
- const type = mapTypeToTs(f.typeRef, ns); // ✅ pass namespace
45
- return ` ${camelCase(f.name)}${f.isOptional ? '?' : ''}: ${type};`;
46
- }).join('\n');
47
-
48
- return `
49
- export interface ${name} {
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
- const lines: string[] = [`export const Mappers: Record<string, any> = {`];
57
-
58
- for (const [ns, ir] of Object.entries(multiIr)) {
59
- const camelNs = camelCase(ns);
60
- const pascalNs = pascalCase(ns);
61
-
62
- lines.push(` ${camelNs}: {`);
63
-
64
- const modelsList = Array.isArray(ir.models) ? ir.models : Object.values(ir.models || {});
65
-
66
- modelsList.forEach((model: any) => {
67
- const name = pascalCase(model.name);
68
- const fullType = `${pascalNs}${name}`; // no namespace now
69
-
70
- lines.push(` ${name}: {\n fromJson: (json: any): ${fullType} => ({`);
71
-
72
- model.fields.forEach((f: any) => {
73
- lines.push(
74
- ` ${camelCase(f.name)}: ${generateJsonLogic(
75
- f.typeRef,
76
- `json["${f.name}"]`,
77
- f.isOptional,
78
- 'fromJson',
79
- camelNs
80
- )},`
81
- );
82
- });
83
-
84
- lines.push(` }),\n toJson: (obj: any): any => ({`);
85
-
86
- model.fields.forEach((f: any) => {
87
- lines.push(
88
- ` "${f.name}": ${generateJsonLogic(
89
- f.typeRef,
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
- typeRef: any,
110
- access: string,
111
- isOpt: boolean,
112
- mode: 'fromJson' | 'toJson',
113
- ns: string
99
+ typeRef: any,
100
+ access: string,
101
+ isOpt: boolean,
102
+ mode: "fromJson" | "toJson",
103
+ ns: string,
114
104
  ): string {
115
- const wrap = (logic: string) =>
116
- isOpt ? `(${access} == null ? undefined : ${logic})` : logic;
117
-
118
- if (!typeRef || !typeRef.kind) return access;
119
-
120
- if (typeRef.kind === 'primitive') {
121
- if (typeRef.value === 'dateTime') {
122
- return mode === 'fromJson'
123
- ? wrap(`new Date(${access})`)
124
- : wrap(`${access}.toISOString()`);
125
- }
126
- if (typeRef.value === 'bytes') {
127
- return mode === 'fromJson'
128
- ? wrap(`new Uint8Array(${access})`)
129
- : wrap(`Array.from(${access})`);
130
- }
131
- return access;
132
- }
133
-
134
- if (typeRef.kind === 'named') {
135
- const name = pascalCase(typeRef.value);
136
- return wrap(
137
- `(Mappers.${ns}["${name}"] ? Mappers.${ns}["${name}"].${mode}(${access}) : ${access})`
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 pascalNs = pascalCase(ns);
21
- lines.push(`export class ${pascalNs}Module {`);
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, pascalNs, isReact));
31
+ lines.push(generateEndpointMethod(ep, ns, camelNs, isReact));
30
32
  });
31
- lines.push(`}\n`);
33
+ lines.push(`};\n`);
32
34
  }
33
35
 
34
- lines.push(`export class AxiomSdk {`);
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(`}\nexport const sdk = new AxiomSdk();`);
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
- pascalNs: string,
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, pascalNs))}`).join(", ")} }`
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, pascalNs);
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((p: any) => p.source === "body") as any;
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", pascalNs);
88
+ const decLogic = generateLambda(ep.returnType, "fromJson", camelNs);
74
89
  const serLogic = bodyParam
75
- ? generateLambda(bodyParam.typeRef, "toJson", pascalNs)
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
- }\n`;
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
- }\n`;
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.${camelCase(ns)}.${pascalCase(typeRef.value.value)}.${mode})`;
134
+ return `(data: any[]) => data.map(models.Mappers.${ns}.${pascalCase(typeRef.value.value)}.${mode})`;
120
135
  if (typeRef.kind === "named")
121
- return `models.Mappers.${camelCase(ns)}.${pascalCase(typeRef.value)}.${mode}`;
136
+ return `models.Mappers.${ns}.${pascalCase(typeRef.value)}.${mode}`;
122
137
  return `(data: any) => data`;
123
138
  }
124
139
 
@@ -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
- // ✨ FIX: Arrays in JavaScript are Objects! We must check Array.isArray FIRST.
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
- if (typeRef.kind === "primitive") {
79
- switch (typeRef.value) {
80
- case "string":
81
- return "string";
82
- case "int":
83
- case "float":
84
- case "double":
85
- return "number";
86
- case "boolean":
87
- return "boolean";
88
- case "dateTime":
89
- return "Date";
90
- case "bytes":
91
- return "Uint8Array";
92
- default:
93
- return "any";
94
- }
95
- }
96
-
97
- if (typeRef.kind === "named") {
98
- const name = pascalCase(typeRef.value);
99
- return ns ? `${ns}${name}` : name;
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 { AtmxMultiConfig, MultiIR } from "./types";
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 an ATMX Multi-Contract Config")
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 the atmx.config.json file")
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") // ✨ NEW
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 the Config File
32
- const rawConfig: AtmxMultiConfig = await fs.readJSON(configPath);
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 the contracts and parse the local .axiom files
44
+ // 2. Loop through contracts
45
45
  for (const [namespace, contract] of Object.entries(rawConfig.contracts)) {
46
- // Resolve the .axiom file path relative to where the config file is located
47
- // (e.g., if config is in /public, and file is "./auth.axiom", it looks in /public/auth.axiom)
48
- const axiomFilePath = path.resolve(configDir, contract.file);
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 for namespace '${namespace}' at ${axiomFilePath}. Skipping...`,
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}] -> ${contract.file}`);
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