atmx-cli 0.39.0 → 0.41.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.
@@ -2,21 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateSdk = generateSdk;
4
4
  const utils_1 = require("./utils");
5
- function generateSdk(multiIr) {
5
+ function generateSdk(multiIr, isReact = false) {
6
6
  const lines = [
7
7
  `// GENERATED CODE – DO NOT EDIT.`,
8
8
  `import * as models from './models';\n`,
9
9
  ];
10
+ if (isReact) {
11
+ lines.push(`import { useAxiomQuery, useAxiomMutation } from 'atmx-react';`);
12
+ lines.push(`import type { AxiomQueryDef } from 'atmx-react';\n`);
13
+ }
10
14
  for (const [ns, ir] of Object.entries(multiIr)) {
11
15
  const pascalNs = (0, utils_1.pascalCase)(ns);
12
16
  lines.push(`export class ${pascalNs}Module {`);
13
- // Support both old array format and new object map format
14
17
  const endpointsMap = ir.endpoints || {};
15
18
  const endpoints = Array.isArray(endpointsMap)
16
19
  ? endpointsMap
17
20
  : Object.values(endpointsMap);
18
21
  endpoints.forEach((ep) => {
19
- lines.push(generateEndpointMethod(ep, ns, pascalNs));
22
+ lines.push(generateEndpointMethod(ep, ns, pascalNs, isReact));
20
23
  });
21
24
  lines.push(`}\n`);
22
25
  }
@@ -27,30 +30,67 @@ function generateSdk(multiIr) {
27
30
  lines.push(`}\nexport const sdk = new AxiomSdk();`);
28
31
  return lines.join("\n");
29
32
  }
30
- function generateEndpointMethod(ep, ns, pascalNs) {
31
- // ✨ FIX: Ensure params is always an Array regardless of the IR structure
33
+ function generateEndpointMethod(ep, ns, pascalNs, isReact) {
32
34
  const rawParams = ep.parameters || [];
33
35
  const params = Array.isArray(rawParams)
34
36
  ? rawParams
35
37
  : Object.values(rawParams);
36
- if (params.length === 0) {
38
+ 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(", ")} }`
40
+ : "void";
41
+ const isQuery = ep.method ? ep.method.toUpperCase() === "GET" : true;
42
+ const rawReturnType = (0, utils_1.mapTypeToTs)(ep.returnType, pascalNs);
43
+ const returnType = rawReturnType === "void" || rawReturnType === "any"
44
+ ? rawReturnType
45
+ : prefixModels(rawReturnType);
46
+ if (isReact) {
47
+ const bodyParam = params.find((p) => p.source === "body");
48
+ const payloadLogic = bodyParam
49
+ ? `const payload = (args as any)?.${(0, utils_1.camelCase)(bodyParam.name)};`
50
+ : `const payload = undefined;`;
51
+ const decLogic = generateLambda(ep.returnType, "fromJson", pascalNs);
52
+ const serLogic = bodyParam
53
+ ? generateLambda(bodyParam.typeRef, "toJson", pascalNs)
54
+ : `(p: any) => p`;
37
55
  return `
38
- /** RPC String Generator for <AxQuery> or <AxMutate> */
39
- ${(0, utils_1.camelCase)(ep.name)}(): string {
40
- return \`${ns}.${ep.name}()\`;
56
+ get${(0, utils_1.pascalCase)(ep.name)}Def(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): AxiomQueryDef<${returnType}> {
57
+ ${payloadLogic}
58
+ return {
59
+ namespace: "${ns}",
60
+ name: "${ep.name}",
61
+ endpointId: ${ep.id},
62
+ method: "${ep.method ? ep.method.toUpperCase() : "GET"}",
63
+ path: "${ep.path}",
64
+ payload: payload,
65
+ args: args || {},
66
+ decoder: ${decLogic},
67
+ serializer: ${serLogic},
68
+ isStream: ${ep.isStream === true}
69
+ };
70
+ }
71
+
72
+ use${(0, utils_1.pascalCase)(ep.name)}${!isQuery ? "Mutation" : ""}(${isQuery ? `args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}, options?: { enabled?: boolean }` : ""}) {
73
+ ${isQuery
74
+ ? `return useAxiomQuery<${returnType}>(this.get${(0, utils_1.pascalCase)(ep.name)}Def(args), options);`
75
+ : `return useAxiomMutation<${returnType}, ${argType === "void" ? "void | Record<string,any>" : argType}>((args) => this.get${(0, utils_1.pascalCase)(ep.name)}Def(args));`}
41
76
  }\n`;
42
77
  }
43
- const argType = `{ ${params
44
- .map((p) => {
45
- return `${(0, utils_1.camelCase)(p.name)}?: ${prefixModels((0, utils_1.mapTypeToTs)(p.typeRef, pascalNs))}`;
46
- })
47
- .join(", ")} }`;
48
- return `
49
- /** RPC String Generator for <AxQuery> or <AxMutate> */
50
- ${(0, utils_1.camelCase)(ep.name)}(args?: ${argType}): string {
78
+ else {
79
+ return `
80
+ ${(0, utils_1.camelCase)(ep.name)}(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): string {
51
81
  const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';
52
82
  return \`${ns}.${ep.name}(\${argsStr})\`;
53
83
  }\n`;
84
+ }
85
+ }
86
+ function generateLambda(typeRef, mode, ns) {
87
+ if (!typeRef || !typeRef.kind || typeRef.kind === "void")
88
+ return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
89
+ 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})`;
91
+ if (typeRef.kind === "named")
92
+ return `models.Mappers.${(0, utils_1.camelCase)(ns)}.${(0, utils_1.pascalCase)(typeRef.value)}.${mode}`;
93
+ return `(data: any) => data`;
54
94
  }
55
95
  function prefixModels(type) {
56
96
  const primitives = [
package/dist/index.js CHANGED
@@ -42,13 +42,14 @@ const sdk_generator_1 = require("./generators/sdk-generator");
42
42
  const utils_1 = require("./generators/utils");
43
43
  const program = new commander_1.Command();
44
44
  program
45
- .name('atmx')
46
- .description('Generate TypeScript SDK from an ATMX Multi-Contract Config')
47
- .version('0.2.0');
45
+ .name("atmx")
46
+ .description("Generate TypeScript SDK from an ATMX Multi-Contract Config")
47
+ .version("0.2.0");
48
48
  program
49
- .command('generate')
50
- .requiredOption('-c, --config <path>', 'Path to the atmx.config.json file')
51
- .requiredOption('-o, --output <dir>', 'Output directory for generated files')
49
+ .command("generate")
50
+ .requiredOption("-c, --config <path>", "Path to the atmx.config.json file")
51
+ .requiredOption("-o, --output <dir>", "Output directory for generated files")
52
+ .option("-r, --react", "Generate React Hooks instead of Vanilla JS strings") // ✨ NEW
52
53
  .action(async (options) => {
53
54
  const configPath = path.resolve(options.config);
54
55
  const outputDir = path.resolve(options.output);
@@ -88,11 +89,12 @@ program
88
89
  }
89
90
  // Ensure output directory exists
90
91
  await fs.ensureDir(outputDir);
91
- // 3. Pass the MultiIR Map to the generators (We will update these in Phase 3)
92
+ // 3. Pass the MultiIR Map to the generators
92
93
  const modelsContent = (0, model_generator_1.generateModels)(multiIr);
93
- await fs.writeFile(path.join(outputDir, 'models.ts'), modelsContent);
94
- const sdkContent = (0, sdk_generator_1.generateSdk)(multiIr);
95
- await fs.writeFile(path.join(outputDir, 'sdk.ts'), sdkContent);
94
+ await fs.writeFile(path.join(outputDir, "models.ts"), modelsContent);
95
+ // NEW: Pass the react flag down
96
+ const sdkContent = (0, sdk_generator_1.generateSdk)(multiIr, options.react);
97
+ await fs.writeFile(path.join(outputDir, "sdk.ts"), sdkContent);
96
98
  console.log(`\n🎉 ATMX Multi-Contract SDK generated successfully in ${outputDir}`);
97
99
  });
98
100
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atmx-cli",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -2,24 +2,31 @@
2
2
  import { AxiomEndpoint, MultiIR } from "../types";
3
3
  import { pascalCase, camelCase, mapTypeToTs } from "./utils";
4
4
 
5
- export function generateSdk(multiIr: MultiIR): string {
5
+ export function generateSdk(
6
+ multiIr: MultiIR,
7
+ isReact: boolean = false,
8
+ ): string {
6
9
  const lines: string[] = [
7
10
  `// GENERATED CODE – DO NOT EDIT.`,
8
11
  `import * as models from './models';\n`,
9
12
  ];
10
13
 
14
+ if (isReact) {
15
+ lines.push(`import { useAxiomQuery, useAxiomMutation } from 'atmx-react';`);
16
+ lines.push(`import type { AxiomQueryDef } from 'atmx-react';\n`);
17
+ }
18
+
11
19
  for (const [ns, ir] of Object.entries(multiIr)) {
12
20
  const pascalNs = pascalCase(ns);
13
21
  lines.push(`export class ${pascalNs}Module {`);
14
22
 
15
- // Support both old array format and new object map format
16
23
  const endpointsMap = ir.endpoints || {};
17
24
  const endpoints = Array.isArray(endpointsMap)
18
25
  ? endpointsMap
19
26
  : Object.values(endpointsMap);
20
27
 
21
28
  endpoints.forEach((ep: any) => {
22
- lines.push(generateEndpointMethod(ep, ns, pascalNs));
29
+ lines.push(generateEndpointMethod(ep, ns, pascalNs, isReact));
23
30
  });
24
31
  lines.push(`}\n`);
25
32
  }
@@ -39,33 +46,80 @@ function generateEndpointMethod(
39
46
  ep: AxiomEndpoint,
40
47
  ns: string,
41
48
  pascalNs: string,
49
+ isReact: boolean,
42
50
  ): string {
43
- // ✨ FIX: Ensure params is always an Array regardless of the IR structure
44
51
  const rawParams = ep.parameters || [];
45
52
  const params = Array.isArray(rawParams)
46
53
  ? rawParams
47
54
  : Object.values(rawParams);
48
55
 
49
- if (params.length === 0) {
56
+ const argType =
57
+ params.length > 0
58
+ ? `{ ${params.map((p: any) => `${camelCase(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels(mapTypeToTs(p.typeRef, pascalNs))}`).join(", ")} }`
59
+ : "void";
60
+
61
+ const isQuery = ep.method ? ep.method.toUpperCase() === "GET" : true;
62
+ const rawReturnType = mapTypeToTs(ep.returnType, pascalNs);
63
+ const returnType =
64
+ rawReturnType === "void" || rawReturnType === "any"
65
+ ? rawReturnType
66
+ : prefixModels(rawReturnType);
67
+
68
+ if (isReact) {
69
+ const bodyParam = params.find((p: any) => p.source === "body") as any;
70
+ const payloadLogic = bodyParam
71
+ ? `const payload = (args as any)?.${camelCase(bodyParam.name)};`
72
+ : `const payload = undefined;`;
73
+ const decLogic = generateLambda(ep.returnType, "fromJson", pascalNs);
74
+ const serLogic = bodyParam
75
+ ? generateLambda(bodyParam.typeRef, "toJson", pascalNs)
76
+ : `(p: any) => p`;
77
+
50
78
  return `
51
- /** RPC String Generator for <AxQuery> or <AxMutate> */
52
- ${camelCase(ep.name)}(): string {
53
- return \`${ns}.${ep.name}()\`;
54
- }\n`;
79
+ get${pascalCase(ep.name)}Def(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): AxiomQueryDef<${returnType}> {
80
+ ${payloadLogic}
81
+ return {
82
+ namespace: "${ns}",
83
+ name: "${ep.name}",
84
+ endpointId: ${ep.id},
85
+ method: "${ep.method ? ep.method.toUpperCase() : "GET"}",
86
+ path: "${ep.path}",
87
+ payload: payload,
88
+ args: args || {},
89
+ decoder: ${decLogic},
90
+ serializer: ${serLogic},
91
+ isStream: ${ep.isStream === true}
92
+ };
55
93
  }
56
94
 
57
- const argType = `{ ${params
58
- .map((p: any) => {
59
- return `${camelCase(p.name)}?: ${prefixModels(mapTypeToTs(p.typeRef, pascalNs))}`;
60
- })
61
- .join(", ")} }`;
62
-
63
- return `
64
- /** RPC String Generator for <AxQuery> or <AxMutate> */
65
- ${camelCase(ep.name)}(args?: ${argType}): string {
95
+ use${pascalCase(ep.name)}${!isQuery ? "Mutation" : ""}(${isQuery ? `args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}, options?: { enabled?: boolean }` : ""}) {
96
+ ${
97
+ isQuery
98
+ ? `return useAxiomQuery<${returnType}>(this.get${pascalCase(ep.name)}Def(args), options);`
99
+ : `return useAxiomMutation<${returnType}, ${argType === "void" ? "void | Record<string,any>" : argType}>((args) => this.get${pascalCase(ep.name)}Def(args));`
100
+ }
101
+ }\n`;
102
+ } else {
103
+ return `
104
+ ${camelCase(ep.name)}(args${params.length > 0 ? "?" : ""}: ${argType === "void" ? "any" : argType}): string {
66
105
  const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';
67
106
  return \`${ns}.${ep.name}(\${argsStr})\`;
68
107
  }\n`;
108
+ }
109
+ }
110
+
111
+ function generateLambda(
112
+ typeRef: any,
113
+ mode: "fromJson" | "toJson",
114
+ ns: string,
115
+ ): string {
116
+ if (!typeRef || !typeRef.kind || typeRef.kind === "void")
117
+ return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
118
+ if (typeRef.kind === "list" && typeRef.value?.kind === "named")
119
+ return `(data: any[]) => data.map(models.Mappers.${camelCase(ns)}.${pascalCase(typeRef.value.value)}.${mode})`;
120
+ if (typeRef.kind === "named")
121
+ return `models.Mappers.${camelCase(ns)}.${pascalCase(typeRef.value)}.${mode}`;
122
+ return `(data: any) => data`;
69
123
  }
70
124
 
71
125
  function prefixModels(type: string): string {
package/src/index.ts CHANGED
@@ -1,82 +1,94 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import * as fs from 'fs-extra';
4
- import * as path from 'path';
5
- import { AtmxMultiConfig, MultiIR } from './types';
6
- import { generateModels } from './generators/model-generator';
7
- import { generateSdk } from './generators/sdk-generator';
8
- import { normalizeIr } from './generators/utils';
2
+ import { Command } from "commander";
3
+ import * as fs from "fs-extra";
4
+ import * as path from "path";
5
+ import { AtmxMultiConfig, MultiIR } from "./types";
6
+ import { generateModels } from "./generators/model-generator";
7
+ import { generateSdk } from "./generators/sdk-generator";
8
+ import { normalizeIr } from "./generators/utils";
9
9
 
10
10
  const program = new Command();
11
11
 
12
12
  program
13
- .name('atmx')
14
- .description('Generate TypeScript SDK from an ATMX Multi-Contract Config')
15
- .version('0.2.0');
13
+ .name("atmx")
14
+ .description("Generate TypeScript SDK from an ATMX Multi-Contract Config")
15
+ .version("0.2.0");
16
16
 
17
17
  program
18
- .command('generate')
19
- .requiredOption('-c, --config <path>', 'Path to the atmx.config.json file')
20
- .requiredOption('-o, --output <dir>', 'Output directory for generated files')
21
- .action(async (options) => {
22
- const configPath = path.resolve(options.config);
23
- const outputDir = path.resolve(options.output);
24
-
25
- if (!fs.existsSync(configPath)) {
26
- console.error(`❌ Error: Config file not found at ${configPath}`);
27
- process.exit(1);
28
- }
29
-
30
- // 1. Read the Config File
31
- const rawConfig: AtmxMultiConfig = await fs.readJSON(configPath);
32
-
33
- if (!rawConfig.contracts || Object.keys(rawConfig.contracts).length === 0) {
34
- console.error("❌ Error: Invalid config file. Missing 'contracts' dictionary.");
35
- process.exit(1);
36
- }
37
-
38
- const configDir = path.dirname(configPath);
39
- const multiIr: MultiIR = {};
40
-
41
- // 2. Loop through the contracts and parse the local .axiom files
42
- for (const [namespace, contract] of Object.entries(rawConfig.contracts)) {
43
- // Resolve the .axiom file path relative to where the config file is located
44
- // (e.g., if config is in /public, and file is "./auth.axiom", it looks in /public/auth.axiom)
45
- const axiomFilePath = path.resolve(configDir, contract.file);
46
-
47
- if (!fs.existsSync(axiomFilePath)) {
48
- console.warn(`⚠️ Warning: Contract file not found for namespace '${namespace}' at ${axiomFilePath}. Skipping...`);
49
- continue;
50
- }
51
-
52
- const rawFile = await fs.readJSON(axiomFilePath);
53
-
54
- if (!rawFile.ir) {
55
- console.warn(`⚠️ Warning: Invalid .axiom file for namespace '${namespace}'. Missing 'ir' property. Skipping...`);
56
- continue;
57
- }
58
-
59
- // Normalize the IR (snake_case -> camelCase) and store it by namespace
60
- multiIr[namespace] = normalizeIr(rawFile.ir);
61
- console.log(`✅ Loaded contract: [${namespace}] -> ${contract.file}`);
62
- }
63
-
64
- if (Object.keys(multiIr).length === 0) {
65
- console.error("❌ Error: No valid contracts were loaded. Aborting generation.");
66
- process.exit(1);
67
- }
68
-
69
- // Ensure output directory exists
70
- await fs.ensureDir(outputDir);
71
-
72
- // 3. Pass the MultiIR Map to the generators (We will update these in Phase 3)
73
- const modelsContent = generateModels(multiIr);
74
- await fs.writeFile(path.join(outputDir, 'models.ts'), modelsContent);
75
-
76
- const sdkContent = generateSdk(multiIr);
77
- await fs.writeFile(path.join(outputDir, 'sdk.ts'), sdkContent);
78
-
79
- console.log(`\n🎉 ATMX Multi-Contract SDK generated successfully in ${outputDir}`);
80
- });
81
-
82
- program.parse();
18
+ .command("generate")
19
+ .requiredOption("-c, --config <path>", "Path to the atmx.config.json file")
20
+ .requiredOption("-o, --output <dir>", "Output directory for generated files")
21
+ .option("-r, --react", "Generate React Hooks instead of Vanilla JS strings") // ✨ NEW
22
+ .action(async (options) => {
23
+ const configPath = path.resolve(options.config);
24
+ const outputDir = path.resolve(options.output);
25
+
26
+ if (!fs.existsSync(configPath)) {
27
+ console.error(`❌ Error: Config file not found at ${configPath}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ // 1. Read the Config File
32
+ const rawConfig: AtmxMultiConfig = await fs.readJSON(configPath);
33
+
34
+ if (!rawConfig.contracts || Object.keys(rawConfig.contracts).length === 0) {
35
+ console.error(
36
+ "❌ Error: Invalid config file. Missing 'contracts' dictionary.",
37
+ );
38
+ process.exit(1);
39
+ }
40
+
41
+ const configDir = path.dirname(configPath);
42
+ const multiIr: MultiIR = {};
43
+
44
+ // 2. Loop through the contracts and parse the local .axiom files
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);
49
+
50
+ if (!fs.existsSync(axiomFilePath)) {
51
+ console.warn(
52
+ `⚠️ Warning: Contract file not found for namespace '${namespace}' at ${axiomFilePath}. Skipping...`,
53
+ );
54
+ continue;
55
+ }
56
+
57
+ const rawFile = await fs.readJSON(axiomFilePath);
58
+
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
+ 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);
76
+ }
77
+
78
+ // Ensure output directory exists
79
+ await fs.ensureDir(outputDir);
80
+
81
+ // 3. Pass the MultiIR Map to the generators
82
+ const modelsContent = generateModels(multiIr);
83
+ await fs.writeFile(path.join(outputDir, "models.ts"), modelsContent);
84
+
85
+ // ✨ NEW: Pass the react flag down
86
+ const sdkContent = generateSdk(multiIr, options.react);
87
+ await fs.writeFile(path.join(outputDir, "sdk.ts"), sdkContent);
88
+
89
+ console.log(
90
+ `\n🎉 ATMX Multi-Contract SDK generated successfully in ${outputDir}`,
91
+ );
92
+ });
93
+
94
+ program.parse();