atmx-cli 0.1.93
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 +94 -0
- package/dist/generators/sdk-generator.js +96 -0
- package/dist/generators/utils.js +59 -0
- package/dist/index.js +98 -0
- package/dist/types.js +2 -0
- package/package.json +26 -0
- package/src/generators/model-generator.ts +148 -0
- package/src/generators/sdk-generator.ts +120 -0
- package/src/generators/utils.ts +58 -0
- package/src/index.ts +82 -0
- package/src/types.ts +58 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateModels = generateModels;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function generateModels(multiIr) {
|
|
6
|
+
const sections = [
|
|
7
|
+
`// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n`
|
|
8
|
+
];
|
|
9
|
+
// 1. Generate flat exports (NO namespaces)
|
|
10
|
+
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)));
|
|
16
|
+
}
|
|
17
|
+
// 2. Generate nested Mappers object
|
|
18
|
+
sections.push(generateMappers(multiIr));
|
|
19
|
+
return sections.join('\n');
|
|
20
|
+
}
|
|
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');
|
|
24
|
+
return `
|
|
25
|
+
export const ${name} = {
|
|
26
|
+
${values}
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export type ${name} = typeof ${name}[keyof typeof ${name}];
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
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');
|
|
38
|
+
return `
|
|
39
|
+
export interface ${name} {
|
|
40
|
+
${fields}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
function generateMappers(multiIr) {
|
|
45
|
+
const lines = [`export const Mappers: Record<string, any> = {`];
|
|
46
|
+
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
47
|
+
const camelNs = (0, utils_1.camelCase)(ns);
|
|
48
|
+
const pascalNs = (0, utils_1.pascalCase)(ns);
|
|
49
|
+
lines.push(` ${camelNs}: {`);
|
|
50
|
+
const modelsList = Array.isArray(ir.models) ? ir.models : Object.values(ir.models || {});
|
|
51
|
+
modelsList.forEach((model) => {
|
|
52
|
+
const name = (0, utils_1.pascalCase)(model.name);
|
|
53
|
+
const fullType = `${pascalNs}${name}`; // no namespace now
|
|
54
|
+
lines.push(` ${name}: {\n fromJson: (json: any): ${fullType} => ({`);
|
|
55
|
+
model.fields.forEach((f) => {
|
|
56
|
+
lines.push(` ${(0, utils_1.camelCase)(f.name)}: ${generateJsonLogic(f.typeRef, `json["${f.name}"]`, f.isOptional, 'fromJson', camelNs)},`);
|
|
57
|
+
});
|
|
58
|
+
lines.push(` }),\n toJson: (obj: any): any => ({`);
|
|
59
|
+
model.fields.forEach((f) => {
|
|
60
|
+
lines.push(` "${f.name}": ${generateJsonLogic(f.typeRef, `obj.${(0, utils_1.camelCase)(f.name)}`, f.isOptional, 'toJson', camelNs)},`);
|
|
61
|
+
});
|
|
62
|
+
lines.push(` })\n },`);
|
|
63
|
+
});
|
|
64
|
+
lines.push(` },`);
|
|
65
|
+
}
|
|
66
|
+
lines.push(`};\n`);
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
function generateJsonLogic(typeRef, access, isOpt, mode, ns) {
|
|
70
|
+
const wrap = (logic) => isOpt ? `(${access} == null ? undefined : ${logic})` : logic;
|
|
71
|
+
if (!typeRef || !typeRef.kind)
|
|
72
|
+
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') {
|
|
87
|
+
const name = (0, utils_1.pascalCase)(typeRef.value);
|
|
88
|
+
return wrap(`(Mappers.${ns}["${name}"] ? Mappers.${ns}["${name}"].${mode}(${access}) : ${access})`);
|
|
89
|
+
}
|
|
90
|
+
if (typeRef.kind === 'list') {
|
|
91
|
+
return wrap(`${access}.map((e: any) => ${generateJsonLogic(typeRef.value, 'e', false, mode, ns)})`);
|
|
92
|
+
}
|
|
93
|
+
return access;
|
|
94
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSdk = generateSdk;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function generateSdk(multiIr) {
|
|
6
|
+
const lines = [
|
|
7
|
+
`// GENERATED CODE – DO NOT EDIT.`,
|
|
8
|
+
`import { useAxiomQuery, useAxiomMutation } from 'atmx-react';`,
|
|
9
|
+
`import type { AxiomQueryDef } from 'atmx-react';`,
|
|
10
|
+
`import * as models from './models';\n`,
|
|
11
|
+
];
|
|
12
|
+
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
13
|
+
const pascalNs = (0, utils_1.pascalCase)(ns);
|
|
14
|
+
lines.push(`export class ${pascalNs}Module {`);
|
|
15
|
+
const endpoints = ir.endpoints || [];
|
|
16
|
+
endpoints.forEach((ep) => {
|
|
17
|
+
lines.push(generateEndpointHook(ep, ns, pascalNs));
|
|
18
|
+
});
|
|
19
|
+
lines.push(`}\n`);
|
|
20
|
+
}
|
|
21
|
+
lines.push(`export class AxiomSdk {`);
|
|
22
|
+
for (const ns of Object.keys(multiIr)) {
|
|
23
|
+
lines.push(` public readonly ${(0, utils_1.camelCase)(ns)} = new ${(0, utils_1.pascalCase)(ns)}Module();`);
|
|
24
|
+
}
|
|
25
|
+
lines.push(`}\nexport const sdk = new AxiomSdk();`);
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
function generateEndpointHook(ep, ns, pascalNs) {
|
|
29
|
+
const isQuery = ep.method ? ep.method.toUpperCase() === "GET" : true;
|
|
30
|
+
const rawReturnType = (0, utils_1.mapTypeToTs)(ep.returnType, pascalNs);
|
|
31
|
+
const returnType = rawReturnType === "void" || rawReturnType === "any"
|
|
32
|
+
? rawReturnType
|
|
33
|
+
: prefixModels(rawReturnType);
|
|
34
|
+
const params = ep.parameters || [];
|
|
35
|
+
const argType = params.length > 0
|
|
36
|
+
? `{ ${params.map((p) => `${(0, utils_1.camelCase)(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels((0, utils_1.mapTypeToTs)(p.typeRef, pascalNs))}`).join(", ")} }`
|
|
37
|
+
: "void";
|
|
38
|
+
const factoryLogic = generateQueryDefFactory(ep, returnType, ns);
|
|
39
|
+
// NEW: Every endpoint now generates BOTH a raw definition getter and a hook!
|
|
40
|
+
return `
|
|
41
|
+
/** Raw definition for <AxQuery> or <AxMutate> */
|
|
42
|
+
get${(0, utils_1.pascalCase)(ep.name)}Def(args: ${argType}): AxiomQueryDef<${returnType}> {
|
|
43
|
+
return (${factoryLogic})(args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Reactive hook for functional components */
|
|
47
|
+
use${(0, utils_1.pascalCase)(ep.name)}${!isQuery ? "Mutation" : ""}(${isQuery ? `args: ${argType}, options?: { enabled?: boolean }` : ""}) {
|
|
48
|
+
const factory = ${factoryLogic};
|
|
49
|
+
return ${isQuery ? `useAxiomQuery<${returnType}>(factory(args), options)` : `useAxiomMutation<${returnType}, ${argType}>(factory)`};
|
|
50
|
+
}\n`;
|
|
51
|
+
}
|
|
52
|
+
function generateQueryDefFactory(ep, returnType, ns) {
|
|
53
|
+
const params = ep.parameters || [];
|
|
54
|
+
const bodyParam = params.find((p) => p.source === "body");
|
|
55
|
+
const payloadLogic = bodyParam
|
|
56
|
+
? `const payload = (args as any).${(0, utils_1.camelCase)(bodyParam.name)};`
|
|
57
|
+
: `const payload = undefined;`;
|
|
58
|
+
const decLogic = generateLambda(ep.returnType, "fromJson", ns);
|
|
59
|
+
const serLogic = bodyParam
|
|
60
|
+
? generateLambda(bodyParam.typeRef, "toJson", ns)
|
|
61
|
+
: `(p: any) => p`;
|
|
62
|
+
// THIN DEFINITION: Leaves URL logic up to ATMX Core Router
|
|
63
|
+
return `(args: any): AxiomQueryDef<${returnType}> => {
|
|
64
|
+
${payloadLogic}
|
|
65
|
+
return {
|
|
66
|
+
namespace: "${ns}",
|
|
67
|
+
name: "${ep.name}",
|
|
68
|
+
endpointId: ${ep.id},
|
|
69
|
+
method: "${ep.method ? ep.method.toUpperCase() : "GET"}",
|
|
70
|
+
path: "${ep.path}",
|
|
71
|
+
payload: payload,
|
|
72
|
+
args: args as any,
|
|
73
|
+
decoder: ${decLogic},
|
|
74
|
+
serializer: ${serLogic},
|
|
75
|
+
isStream: ${ep.isStream === true}
|
|
76
|
+
};
|
|
77
|
+
}`;
|
|
78
|
+
}
|
|
79
|
+
function generateLambda(typeRef, mode, ns) {
|
|
80
|
+
if (!typeRef || !typeRef.kind || typeRef.kind === "void")
|
|
81
|
+
return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
|
|
82
|
+
if (typeRef.kind === "list" && typeRef.value?.kind === "named")
|
|
83
|
+
return `(data: any[]) => data.map(models.Mappers.${(0, utils_1.camelCase)(ns)}.${(0, utils_1.pascalCase)(typeRef.value.value)}.${mode})`;
|
|
84
|
+
if (typeRef.kind === "named")
|
|
85
|
+
return `models.Mappers.${(0, utils_1.camelCase)(ns)}.${(0, utils_1.pascalCase)(typeRef.value)}.${mode}`;
|
|
86
|
+
return `(data: any) => data`;
|
|
87
|
+
}
|
|
88
|
+
function prefixModels(type) {
|
|
89
|
+
if (!type || type === "void" || type === "any")
|
|
90
|
+
return type;
|
|
91
|
+
if (type.endsWith("[]"))
|
|
92
|
+
return `${prefixModels(type.slice(0, -2))}[]`;
|
|
93
|
+
if (type.startsWith("models."))
|
|
94
|
+
return type;
|
|
95
|
+
return `models.${type}`;
|
|
96
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// atmx-cli/src/generators/utils.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.pascalCase = pascalCase;
|
|
5
|
+
exports.camelCase = camelCase;
|
|
6
|
+
exports.normalizeIr = normalizeIr;
|
|
7
|
+
exports.mapTypeToTs = mapTypeToTs;
|
|
8
|
+
function pascalCase(str) {
|
|
9
|
+
if (!str)
|
|
10
|
+
return '';
|
|
11
|
+
return str
|
|
12
|
+
.split(/[_\-\s]+/)
|
|
13
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
14
|
+
.join('');
|
|
15
|
+
}
|
|
16
|
+
function camelCase(str) {
|
|
17
|
+
const pascal = pascalCase(str);
|
|
18
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
19
|
+
}
|
|
20
|
+
function normalizeIr(obj) {
|
|
21
|
+
if (Array.isArray(obj))
|
|
22
|
+
return obj.map(normalizeIr);
|
|
23
|
+
if (obj !== null && typeof obj === 'object') {
|
|
24
|
+
const newObj = {};
|
|
25
|
+
for (const key of Object.keys(obj)) {
|
|
26
|
+
const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
27
|
+
newObj[camelKey] = normalizeIr(obj[key]);
|
|
28
|
+
}
|
|
29
|
+
return newObj;
|
|
30
|
+
}
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param scopedNamespace If provided (e.g. 'Auth'), prefixes named types with 'models.Auth.'
|
|
35
|
+
*/
|
|
36
|
+
function mapTypeToTs(typeRef, ns) {
|
|
37
|
+
if (!typeRef)
|
|
38
|
+
return 'any';
|
|
39
|
+
if (typeRef.kind === 'primitive') {
|
|
40
|
+
switch (typeRef.value) {
|
|
41
|
+
case 'string': return 'string';
|
|
42
|
+
case 'int':
|
|
43
|
+
case 'float':
|
|
44
|
+
case 'double': return 'number';
|
|
45
|
+
case 'boolean': return 'boolean';
|
|
46
|
+
case 'dateTime': return 'Date';
|
|
47
|
+
case 'bytes': return 'Uint8Array';
|
|
48
|
+
default: return 'any';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (typeRef.kind === 'named') {
|
|
52
|
+
const name = pascalCase(typeRef.value);
|
|
53
|
+
return ns ? `${ns}${name}` : name; // ✅ FIX HERE
|
|
54
|
+
}
|
|
55
|
+
if (typeRef.kind === 'list') {
|
|
56
|
+
return `${mapTypeToTs(typeRef.value, ns)}[]`;
|
|
57
|
+
}
|
|
58
|
+
return 'any';
|
|
59
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const fs = __importStar(require("fs-extra"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const model_generator_1 = require("./generators/model-generator");
|
|
41
|
+
const sdk_generator_1 = require("./generators/sdk-generator");
|
|
42
|
+
const utils_1 = require("./generators/utils");
|
|
43
|
+
const program = new commander_1.Command();
|
|
44
|
+
program
|
|
45
|
+
.name('atmx')
|
|
46
|
+
.description('Generate TypeScript SDK from an ATMX Multi-Contract Config')
|
|
47
|
+
.version('0.2.0');
|
|
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')
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
const configPath = path.resolve(options.config);
|
|
54
|
+
const outputDir = path.resolve(options.output);
|
|
55
|
+
if (!fs.existsSync(configPath)) {
|
|
56
|
+
console.error(`❌ Error: Config file not found at ${configPath}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
// 1. Read the Config File
|
|
60
|
+
const rawConfig = await fs.readJSON(configPath);
|
|
61
|
+
if (!rawConfig.contracts || Object.keys(rawConfig.contracts).length === 0) {
|
|
62
|
+
console.error("❌ Error: Invalid config file. Missing 'contracts' dictionary.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const configDir = path.dirname(configPath);
|
|
66
|
+
const multiIr = {};
|
|
67
|
+
// 2. Loop through the contracts and parse the local .axiom files
|
|
68
|
+
for (const [namespace, contract] of Object.entries(rawConfig.contracts)) {
|
|
69
|
+
// Resolve the .axiom file path relative to where the config file is located
|
|
70
|
+
// (e.g., if config is in /public, and file is "./auth.axiom", it looks in /public/auth.axiom)
|
|
71
|
+
const axiomFilePath = path.resolve(configDir, contract.file);
|
|
72
|
+
if (!fs.existsSync(axiomFilePath)) {
|
|
73
|
+
console.warn(`⚠️ Warning: Contract file not found for namespace '${namespace}' at ${axiomFilePath}. Skipping...`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const rawFile = await fs.readJSON(axiomFilePath);
|
|
77
|
+
if (!rawFile.ir) {
|
|
78
|
+
console.warn(`⚠️ Warning: Invalid .axiom file for namespace '${namespace}'. Missing 'ir' property. Skipping...`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Normalize the IR (snake_case -> camelCase) and store it by namespace
|
|
82
|
+
multiIr[namespace] = (0, utils_1.normalizeIr)(rawFile.ir);
|
|
83
|
+
console.log(`✅ Loaded contract: [${namespace}] -> ${contract.file}`);
|
|
84
|
+
}
|
|
85
|
+
if (Object.keys(multiIr).length === 0) {
|
|
86
|
+
console.error("❌ Error: No valid contracts were loaded. Aborting generation.");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
// Ensure output directory exists
|
|
90
|
+
await fs.ensureDir(outputDir);
|
|
91
|
+
// 3. Pass the MultiIR Map to the generators (We will update these in Phase 3)
|
|
92
|
+
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);
|
|
96
|
+
console.log(`\n🎉 ATMX Multi-Contract SDK generated successfully in ${outputDir}`);
|
|
97
|
+
});
|
|
98
|
+
program.parse();
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "atmx-cli",
|
|
3
|
+
"version": "0.1.93",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"atmx": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"commander": "^14.0.3",
|
|
18
|
+
"fs-extra": "^11.3.4"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/fs-extra": "^11.0.4",
|
|
22
|
+
"@types/node": "^25.5.0",
|
|
23
|
+
"ts-node": "^10.9.2",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// atmx-cli/src/generators/model-generator.ts
|
|
2
|
+
import { AxiomEnum, AxiomModel, MultiIR } from '../types';
|
|
3
|
+
import { pascalCase, camelCase, mapTypeToTs } from './utils';
|
|
4
|
+
|
|
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');
|
|
25
|
+
}
|
|
26
|
+
|
|
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
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
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} {
|
|
50
|
+
${fields}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
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');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function generateJsonLogic(
|
|
109
|
+
typeRef: any,
|
|
110
|
+
access: string,
|
|
111
|
+
isOpt: boolean,
|
|
112
|
+
mode: 'fromJson' | 'toJson',
|
|
113
|
+
ns: string
|
|
114
|
+
): 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
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// FILE: atmx-cli/src/generators/sdk-generator.ts
|
|
2
|
+
import { AxiomEndpoint, MultiIR } from "../types";
|
|
3
|
+
import { pascalCase, camelCase, mapTypeToTs } from "./utils";
|
|
4
|
+
|
|
5
|
+
export function generateSdk(multiIr: MultiIR): string {
|
|
6
|
+
const lines: string[] = [
|
|
7
|
+
`// GENERATED CODE – DO NOT EDIT.`,
|
|
8
|
+
`import { useAxiomQuery, useAxiomMutation } from 'atmx-react';`,
|
|
9
|
+
`import type { AxiomQueryDef } from 'atmx-react';`,
|
|
10
|
+
`import * as models from './models';\n`,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
14
|
+
const pascalNs = pascalCase(ns);
|
|
15
|
+
lines.push(`export class ${pascalNs}Module {`);
|
|
16
|
+
const endpoints = ir.endpoints || [];
|
|
17
|
+
endpoints.forEach((ep: any) => {
|
|
18
|
+
lines.push(generateEndpointHook(ep, ns, pascalNs));
|
|
19
|
+
});
|
|
20
|
+
lines.push(`}\n`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
lines.push(`export class AxiomSdk {`);
|
|
24
|
+
for (const ns of Object.keys(multiIr)) {
|
|
25
|
+
lines.push(
|
|
26
|
+
` public readonly ${camelCase(ns)} = new ${pascalCase(ns)}Module();`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
lines.push(`}\nexport const sdk = new AxiomSdk();`);
|
|
30
|
+
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function generateEndpointHook(
|
|
35
|
+
ep: AxiomEndpoint,
|
|
36
|
+
ns: string,
|
|
37
|
+
pascalNs: string,
|
|
38
|
+
): string {
|
|
39
|
+
const isQuery = ep.method ? ep.method.toUpperCase() === "GET" : true;
|
|
40
|
+
const rawReturnType = mapTypeToTs(ep.returnType, pascalNs);
|
|
41
|
+
const returnType =
|
|
42
|
+
rawReturnType === "void" || rawReturnType === "any"
|
|
43
|
+
? rawReturnType
|
|
44
|
+
: prefixModels(rawReturnType);
|
|
45
|
+
|
|
46
|
+
const params = ep.parameters || [];
|
|
47
|
+
const argType =
|
|
48
|
+
params.length > 0
|
|
49
|
+
? `{ ${params.map((p) => `${camelCase(p.name)}${p.isOptional ? "?" : ""}: ${prefixModels(mapTypeToTs(p.typeRef, pascalNs))}`).join(", ")} }`
|
|
50
|
+
: "void";
|
|
51
|
+
|
|
52
|
+
const factoryLogic = generateQueryDefFactory(ep, returnType, ns);
|
|
53
|
+
|
|
54
|
+
// NEW: Every endpoint now generates BOTH a raw definition getter and a hook!
|
|
55
|
+
return `
|
|
56
|
+
/** Raw definition for <AxQuery> or <AxMutate> */
|
|
57
|
+
get${pascalCase(ep.name)}Def(args: ${argType}): AxiomQueryDef<${returnType}> {
|
|
58
|
+
return (${factoryLogic})(args);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Reactive hook for functional components */
|
|
62
|
+
use${pascalCase(ep.name)}${!isQuery ? "Mutation" : ""}(${isQuery ? `args: ${argType}, options?: { enabled?: boolean }` : ""}) {
|
|
63
|
+
const factory = ${factoryLogic};
|
|
64
|
+
return ${isQuery ? `useAxiomQuery<${returnType}>(factory(args), options)` : `useAxiomMutation<${returnType}, ${argType}>(factory)`};
|
|
65
|
+
}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function generateQueryDefFactory(
|
|
69
|
+
ep: AxiomEndpoint,
|
|
70
|
+
returnType: string,
|
|
71
|
+
ns: string,
|
|
72
|
+
): string {
|
|
73
|
+
const params = ep.parameters || [];
|
|
74
|
+
const bodyParam = params.find((p) => p.source === "body");
|
|
75
|
+
const payloadLogic = bodyParam
|
|
76
|
+
? `const payload = (args as any).${camelCase(bodyParam.name)};`
|
|
77
|
+
: `const payload = undefined;`;
|
|
78
|
+
const decLogic = generateLambda(ep.returnType, "fromJson", ns);
|
|
79
|
+
const serLogic = bodyParam
|
|
80
|
+
? generateLambda(bodyParam.typeRef, "toJson", ns)
|
|
81
|
+
: `(p: any) => p`;
|
|
82
|
+
|
|
83
|
+
// THIN DEFINITION: Leaves URL logic up to ATMX Core Router
|
|
84
|
+
return `(args: any): AxiomQueryDef<${returnType}> => {
|
|
85
|
+
${payloadLogic}
|
|
86
|
+
return {
|
|
87
|
+
namespace: "${ns}",
|
|
88
|
+
name: "${ep.name}",
|
|
89
|
+
endpointId: ${ep.id},
|
|
90
|
+
method: "${ep.method ? ep.method.toUpperCase() : "GET"}",
|
|
91
|
+
path: "${ep.path}",
|
|
92
|
+
payload: payload,
|
|
93
|
+
args: args as any,
|
|
94
|
+
decoder: ${decLogic},
|
|
95
|
+
serializer: ${serLogic},
|
|
96
|
+
isStream: ${ep.isStream === true}
|
|
97
|
+
};
|
|
98
|
+
}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function generateLambda(
|
|
102
|
+
typeRef: any,
|
|
103
|
+
mode: "fromJson" | "toJson",
|
|
104
|
+
ns: string,
|
|
105
|
+
): string {
|
|
106
|
+
if (!typeRef || !typeRef.kind || typeRef.kind === "void")
|
|
107
|
+
return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
|
|
108
|
+
if (typeRef.kind === "list" && typeRef.value?.kind === "named")
|
|
109
|
+
return `(data: any[]) => data.map(models.Mappers.${camelCase(ns)}.${pascalCase(typeRef.value.value)}.${mode})`;
|
|
110
|
+
if (typeRef.kind === "named")
|
|
111
|
+
return `models.Mappers.${camelCase(ns)}.${pascalCase(typeRef.value)}.${mode}`;
|
|
112
|
+
return `(data: any) => data`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function prefixModels(type: string): string {
|
|
116
|
+
if (!type || type === "void" || type === "any") return type;
|
|
117
|
+
if (type.endsWith("[]")) return `${prefixModels(type.slice(0, -2))}[]`;
|
|
118
|
+
if (type.startsWith("models.")) return type;
|
|
119
|
+
return `models.${type}`;
|
|
120
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// atmx-cli/src/generators/utils.ts
|
|
2
|
+
|
|
3
|
+
export function pascalCase(str: string): string {
|
|
4
|
+
if (!str) return '';
|
|
5
|
+
return str
|
|
6
|
+
.split(/[_\-\s]+/)
|
|
7
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
8
|
+
.join('');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function camelCase(str: string): string {
|
|
12
|
+
const pascal = pascalCase(str);
|
|
13
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeIr(obj: any): any {
|
|
17
|
+
if (Array.isArray(obj)) return obj.map(normalizeIr);
|
|
18
|
+
if (obj !== null && typeof obj === 'object') {
|
|
19
|
+
const newObj: any = {};
|
|
20
|
+
for (const key of Object.keys(obj)) {
|
|
21
|
+
const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
22
|
+
newObj[camelKey] = normalizeIr(obj[key]);
|
|
23
|
+
}
|
|
24
|
+
return newObj;
|
|
25
|
+
}
|
|
26
|
+
return obj;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param scopedNamespace If provided (e.g. 'Auth'), prefixes named types with 'models.Auth.'
|
|
31
|
+
*/
|
|
32
|
+
export function mapTypeToTs(typeRef: any, ns?: string): string {
|
|
33
|
+
if (!typeRef) return 'any';
|
|
34
|
+
|
|
35
|
+
if (typeRef.kind === 'primitive') {
|
|
36
|
+
switch (typeRef.value) {
|
|
37
|
+
case 'string': return 'string';
|
|
38
|
+
case 'int':
|
|
39
|
+
case 'float':
|
|
40
|
+
case 'double': return 'number';
|
|
41
|
+
case 'boolean': return 'boolean';
|
|
42
|
+
case 'dateTime': return 'Date';
|
|
43
|
+
case 'bytes': return 'Uint8Array';
|
|
44
|
+
default: return 'any';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeRef.kind === 'named') {
|
|
49
|
+
const name = pascalCase(typeRef.value);
|
|
50
|
+
return ns ? `${ns}${name}` : name; // ✅ FIX HERE
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeRef.kind === 'list') {
|
|
54
|
+
return `${mapTypeToTs(typeRef.value, ns)}[]`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return 'any';
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
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';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('atmx')
|
|
14
|
+
.description('Generate TypeScript SDK from an ATMX Multi-Contract Config')
|
|
15
|
+
.version('0.2.0');
|
|
16
|
+
|
|
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();
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface AxiomIR {
|
|
2
|
+
serviceName: string;
|
|
3
|
+
endpoints: AxiomEndpoint[];
|
|
4
|
+
models: Record<string, AxiomModel>;
|
|
5
|
+
enums: Record<string, AxiomEnum>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AxiomEndpoint {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
method: string;
|
|
13
|
+
parameters: AxiomParameter[];
|
|
14
|
+
returnType: AxiomTypeRef;
|
|
15
|
+
returnIsOptional: boolean;
|
|
16
|
+
isStream: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AxiomParameter {
|
|
20
|
+
name: string;
|
|
21
|
+
source: "path" | "query" | "body";
|
|
22
|
+
typeRef: AxiomTypeRef;
|
|
23
|
+
isOptional: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AxiomTypeRef =
|
|
27
|
+
| { kind: "primitive" | "named"; value: string }
|
|
28
|
+
| { kind: "list"; value: AxiomTypeRef }
|
|
29
|
+
| { kind: "map"; value: [AxiomTypeRef, AxiomTypeRef] }
|
|
30
|
+
| { kind: "void" };
|
|
31
|
+
|
|
32
|
+
export interface AxiomModel {
|
|
33
|
+
name: string;
|
|
34
|
+
fields: AxiomField[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AxiomField {
|
|
38
|
+
name: string;
|
|
39
|
+
typeRef: AxiomTypeRef;
|
|
40
|
+
isOptional: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AxiomEnum {
|
|
44
|
+
name: string;
|
|
45
|
+
values: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AtmxContractConfig {
|
|
49
|
+
file: string; // Path relative to the config file (e.g., "./auth.axiom")
|
|
50
|
+
baseUrl: string; // The URL for runtime (not used during code generation, but part of schema)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AtmxMultiConfig {
|
|
54
|
+
contracts: Record<string, AtmxContractConfig>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// A Map holding the normalized IR for each contract
|
|
58
|
+
export type MultiIR = Record<string, AxiomIR>;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020"
|
|
7
|
+
],
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"moduleResolution": "node",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/**/*"
|
|
18
|
+
]
|
|
19
|
+
}
|