atmx-cli 0.85.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators/sdk-generator.js +177 -175
- package/dist/index.js +11 -1
- package/package.json +1 -1
- package/src/generators/sdk-generator.ts +187 -237
- package/src/index.ts +17 -2
|
@@ -1,190 +1,192 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`import * as models from './models';\n`,
|
|
11
|
-
];
|
|
12
|
-
if (isReact) {
|
|
13
|
-
// ✨ FIX: Auto-import the auth helpers and QueryManager directly
|
|
14
|
-
lines.push(`import { useAxiomQuery, useAxiomMutation, setAuthToken, clearAuthToken, axiomQueryManager } from 'atmx-react';`);
|
|
15
|
-
lines.push(`import type { AxiomQueryDef } from 'atmx-react';\n`);
|
|
3
|
+
exports.generateSDKContent = generateSDKContent;
|
|
4
|
+
// Helper to convert Axiom TypeRef to TypeScript Types
|
|
5
|
+
function getTsType(namespace, typeRef) {
|
|
6
|
+
if (!typeRef)
|
|
7
|
+
return "any";
|
|
8
|
+
if (typeRef.kind === "named") {
|
|
9
|
+
return `models.${namespace}.${typeRef.value}`;
|
|
16
10
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
lines.push(`export const ${camelNs}Module = {`);
|
|
20
|
-
lines.push(` axiom: {`);
|
|
21
|
-
if (isReact) {
|
|
22
|
-
lines.push(` setAuthToken(methodName: string, token: string) {`);
|
|
23
|
-
lines.push(` setAuthToken("${ns}", methodName, token);`);
|
|
24
|
-
lines.push(` },`);
|
|
25
|
-
lines.push(` clearAuthToken(methodName: string) {`);
|
|
26
|
-
lines.push(` clearAuthToken("${ns}", methodName);`);
|
|
27
|
-
lines.push(` },`);
|
|
28
|
-
lines.push(` connect(methodName: string, args?: Record<string, any>) {`);
|
|
29
|
-
// ✨ FIX: Use the specific module name instead of 'this'
|
|
30
|
-
lines.push(` const def = (${camelNs}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);`);
|
|
31
|
-
lines.push(` axiomQueryManager.connect(def);`);
|
|
32
|
-
lines.push(` },`);
|
|
33
|
-
lines.push(` disconnect(methodName: string, args?: Record<string, any>) {`);
|
|
34
|
-
lines.push(` const def = (${camelNs}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);`);
|
|
35
|
-
lines.push(` axiomQueryManager.disconnect(def);`);
|
|
36
|
-
lines.push(` },`);
|
|
37
|
-
lines.push(` send(methodName: string, payload: any, args?: Record<string, any>) {`);
|
|
38
|
-
lines.push(` const def = (${camelNs}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);`);
|
|
39
|
-
lines.push(` axiomQueryManager.send(def, payload);`);
|
|
40
|
-
lines.push(` }`);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
lines.push(` setAuthToken(methodName: string, token: string) {`);
|
|
44
|
-
lines.push(` (window as any).atmx?.setAuthToken("${ns}", methodName, token);`);
|
|
45
|
-
lines.push(` },`);
|
|
46
|
-
lines.push(` clearAuthToken(methodName: string) {`);
|
|
47
|
-
lines.push(` (window as any).atmx?.clearAuthToken("${ns}", methodName);`);
|
|
48
|
-
lines.push(` },`);
|
|
49
|
-
lines.push(` connect(methodName: string, args?: Record<string, any>) {`);
|
|
50
|
-
lines.push(` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';`);
|
|
51
|
-
lines.push(` (window as any).atmx?.connect(\`${ns}.\${methodName}(\${argsStr})\`);`);
|
|
52
|
-
lines.push(` },`);
|
|
53
|
-
lines.push(` disconnect(methodName: string, args?: Record<string, any>) {`);
|
|
54
|
-
lines.push(` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';`);
|
|
55
|
-
lines.push(` (window as any).atmx?.disconnect(\`${ns}.\${methodName}(\${argsStr})\`);`);
|
|
56
|
-
lines.push(` },`);
|
|
57
|
-
lines.push(` send(methodName: string, payload: any, args?: Record<string, any>) {`);
|
|
58
|
-
lines.push(` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';`);
|
|
59
|
-
lines.push(` (window as any).atmx?.send(\`${ns}.\${methodName}(\${argsStr})\`, payload);`);
|
|
60
|
-
lines.push(` }`);
|
|
61
|
-
}
|
|
62
|
-
lines.push(` },`);
|
|
63
|
-
const endpointsMap = ir.endpoints || {};
|
|
64
|
-
const endpoints = Array.isArray(endpointsMap)
|
|
65
|
-
? endpointsMap
|
|
66
|
-
: Object.values(endpointsMap);
|
|
67
|
-
endpoints.forEach((ep) => {
|
|
68
|
-
lines.push(generateEndpointMethod(ep, ns, camelNs, isReact));
|
|
69
|
-
});
|
|
70
|
-
lines.push(`};\n`);
|
|
11
|
+
else if (typeRef.kind === "list") {
|
|
12
|
+
return `${getTsType(namespace, typeRef.value)}[]`;
|
|
71
13
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
14
|
+
else if (typeRef.kind === "primitive") {
|
|
15
|
+
if (["int32", "int64", "float32", "float64"].includes(typeRef.value))
|
|
16
|
+
return "number";
|
|
17
|
+
if (typeRef.value === "bool")
|
|
18
|
+
return "boolean";
|
|
19
|
+
if (typeRef.value === "string")
|
|
20
|
+
return "string";
|
|
75
21
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
lines.push(` contracts: {`);
|
|
79
|
-
for (const ns of Object.keys(multiIr)) {
|
|
80
|
-
lines.push(` "${ns}": {`);
|
|
81
|
-
lines.push(` contractUrl: "/${ns}.axiom",`);
|
|
82
|
-
lines.push(` baseUrl: "http://localhost:8000"`);
|
|
83
|
-
lines.push(` },`);
|
|
22
|
+
else if (typeRef.kind === "void") {
|
|
23
|
+
return "void";
|
|
84
24
|
}
|
|
85
|
-
|
|
86
|
-
lines.push(`};\n`);
|
|
87
|
-
return lines.join("\n");
|
|
25
|
+
return "any";
|
|
88
26
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
27
|
+
// Helper to get the correct deserializer function for a given TypeRef
|
|
28
|
+
function getDecoder(namespace, typeRef) {
|
|
29
|
+
if (!typeRef)
|
|
30
|
+
return `(json: any) => json`;
|
|
31
|
+
if (typeRef.kind === "named") {
|
|
32
|
+
return `models.Mappers.${namespace}.${typeRef.value}.fromJson`;
|
|
33
|
+
}
|
|
34
|
+
else if (typeRef.kind === "list" &&
|
|
35
|
+
typeRef.value.kind === "named") {
|
|
36
|
+
const innerName = typeRef.value.value;
|
|
37
|
+
return `(json: any[]) => json.map(models.Mappers.${namespace}.${innerName}.fromJson)`;
|
|
38
|
+
}
|
|
39
|
+
return `(json: any) => json`;
|
|
40
|
+
}
|
|
41
|
+
function generateSDKContent(contracts, isReact) {
|
|
42
|
+
let content = `// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n`;
|
|
43
|
+
if (!isReact) {
|
|
44
|
+
content += `import * as models from './models.js';\n\n`;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
content += `import * as models from './models.js';\n`;
|
|
48
|
+
content += `import { useAxiomQuery, useAxiomMutation, setAuthToken, clearAuthToken, axiomQueryManager } from "atmx-react";\n`;
|
|
49
|
+
content += `import type { AxiomQueryDef } from "atmx-react";\n\n`;
|
|
50
|
+
}
|
|
51
|
+
// 1. Generate Individual Modules
|
|
52
|
+
for (const [namespace, contract] of Object.entries(contracts)) {
|
|
53
|
+
content += `export const ${namespace}Module = {\n`;
|
|
54
|
+
content += ` axiom: {\n`;
|
|
55
|
+
content += ` setAuthToken(methodName: string, token: string) {\n`;
|
|
56
|
+
if (isReact) {
|
|
57
|
+
content += ` setAuthToken("${namespace}", methodName, token);\n`;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
content += ` (window as any).atmx?.setAuthToken("${namespace}", methodName, token);\n`;
|
|
61
|
+
}
|
|
62
|
+
content += ` },\n`;
|
|
63
|
+
content += ` clearAuthToken(methodName: string) {\n`;
|
|
64
|
+
if (isReact) {
|
|
65
|
+
content += ` clearAuthToken("${namespace}", methodName);\n`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
content += ` (window as any).atmx?.clearAuthToken("${namespace}", methodName);\n`;
|
|
69
|
+
}
|
|
70
|
+
content += ` },\n`;
|
|
71
|
+
content += ` connect(methodName: string, args?: Record<string, any>) {\n`;
|
|
72
|
+
if (isReact) {
|
|
73
|
+
content += ` const def = (${namespace}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);\n`;
|
|
74
|
+
content += ` axiomQueryManager.connect(def);\n`;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
78
|
+
content += ` (window as any).atmx?.connect(\`${namespace}.\${methodName}(\${argsStr})\`);\n`;
|
|
79
|
+
}
|
|
80
|
+
content += ` },\n`;
|
|
81
|
+
content += ` disconnect(methodName: string, args?: Record<string, any>) {\n`;
|
|
106
82
|
if (isReact) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
get${(0, utils_1.pascalCase)(ep.name)}Def(args?: Record<string, any>): AxiomQueryDef<${returnType}> {
|
|
110
|
-
return {
|
|
111
|
-
namespace: "${ns}", name: "${ep.name}", endpointId: ${ep.id},
|
|
112
|
-
method: "${ep.method ? ep.method.toUpperCase() : "GET"}", path: "${ep.path}",
|
|
113
|
-
args: args || {}, decoder: ${decLogic}, serializer: (p: any) => p, isStream: ${ep.isStream === true}
|
|
114
|
-
};
|
|
115
|
-
},
|
|
116
|
-
use${(0, utils_1.pascalCase)(ep.name)}(${isQuery ? "options?: { enabled?: boolean }" : ""}) {
|
|
117
|
-
${isQuery ? `return useAxiomQuery<${returnType}>(this.get${(0, utils_1.pascalCase)(ep.name)}Def(), options);` : `return useAxiomMutation<${returnType}, void | Record<string,any>>((a) => this.get${(0, utils_1.pascalCase)(ep.name)}Def(a));`}
|
|
118
|
-
},`;
|
|
83
|
+
content += ` const def = (${namespace}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);\n`;
|
|
84
|
+
content += ` axiomQueryManager.disconnect(def);\n`;
|
|
119
85
|
}
|
|
120
86
|
else {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';
|
|
124
|
-
return \`${ns}.${ep.name}(\${argsStr})\`;
|
|
125
|
-
},`;
|
|
87
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
88
|
+
content += ` (window as any).atmx?.disconnect(\`${namespace}.\${methodName}(\${argsStr})\`);\n`;
|
|
126
89
|
}
|
|
90
|
+
content += ` },\n`;
|
|
91
|
+
content += ` send(methodName: string, payload: any, args?: Record<string, any>) {\n`;
|
|
92
|
+
if (isReact) {
|
|
93
|
+
content += ` const def = (${namespace}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);\n`;
|
|
94
|
+
content += ` axiomQueryManager.send(def, payload);\n`;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
98
|
+
content += ` (window as any).atmx?.send(\`${namespace}.\${methodName}(\${argsStr})\`, payload);\n`;
|
|
99
|
+
}
|
|
100
|
+
content += ` }\n`;
|
|
101
|
+
content += ` },\n\n`;
|
|
102
|
+
// Extract endpoints handling objects vs arrays
|
|
103
|
+
const endpoints = Array.isArray(contract.ir.endpoints)
|
|
104
|
+
? contract.ir.endpoints
|
|
105
|
+
: Object.values(contract.ir.endpoints || {});
|
|
106
|
+
for (const endpoint of endpoints) {
|
|
107
|
+
const fnName = endpoint.name.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
108
|
+
const capFnName = fnName.charAt(0).toUpperCase() + fnName.slice(1);
|
|
109
|
+
if (isReact) {
|
|
110
|
+
const tsType = getTsType(namespace, endpoint.returnType);
|
|
111
|
+
const decoder = getDecoder(namespace, endpoint.returnType);
|
|
112
|
+
content += ` get${capFnName}Def(\n`;
|
|
113
|
+
content += ` args?: Record<string, any>,\n`;
|
|
114
|
+
content += ` ): AxiomQueryDef<${tsType}> {\n`;
|
|
115
|
+
content += ` return {\n`;
|
|
116
|
+
content += ` namespace: "${namespace}",\n`;
|
|
117
|
+
content += ` name: "${endpoint.name}",\n`;
|
|
118
|
+
content += ` endpointId: ${endpoint.id || 0},\n`;
|
|
119
|
+
content += ` method: "${endpoint.method}",\n`;
|
|
120
|
+
content += ` path: "${endpoint.path}",\n`;
|
|
121
|
+
content += ` args: args || {},\n`;
|
|
122
|
+
content += ` decoder: ${decoder},\n`;
|
|
123
|
+
content += ` serializer: (p: any) => p,\n`;
|
|
124
|
+
content += ` isStream: ${endpoint.isStream ? "true" : "false"},\n`;
|
|
125
|
+
content += ` };\n`;
|
|
126
|
+
content += ` },\n`;
|
|
127
|
+
if (endpoint.method === "GET" || endpoint.method === "WS") {
|
|
128
|
+
content += ` use${capFnName}(options?: { enabled?: boolean }) {\n`;
|
|
129
|
+
content += ` return useAxiomQuery<${tsType}>(\n`;
|
|
130
|
+
content += ` this.get${capFnName}Def(),\n`;
|
|
131
|
+
content += ` options,\n`;
|
|
132
|
+
content += ` );\n`;
|
|
133
|
+
content += ` },\n`;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
content += ` use${capFnName}(options?: any) {\n`;
|
|
137
|
+
content += ` return useAxiomMutation<${tsType}>(\n`;
|
|
138
|
+
content += ` this.get${capFnName}Def(),\n`;
|
|
139
|
+
content += ` options,\n`;
|
|
140
|
+
content += ` );\n`;
|
|
141
|
+
content += ` },\n`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Vanilla Web generator logic
|
|
146
|
+
content += ` ${fnName}(args?: Record<string, any>): string {\n`;
|
|
147
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
148
|
+
content += ` return \`${namespace}.${endpoint.name}(\${argsStr})\`;\n`;
|
|
149
|
+
content += ` },\n`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
content += `};\n\n`;
|
|
127
153
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
? `const payload = (args as any)?.${(0, utils_1.camelCase)(bodyParam.name)};`
|
|
133
|
-
: `const payload = undefined;`;
|
|
134
|
-
const decLogic = generateLambda(ep.returnType, "fromJson", camelNs);
|
|
135
|
-
const serLogic = bodyParam
|
|
136
|
-
? generateLambda(bodyParam.typeRef, "toJson", camelNs)
|
|
137
|
-
: `(p: any) => p`;
|
|
138
|
-
return `
|
|
139
|
-
get${(0, utils_1.pascalCase)(ep.name)}Def(args?: ${argType}): AxiomQueryDef<${returnType}> {
|
|
140
|
-
${payloadLogic}
|
|
141
|
-
const mappedArgs: any = { ...(args || {}) };
|
|
142
|
-
${argsMapping}
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
namespace: "${ns}", name: "${ep.name}", endpointId: ${ep.id},
|
|
146
|
-
method: "${ep.method ? ep.method.toUpperCase() : "GET"}", path: "${ep.path}",
|
|
147
|
-
payload: payload, args: mappedArgs, decoder: ${decLogic}, serializer: ${serLogic}, isStream: ${ep.isStream === true}
|
|
148
|
-
};
|
|
149
|
-
},
|
|
150
|
-
use${(0, utils_1.pascalCase)(ep.name)}(${isQuery ? `args?: ${argType}, options?: { enabled?: boolean }` : `args?: ${argType}`}) {
|
|
151
|
-
${isQuery ? `return useAxiomQuery<${returnType}>(this.get${(0, utils_1.pascalCase)(ep.name)}Def(args), options);` : `return useAxiomMutation<${returnType}, ${argType}>((a) => this.get${(0, utils_1.pascalCase)(ep.name)}Def(a || args));`}
|
|
152
|
-
},`;
|
|
154
|
+
// 2. Generate the Smart Proxy SDK
|
|
155
|
+
content += `const internalSdk: Record<string, any> = {\n`;
|
|
156
|
+
for (const namespace of Object.keys(contracts)) {
|
|
157
|
+
content += ` ${namespace}: ${namespace}Module,\n`;
|
|
153
158
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
content += `};\n\n`;
|
|
160
|
+
content += `// ✨ The Magic Proxy: Safely intercepts Alpine.js evaluations during boot!\n`;
|
|
161
|
+
content += `export const sdk = new Proxy(internalSdk, {\n`;
|
|
162
|
+
content += ` get(target: any, prop: string, receiver: any) {\n`;
|
|
163
|
+
content += ` if (prop in target) {\n`;
|
|
164
|
+
content += ` return Reflect.get(target, prop, receiver);\n`;
|
|
165
|
+
content += ` }\n`;
|
|
166
|
+
content += ` // If Alpine tries to access a namespace that doesn't exist yet, return a nested Proxy!\n`;
|
|
167
|
+
content += ` return new Proxy({}, {\n`;
|
|
168
|
+
content += ` get(subTarget: any, subProp: string) {\n`;
|
|
169
|
+
content += ` return () => \`\${String(prop)}.\${String(subProp)}()\`;\n`;
|
|
170
|
+
content += ` }\n`;
|
|
171
|
+
content += ` });\n`;
|
|
172
|
+
content += ` }\n`;
|
|
173
|
+
content += `});\n\n`;
|
|
174
|
+
content += `// Auto-attach to window for Alpine.js immediate hydration\n`;
|
|
175
|
+
content += `if (typeof window !== "undefined") {\n`;
|
|
176
|
+
content += ` (window as any).sdk = sdk;\n`;
|
|
177
|
+
content += `}\n\n`;
|
|
178
|
+
// 3. Generate Default Config
|
|
179
|
+
content += `export const AxiomDefaultConfig = {\n`;
|
|
180
|
+
content += ` contracts: {\n`;
|
|
181
|
+
for (const [ns, def] of Object.entries(contracts)) {
|
|
182
|
+
// Determine file path or default to namespace
|
|
183
|
+
const contractPath = def.file ? def.file : `/${ns}.axiom`;
|
|
184
|
+
content += ` "${ns}": {\n`;
|
|
185
|
+
content += ` contractUrl: "${contractPath}",\n`;
|
|
186
|
+
content += ` baseUrl: "${def.baseUrl}"\n`;
|
|
187
|
+
content += ` },\n`;
|
|
162
188
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return mode === "fromJson" ? `() => undefined` : `(p: any) => p`;
|
|
167
|
-
if (typeRef.kind === "list" && typeRef.value?.kind === "named")
|
|
168
|
-
return `(data: any[]) => data.map(models.Mappers.${ns}.${(0, utils_1.pascalCase)(typeRef.value.value)}.${mode})`;
|
|
169
|
-
if (typeRef.kind === "named")
|
|
170
|
-
return `models.Mappers.${ns}.${(0, utils_1.pascalCase)(typeRef.value)}.${mode}`;
|
|
171
|
-
return `(data: any) => data`;
|
|
172
|
-
}
|
|
173
|
-
function prefixModels(type) {
|
|
174
|
-
const primitives = [
|
|
175
|
-
"string",
|
|
176
|
-
"number",
|
|
177
|
-
"boolean",
|
|
178
|
-
"Date",
|
|
179
|
-
"Uint8Array",
|
|
180
|
-
"void",
|
|
181
|
-
"any",
|
|
182
|
-
];
|
|
183
|
-
if (!type || primitives.includes(type))
|
|
184
|
-
return type;
|
|
185
|
-
if (type.endsWith("[]"))
|
|
186
|
-
return `${prefixModels(type.slice(0, -2))}[]`;
|
|
187
|
-
if (type.startsWith("models."))
|
|
188
|
-
return type;
|
|
189
|
-
return `models.${type}`;
|
|
189
|
+
content += ` }\n`;
|
|
190
|
+
content += `};\n`;
|
|
191
|
+
return content;
|
|
190
192
|
}
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs-extra"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const toml = __importStar(require("@iarna/toml"));
|
|
41
41
|
const model_generator_1 = require("./generators/model-generator");
|
|
42
|
+
// ✨ FIX: Import generateSDKContent and ContractPayload
|
|
42
43
|
const sdk_generator_1 = require("./generators/sdk-generator");
|
|
43
44
|
const utils_1 = require("./generators/utils");
|
|
44
45
|
const program = new commander_1.Command();
|
|
@@ -66,6 +67,7 @@ program
|
|
|
66
67
|
process.exit(1);
|
|
67
68
|
}
|
|
68
69
|
const multiIr = {};
|
|
70
|
+
const generatorPayload = {}; // ✨ NEW: Payload for SDK
|
|
69
71
|
const projectRoot = path.dirname(configPath); // Frontend project root
|
|
70
72
|
// 2. Loop through contracts
|
|
71
73
|
for (const [namespace, contract] of Object.entries(rawConfig.contracts)) {
|
|
@@ -79,12 +81,20 @@ program
|
|
|
79
81
|
if (!rawFile.ir)
|
|
80
82
|
continue;
|
|
81
83
|
multiIr[namespace] = (0, utils_1.normalizeIr)(rawFile.ir);
|
|
84
|
+
// ✨ NEW: Combine IR with TOML config for the SDK generator
|
|
85
|
+
generatorPayload[namespace] = {
|
|
86
|
+
ir: multiIr[namespace],
|
|
87
|
+
baseUrl: contract.base_url || "http://localhost:8080",
|
|
88
|
+
file: `/${namespace}.axiom`,
|
|
89
|
+
};
|
|
82
90
|
console.log(`✅ Loaded contract: [${namespace}] -> ${axiomFilePath}`);
|
|
83
91
|
}
|
|
84
92
|
await fs.ensureDir(outputDir);
|
|
93
|
+
// 3. Generate Models (Needs raw MultiIR)
|
|
85
94
|
const modelsContent = (0, model_generator_1.generateModels)(multiIr);
|
|
86
95
|
await fs.writeFile(path.join(outputDir, "models.ts"), modelsContent);
|
|
87
|
-
|
|
96
|
+
// 4. Generate SDK (Needs enriched ContractPayload)
|
|
97
|
+
const sdkContent = (0, sdk_generator_1.generateSDKContent)(generatorPayload, options.react);
|
|
88
98
|
await fs.writeFile(path.join(outputDir, "sdk.ts"), sdkContent);
|
|
89
99
|
console.log(`\n🎉 ATMX Multi-Contract SDK generated successfully in ${outputDir}`);
|
|
90
100
|
});
|
package/package.json
CHANGED
|
@@ -1,255 +1,205 @@
|
|
|
1
|
-
|
|
2
|
-
import { AxiomEndpoint, AxiomParameter, MultiIR } from "../types";
|
|
3
|
-
import { pascalCase, camelCase, mapTypeToTs } from "./utils";
|
|
1
|
+
import { AxiomIR, AxiomEndpoint, AxiomTypeRef } from "../types.js";
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`// GENERATED CODE – DO NOT EDIT.`,
|
|
11
|
-
`/* eslint-disable @typescript-eslint/no-explicit-any */`,
|
|
12
|
-
`/* eslint-disable @typescript-eslint/no-unused-vars */`,
|
|
13
|
-
`import * as models from './models';\n`,
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
if (isReact) {
|
|
17
|
-
// ✨ FIX: Auto-import the auth helpers and QueryManager directly
|
|
18
|
-
lines.push(
|
|
19
|
-
`import { useAxiomQuery, useAxiomMutation, setAuthToken, clearAuthToken, axiomQueryManager } from 'atmx-react';`,
|
|
20
|
-
);
|
|
21
|
-
lines.push(`import type { AxiomQueryDef } from 'atmx-react';\n`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
for (const [ns, ir] of Object.entries(multiIr)) {
|
|
25
|
-
const camelNs = camelCase(ns);
|
|
26
|
-
lines.push(`export const ${camelNs}Module = {`);
|
|
27
|
-
|
|
28
|
-
lines.push(` axiom: {`);
|
|
29
|
-
if (isReact) {
|
|
30
|
-
lines.push(` setAuthToken(methodName: string, token: string) {`);
|
|
31
|
-
lines.push(` setAuthToken("${ns}", methodName, token);`);
|
|
32
|
-
lines.push(` },`);
|
|
33
|
-
lines.push(` clearAuthToken(methodName: string) {`);
|
|
34
|
-
lines.push(` clearAuthToken("${ns}", methodName);`);
|
|
35
|
-
lines.push(` },`);
|
|
36
|
-
lines.push(
|
|
37
|
-
` connect(methodName: string, args?: Record<string, any>) {`,
|
|
38
|
-
);
|
|
39
|
-
// ✨ FIX: Use the specific module name instead of 'this'
|
|
40
|
-
lines.push(
|
|
41
|
-
` const def = (${camelNs}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);`,
|
|
42
|
-
);
|
|
43
|
-
lines.push(` axiomQueryManager.connect(def);`);
|
|
44
|
-
lines.push(` },`);
|
|
45
|
-
lines.push(
|
|
46
|
-
` disconnect(methodName: string, args?: Record<string, any>) {`,
|
|
47
|
-
);
|
|
48
|
-
lines.push(
|
|
49
|
-
` const def = (${camelNs}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);`,
|
|
50
|
-
);
|
|
51
|
-
lines.push(` axiomQueryManager.disconnect(def);`);
|
|
52
|
-
lines.push(` },`);
|
|
53
|
-
lines.push(
|
|
54
|
-
` send(methodName: string, payload: any, args?: Record<string, any>) {`,
|
|
55
|
-
);
|
|
56
|
-
lines.push(
|
|
57
|
-
` const def = (${camelNs}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);`,
|
|
58
|
-
);
|
|
59
|
-
lines.push(` axiomQueryManager.send(def, payload);`);
|
|
60
|
-
lines.push(` }`);
|
|
61
|
-
} else {
|
|
62
|
-
lines.push(` setAuthToken(methodName: string, token: string) {`);
|
|
63
|
-
lines.push(
|
|
64
|
-
` (window as any).atmx?.setAuthToken("${ns}", methodName, token);`,
|
|
65
|
-
);
|
|
66
|
-
lines.push(` },`);
|
|
67
|
-
lines.push(` clearAuthToken(methodName: string) {`);
|
|
68
|
-
lines.push(
|
|
69
|
-
` (window as any).atmx?.clearAuthToken("${ns}", methodName);`,
|
|
70
|
-
);
|
|
71
|
-
lines.push(` },`);
|
|
72
|
-
lines.push(
|
|
73
|
-
` connect(methodName: string, args?: Record<string, any>) {`,
|
|
74
|
-
);
|
|
75
|
-
lines.push(
|
|
76
|
-
` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';`,
|
|
77
|
-
);
|
|
78
|
-
lines.push(
|
|
79
|
-
` (window as any).atmx?.connect(\`${ns}.\${methodName}(\${argsStr})\`);`,
|
|
80
|
-
);
|
|
81
|
-
lines.push(` },`);
|
|
82
|
-
lines.push(
|
|
83
|
-
` disconnect(methodName: string, args?: Record<string, any>) {`,
|
|
84
|
-
);
|
|
85
|
-
lines.push(
|
|
86
|
-
` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';`,
|
|
87
|
-
);
|
|
88
|
-
lines.push(
|
|
89
|
-
` (window as any).atmx?.disconnect(\`${ns}.\${methodName}(\${argsStr})\`);`,
|
|
90
|
-
);
|
|
91
|
-
lines.push(` },`);
|
|
92
|
-
lines.push(
|
|
93
|
-
` send(methodName: string, payload: any, args?: Record<string, any>) {`,
|
|
94
|
-
);
|
|
95
|
-
lines.push(
|
|
96
|
-
` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';`,
|
|
97
|
-
);
|
|
98
|
-
lines.push(
|
|
99
|
-
` (window as any).atmx?.send(\`${ns}.\${methodName}(\${argsStr})\`, payload);`,
|
|
100
|
-
);
|
|
101
|
-
lines.push(` }`);
|
|
102
|
-
}
|
|
103
|
-
lines.push(` },`);
|
|
104
|
-
|
|
105
|
-
const endpointsMap = ir.endpoints || {};
|
|
106
|
-
const endpoints = Array.isArray(endpointsMap)
|
|
107
|
-
? endpointsMap
|
|
108
|
-
: Object.values(endpointsMap);
|
|
3
|
+
export interface ContractPayload {
|
|
4
|
+
ir: AxiomIR;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
file: string;
|
|
7
|
+
}
|
|
109
8
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
9
|
+
// Helper to convert Axiom TypeRef to TypeScript Types
|
|
10
|
+
function getTsType(namespace: string, typeRef?: AxiomTypeRef): string {
|
|
11
|
+
if (!typeRef) return "any";
|
|
12
|
+
if (typeRef.kind === "named") {
|
|
13
|
+
return `models.${namespace}.${typeRef.value}`;
|
|
14
|
+
} else if (typeRef.kind === "list") {
|
|
15
|
+
return `${getTsType(namespace, typeRef.value as AxiomTypeRef)}[]`;
|
|
16
|
+
} else if (typeRef.kind === "primitive") {
|
|
17
|
+
if (["int32", "int64", "float32", "float64"].includes(typeRef.value))
|
|
18
|
+
return "number";
|
|
19
|
+
if (typeRef.value === "bool") return "boolean";
|
|
20
|
+
if (typeRef.value === "string") return "string";
|
|
21
|
+
} else if (typeRef.kind === "void") {
|
|
22
|
+
return "void";
|
|
114
23
|
}
|
|
24
|
+
return "any";
|
|
25
|
+
}
|
|
115
26
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
lines.push(` },`);
|
|
27
|
+
// Helper to get the correct deserializer function for a given TypeRef
|
|
28
|
+
function getDecoder(namespace: string, typeRef?: AxiomTypeRef): string {
|
|
29
|
+
if (!typeRef) return `(json: any) => json`;
|
|
30
|
+
|
|
31
|
+
if (typeRef.kind === "named") {
|
|
32
|
+
return `models.Mappers.${namespace}.${typeRef.value}.fromJson`;
|
|
33
|
+
} else if (
|
|
34
|
+
typeRef.kind === "list" &&
|
|
35
|
+
(typeRef.value as AxiomTypeRef).kind === "named"
|
|
36
|
+
) {
|
|
37
|
+
const innerName = (typeRef.value as any).value;
|
|
38
|
+
return `(json: any[]) => json.map(models.Mappers.${namespace}.${innerName}.fromJson)`;
|
|
129
39
|
}
|
|
130
|
-
lines.push(` }`);
|
|
131
|
-
lines.push(`};\n`);
|
|
132
40
|
|
|
133
|
-
return
|
|
41
|
+
return `(json: any) => json`;
|
|
134
42
|
}
|
|
135
43
|
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
ns: string,
|
|
139
|
-
camelNs: string,
|
|
44
|
+
export function generateSDKContent(
|
|
45
|
+
contracts: Record<string, ContractPayload>,
|
|
140
46
|
isReact: boolean,
|
|
141
47
|
): string {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const argsMapping = params
|
|
158
|
-
.map(
|
|
159
|
-
(p: any) =>
|
|
160
|
-
`if (args && '${camelCase(p.name)}' in args) { mappedArgs["${p.name}"] = (args as any)["${camelCase(p.name)}"]; delete mappedArgs["${camelCase(p.name)}"]; }`,
|
|
161
|
-
)
|
|
162
|
-
.join("\n ");
|
|
163
|
-
|
|
164
|
-
if (params.length === 0) {
|
|
48
|
+
let content = `// GENERATED CODE – DO NOT EDIT.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\n`;
|
|
49
|
+
|
|
50
|
+
if (!isReact) {
|
|
51
|
+
content += `import * as models from './models.js';\n\n`;
|
|
52
|
+
} else {
|
|
53
|
+
content += `import * as models from './models.js';\n`;
|
|
54
|
+
content += `import { useAxiomQuery, useAxiomMutation, setAuthToken, clearAuthToken, axiomQueryManager } from "atmx-react";\n`;
|
|
55
|
+
content += `import type { AxiomQueryDef } from "atmx-react";\n\n`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 1. Generate Individual Modules
|
|
59
|
+
for (const [namespace, contract] of Object.entries(contracts)) {
|
|
60
|
+
content += `export const ${namespace}Module = {\n`;
|
|
61
|
+
content += ` axiom: {\n`;
|
|
62
|
+
content += ` setAuthToken(methodName: string, token: string) {\n`;
|
|
165
63
|
if (isReact) {
|
|
166
|
-
|
|
167
|
-
return `
|
|
168
|
-
get${pascalCase(ep.name)}Def(args?: Record<string, any>): AxiomQueryDef<${returnType}> {
|
|
169
|
-
return {
|
|
170
|
-
namespace: "${ns}", name: "${ep.name}", endpointId: ${ep.id},
|
|
171
|
-
method: "${ep.method ? ep.method.toUpperCase() : "GET"}", path: "${ep.path}",
|
|
172
|
-
args: args || {}, decoder: ${decLogic}, serializer: (p: any) => p, isStream: ${ep.isStream === true}
|
|
173
|
-
};
|
|
174
|
-
},
|
|
175
|
-
use${pascalCase(ep.name)}(${isQuery ? "options?: { enabled?: boolean }" : ""}) {
|
|
176
|
-
${isQuery ? `return useAxiomQuery<${returnType}>(this.get${pascalCase(ep.name)}Def(), options);` : `return useAxiomMutation<${returnType}, void | Record<string,any>>((a) => this.get${pascalCase(ep.name)}Def(a));`}
|
|
177
|
-
},`;
|
|
64
|
+
content += ` setAuthToken("${namespace}", methodName, token);\n`;
|
|
178
65
|
} else {
|
|
179
|
-
|
|
180
|
-
${camelCase(ep.name)}(args?: Record<string, any>): string {
|
|
181
|
-
const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';
|
|
182
|
-
return \`${ns}.${ep.name}(\${argsStr})\`;
|
|
183
|
-
},`;
|
|
66
|
+
content += ` (window as any).atmx?.setAuthToken("${namespace}", methodName, token);\n`;
|
|
184
67
|
}
|
|
68
|
+
content += ` },\n`;
|
|
69
|
+
content += ` clearAuthToken(methodName: string) {\n`;
|
|
70
|
+
if (isReact) {
|
|
71
|
+
content += ` clearAuthToken("${namespace}", methodName);\n`;
|
|
72
|
+
} else {
|
|
73
|
+
content += ` (window as any).atmx?.clearAuthToken("${namespace}", methodName);\n`;
|
|
74
|
+
}
|
|
75
|
+
content += ` },\n`;
|
|
76
|
+
content += ` connect(methodName: string, args?: Record<string, any>) {\n`;
|
|
77
|
+
if (isReact) {
|
|
78
|
+
content += ` const def = (${namespace}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);\n`;
|
|
79
|
+
content += ` axiomQueryManager.connect(def);\n`;
|
|
80
|
+
} else {
|
|
81
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
82
|
+
content += ` (window as any).atmx?.connect(\`${namespace}.\${methodName}(\${argsStr})\`);\n`;
|
|
83
|
+
}
|
|
84
|
+
content += ` },\n`;
|
|
85
|
+
content += ` disconnect(methodName: string, args?: Record<string, any>) {\n`;
|
|
86
|
+
if (isReact) {
|
|
87
|
+
content += ` const def = (${namespace}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);\n`;
|
|
88
|
+
content += ` axiomQueryManager.disconnect(def);\n`;
|
|
89
|
+
} else {
|
|
90
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
91
|
+
content += ` (window as any).atmx?.disconnect(\`${namespace}.\${methodName}(\${argsStr})\`);\n`;
|
|
92
|
+
}
|
|
93
|
+
content += ` },\n`;
|
|
94
|
+
content += ` send(methodName: string, payload: any, args?: Record<string, any>) {\n`;
|
|
95
|
+
if (isReact) {
|
|
96
|
+
content += ` const def = (${namespace}Module as any)[\`get\${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Def\`](args);\n`;
|
|
97
|
+
content += ` axiomQueryManager.send(def, payload);\n`;
|
|
98
|
+
} else {
|
|
99
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
100
|
+
content += ` (window as any).atmx?.send(\`${namespace}.\${methodName}(\${argsStr})\`, payload);\n`;
|
|
101
|
+
}
|
|
102
|
+
content += ` }\n`;
|
|
103
|
+
content += ` },\n\n`;
|
|
104
|
+
|
|
105
|
+
// Extract endpoints handling objects vs arrays
|
|
106
|
+
const endpoints: AxiomEndpoint[] = Array.isArray(contract.ir.endpoints)
|
|
107
|
+
? contract.ir.endpoints
|
|
108
|
+
: Object.values(contract.ir.endpoints || {});
|
|
109
|
+
|
|
110
|
+
for (const endpoint of endpoints) {
|
|
111
|
+
const fnName = endpoint.name.replace(/_([a-z])/g, (g) =>
|
|
112
|
+
g[1].toUpperCase(),
|
|
113
|
+
);
|
|
114
|
+
const capFnName = fnName.charAt(0).toUpperCase() + fnName.slice(1);
|
|
115
|
+
|
|
116
|
+
if (isReact) {
|
|
117
|
+
const tsType = getTsType(namespace, endpoint.returnType);
|
|
118
|
+
const decoder = getDecoder(namespace, endpoint.returnType);
|
|
119
|
+
|
|
120
|
+
content += ` get${capFnName}Def(\n`;
|
|
121
|
+
content += ` args?: Record<string, any>,\n`;
|
|
122
|
+
content += ` ): AxiomQueryDef<${tsType}> {\n`;
|
|
123
|
+
content += ` return {\n`;
|
|
124
|
+
content += ` namespace: "${namespace}",\n`;
|
|
125
|
+
content += ` name: "${endpoint.name}",\n`;
|
|
126
|
+
content += ` endpointId: ${endpoint.id || 0},\n`;
|
|
127
|
+
content += ` method: "${endpoint.method}",\n`;
|
|
128
|
+
content += ` path: "${endpoint.path}",\n`;
|
|
129
|
+
content += ` args: args || {},\n`;
|
|
130
|
+
content += ` decoder: ${decoder},\n`;
|
|
131
|
+
content += ` serializer: (p: any) => p,\n`;
|
|
132
|
+
content += ` isStream: ${endpoint.isStream ? "true" : "false"},\n`;
|
|
133
|
+
content += ` };\n`;
|
|
134
|
+
content += ` },\n`;
|
|
135
|
+
|
|
136
|
+
if (endpoint.method === "GET" || endpoint.method === "WS") {
|
|
137
|
+
content += ` use${capFnName}(options?: { enabled?: boolean }) {\n`;
|
|
138
|
+
content += ` return useAxiomQuery<${tsType}>(\n`;
|
|
139
|
+
content += ` this.get${capFnName}Def(),\n`;
|
|
140
|
+
content += ` options,\n`;
|
|
141
|
+
content += ` );\n`;
|
|
142
|
+
content += ` },\n`;
|
|
143
|
+
} else {
|
|
144
|
+
content += ` use${capFnName}(options?: any) {\n`;
|
|
145
|
+
content += ` return useAxiomMutation<${tsType}>(\n`;
|
|
146
|
+
content += ` this.get${capFnName}Def(),\n`;
|
|
147
|
+
content += ` options,\n`;
|
|
148
|
+
content += ` );\n`;
|
|
149
|
+
content += ` },\n`;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// Vanilla Web generator logic
|
|
153
|
+
content += ` ${fnName}(args?: Record<string, any>): string {\n`;
|
|
154
|
+
content += ` const argsStr = args && Object.keys(args).length > 0 ? JSON.stringify(args) : '';\n`;
|
|
155
|
+
content += ` return \`${namespace}.${endpoint.name}(\${argsStr})\`;\n`;
|
|
156
|
+
content += ` },\n`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
content += `};\n\n`;
|
|
185
160
|
}
|
|
186
161
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
(p: any) => p.source === "body",
|
|
192
|
-
) as AxiomParameter;
|
|
193
|
-
const payloadLogic = bodyParam
|
|
194
|
-
? `const payload = (args as any)?.${camelCase(bodyParam.name)};`
|
|
195
|
-
: `const payload = undefined;`;
|
|
196
|
-
const decLogic = generateLambda(ep.returnType, "fromJson", camelNs);
|
|
197
|
-
const serLogic = bodyParam
|
|
198
|
-
? generateLambda(bodyParam.typeRef, "toJson", camelNs)
|
|
199
|
-
: `(p: any) => p`;
|
|
200
|
-
|
|
201
|
-
return `
|
|
202
|
-
get${pascalCase(ep.name)}Def(args?: ${argType}): AxiomQueryDef<${returnType}> {
|
|
203
|
-
${payloadLogic}
|
|
204
|
-
const mappedArgs: any = { ...(args || {}) };
|
|
205
|
-
${argsMapping}
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
namespace: "${ns}", name: "${ep.name}", endpointId: ${ep.id},
|
|
209
|
-
method: "${ep.method ? ep.method.toUpperCase() : "GET"}", path: "${ep.path}",
|
|
210
|
-
payload: payload, args: mappedArgs, decoder: ${decLogic}, serializer: ${serLogic}, isStream: ${ep.isStream === true}
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
use${pascalCase(ep.name)}(${isQuery ? `args?: ${argType}, options?: { enabled?: boolean }` : `args?: ${argType}`}) {
|
|
214
|
-
${isQuery ? `return useAxiomQuery<${returnType}>(this.get${pascalCase(ep.name)}Def(args), options);` : `return useAxiomMutation<${returnType}, ${argType}>((a) => this.get${pascalCase(ep.name)}Def(a || args));`}
|
|
215
|
-
},`;
|
|
216
|
-
} else {
|
|
217
|
-
return `
|
|
218
|
-
${camelCase(ep.name)}(args?: ${argType}): string {
|
|
219
|
-
const mappedArgs: any = { ...(args || {}) };
|
|
220
|
-
${argsMapping}
|
|
221
|
-
const argsStr = Object.keys(mappedArgs).length > 0 ? JSON.stringify(mappedArgs) : '';
|
|
222
|
-
return \`${ns}.${ep.name}(\${argsStr})\`;
|
|
223
|
-
},`;
|
|
162
|
+
// 2. Generate the Smart Proxy SDK
|
|
163
|
+
content += `const internalSdk: Record<string, any> = {\n`;
|
|
164
|
+
for (const namespace of Object.keys(contracts)) {
|
|
165
|
+
content += ` ${namespace}: ${namespace}Module,\n`;
|
|
224
166
|
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
167
|
+
content += `};\n\n`;
|
|
168
|
+
|
|
169
|
+
content += `// ✨ The Magic Proxy: Safely intercepts Alpine.js evaluations during boot!\n`;
|
|
170
|
+
content += `export const sdk = new Proxy(internalSdk, {\n`;
|
|
171
|
+
content += ` get(target: any, prop: string, receiver: any) {\n`;
|
|
172
|
+
content += ` if (prop in target) {\n`;
|
|
173
|
+
content += ` return Reflect.get(target, prop, receiver);\n`;
|
|
174
|
+
content += ` }\n`;
|
|
175
|
+
content += ` // If Alpine tries to access a namespace that doesn't exist yet, return a nested Proxy!\n`;
|
|
176
|
+
content += ` return new Proxy({}, {\n`;
|
|
177
|
+
content += ` get(subTarget: any, subProp: string) {\n`;
|
|
178
|
+
content += ` return () => \`\${String(prop)}.\${String(subProp)}()\`;\n`;
|
|
179
|
+
content += ` }\n`;
|
|
180
|
+
content += ` });\n`;
|
|
181
|
+
content += ` }\n`;
|
|
182
|
+
content += `});\n\n`;
|
|
183
|
+
|
|
184
|
+
content += `// Auto-attach to window for Alpine.js immediate hydration\n`;
|
|
185
|
+
content += `if (typeof window !== "undefined") {\n`;
|
|
186
|
+
content += ` (window as any).sdk = sdk;\n`;
|
|
187
|
+
content += `}\n\n`;
|
|
188
|
+
|
|
189
|
+
// 3. Generate Default Config
|
|
190
|
+
content += `export const AxiomDefaultConfig = {\n`;
|
|
191
|
+
content += ` contracts: {\n`;
|
|
192
|
+
for (const [ns, def] of Object.entries(contracts)) {
|
|
193
|
+
// Determine file path or default to namespace
|
|
194
|
+
const contractPath = def.file ? def.file : `/${ns}.axiom`;
|
|
195
|
+
|
|
196
|
+
content += ` "${ns}": {\n`;
|
|
197
|
+
content += ` contractUrl: "${contractPath}",\n`;
|
|
198
|
+
content += ` baseUrl: "${def.baseUrl}"\n`;
|
|
199
|
+
content += ` },\n`;
|
|
200
|
+
}
|
|
201
|
+
content += ` }\n`;
|
|
202
|
+
content += `};\n`;
|
|
240
203
|
|
|
241
|
-
|
|
242
|
-
const primitives = [
|
|
243
|
-
"string",
|
|
244
|
-
"number",
|
|
245
|
-
"boolean",
|
|
246
|
-
"Date",
|
|
247
|
-
"Uint8Array",
|
|
248
|
-
"void",
|
|
249
|
-
"any",
|
|
250
|
-
];
|
|
251
|
-
if (!type || primitives.includes(type)) return type;
|
|
252
|
-
if (type.endsWith("[]")) return `${prefixModels(type.slice(0, -2))}[]`;
|
|
253
|
-
if (type.startsWith("models.")) return type;
|
|
254
|
-
return `models.${type}`;
|
|
204
|
+
return content;
|
|
255
205
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,11 @@ import * as path from "path";
|
|
|
5
5
|
import * as toml from "@iarna/toml";
|
|
6
6
|
import { MultiIR } from "./types";
|
|
7
7
|
import { generateModels } from "./generators/model-generator";
|
|
8
|
-
|
|
8
|
+
// ✨ FIX: Import generateSDKContent and ContractPayload
|
|
9
|
+
import {
|
|
10
|
+
generateSDKContent,
|
|
11
|
+
ContractPayload,
|
|
12
|
+
} from "./generators/sdk-generator";
|
|
9
13
|
import { normalizeIr } from "./generators/utils";
|
|
10
14
|
|
|
11
15
|
const program = new Command();
|
|
@@ -39,6 +43,7 @@ program
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const multiIr: MultiIR = {};
|
|
46
|
+
const generatorPayload: Record<string, ContractPayload> = {}; // ✨ NEW: Payload for SDK
|
|
42
47
|
const projectRoot = path.dirname(configPath); // Frontend project root
|
|
43
48
|
|
|
44
49
|
// 2. Loop through contracts
|
|
@@ -60,15 +65,25 @@ program
|
|
|
60
65
|
if (!rawFile.ir) continue;
|
|
61
66
|
|
|
62
67
|
multiIr[namespace] = normalizeIr(rawFile.ir);
|
|
68
|
+
|
|
69
|
+
// ✨ NEW: Combine IR with TOML config for the SDK generator
|
|
70
|
+
generatorPayload[namespace] = {
|
|
71
|
+
ir: multiIr[namespace],
|
|
72
|
+
baseUrl: (contract as any).base_url || "http://localhost:8080",
|
|
73
|
+
file: `/${namespace}.axiom`,
|
|
74
|
+
};
|
|
75
|
+
|
|
63
76
|
console.log(`✅ Loaded contract: [${namespace}] -> ${axiomFilePath}`);
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
await fs.ensureDir(outputDir);
|
|
67
80
|
|
|
81
|
+
// 3. Generate Models (Needs raw MultiIR)
|
|
68
82
|
const modelsContent = generateModels(multiIr);
|
|
69
83
|
await fs.writeFile(path.join(outputDir, "models.ts"), modelsContent);
|
|
70
84
|
|
|
71
|
-
|
|
85
|
+
// 4. Generate SDK (Needs enriched ContractPayload)
|
|
86
|
+
const sdkContent = generateSDKContent(generatorPayload, options.react);
|
|
72
87
|
await fs.writeFile(path.join(outputDir, "sdk.ts"), sdkContent);
|
|
73
88
|
|
|
74
89
|
console.log(
|