atmx-cli 0.137.0 → 0.138.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators/model-generator.js +31 -11
- package/dist/generators/sdk-generator.js +7 -3
- package/dist/generators/utils.js +10 -10
- package/dist/index.js +3 -0
- package/package.json +1 -1
- package/src/generators/model-generator.ts +31 -12
- package/src/generators/sdk-generator.ts +7 -4
- package/src/generators/utils.ts +10 -16
- package/src/index.ts +4 -0
- package/src/types.ts +21 -0
|
@@ -19,20 +19,44 @@ function generateModels(multiIr) {
|
|
|
19
19
|
: Object.values(ir.models || {});
|
|
20
20
|
enumsList.forEach((en) => sections.push(generateEnum(en)));
|
|
21
21
|
modelsList.forEach((model) => sections.push(generateInterface(model, camelNs)));
|
|
22
|
+
sections.push(generateDomainProjectionTypes(ir.domain, modelsList));
|
|
22
23
|
sections.push(`}\n`);
|
|
23
24
|
}
|
|
24
25
|
sections.push(generateMappers(multiIr));
|
|
25
26
|
return sections.join("\n");
|
|
26
27
|
}
|
|
28
|
+
function generateDomainProjectionTypes(domain, models) {
|
|
29
|
+
if (!domain?.projections || !domain?.entities)
|
|
30
|
+
return "";
|
|
31
|
+
const entities = domain.entities || {};
|
|
32
|
+
const projections = domain.projections || {};
|
|
33
|
+
const availableModels = new Set(models.map((model) => String(model?.name || "")));
|
|
34
|
+
const lines = [" export namespace Domain {"];
|
|
35
|
+
let count = 0;
|
|
36
|
+
for (const [name, projection] of Object.entries(projections)) {
|
|
37
|
+
const entity = entities[projection.entity];
|
|
38
|
+
const model = entity?.model;
|
|
39
|
+
const fields = Array.isArray(projection.fields) ? projection.fields : [];
|
|
40
|
+
if (!model || !availableModels.has(model) || fields.length === 0)
|
|
41
|
+
continue;
|
|
42
|
+
const fieldUnion = fields.map((field) => JSON.stringify((0, utils_1.camelCase)(field))).join(" | ");
|
|
43
|
+
lines.push(` export type ${(0, utils_1.pascalCase)(name)} = Pick<${(0, utils_1.pascalCase)(model)}, ${fieldUnion}>;`);
|
|
44
|
+
count += 1;
|
|
45
|
+
}
|
|
46
|
+
lines.push(" }");
|
|
47
|
+
return count ? lines.join("\n") : "";
|
|
48
|
+
}
|
|
27
49
|
function generateEnum(en) {
|
|
28
50
|
const name = (0, utils_1.pascalCase)(en.name);
|
|
29
51
|
const values = en.values.map((v) => ` ${(0, utils_1.pascalCase)(v)}: "${v}"`).join(",\n");
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
${
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
return [
|
|
53
|
+
"",
|
|
54
|
+
` export const ${name} = {`,
|
|
55
|
+
values,
|
|
56
|
+
" } as const;",
|
|
57
|
+
` export type ${name} = typeof ${name}[keyof typeof ${name}];`,
|
|
58
|
+
"",
|
|
59
|
+
].join("\n");
|
|
36
60
|
}
|
|
37
61
|
function generateInterface(model, ns) {
|
|
38
62
|
const name = (0, utils_1.pascalCase)(model.name);
|
|
@@ -42,11 +66,7 @@ function generateInterface(model, ns) {
|
|
|
42
66
|
return ` ${(0, utils_1.camelCase)(f.name)}${f.isOptional ? "?" : ""}: ${type};`;
|
|
43
67
|
})
|
|
44
68
|
.join("\n");
|
|
45
|
-
return `
|
|
46
|
-
export interface ${name} {
|
|
47
|
-
${fields}
|
|
48
|
-
}
|
|
49
|
-
`;
|
|
69
|
+
return ["", ` export interface ${name} {`, fields, " }", ""].join("\n");
|
|
50
70
|
}
|
|
51
71
|
function generateMappers(multiIr) {
|
|
52
72
|
const lines = [`export const Mappers: Record<string, any> = {`];
|
|
@@ -16,7 +16,9 @@ function moduleIdentifier(namespace) {
|
|
|
16
16
|
return `${namespaceIdentifier(namespace)}Module`;
|
|
17
17
|
}
|
|
18
18
|
// Helper to convert Axiom TypeRef to TypeScript Types
|
|
19
|
-
function getTsType(namespace, typeRef) {
|
|
19
|
+
function getTsType(namespace, typeRef, projection) {
|
|
20
|
+
if (projection)
|
|
21
|
+
return `models.${namespaceIdentifier(namespace)}.Domain.${(0, utils_js_1.pascalCase)(projection)}`;
|
|
20
22
|
if (!typeRef)
|
|
21
23
|
return "any";
|
|
22
24
|
if (typeRef.kind === "named") {
|
|
@@ -128,8 +130,10 @@ function generateSDKContent(contracts, isReact) {
|
|
|
128
130
|
const fnName = endpoint.name.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
129
131
|
const capFnName = fnName.charAt(0).toUpperCase() + fnName.slice(1);
|
|
130
132
|
if (isReact) {
|
|
131
|
-
const tsType = getTsType(namespace, endpoint.returnType);
|
|
132
|
-
const decoder =
|
|
133
|
+
const tsType = getTsType(namespace, endpoint.returnType, endpoint.responseProjection);
|
|
134
|
+
const decoder = endpoint.responseProjection
|
|
135
|
+
? `(json: any) => json as ${tsType}`
|
|
136
|
+
: getDecoder(namespace, endpoint.returnType);
|
|
133
137
|
content += ` get${capFnName}Def(\n`;
|
|
134
138
|
content += ` args?: Record<string, any>,\n`;
|
|
135
139
|
content += ` ): AxiomQueryDef<${tsType}> {\n`;
|
package/dist/generators/utils.js
CHANGED
|
@@ -26,22 +26,22 @@ function normalizeIr(obj) {
|
|
|
26
26
|
const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
27
27
|
newObj[camelKey] = normalizeIr(obj[key]);
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Only the IR root owns endpoint/model/enum maps. A previous generic
|
|
30
|
+
// conversion treated *any* field named `endpoints` as an IR map, which
|
|
31
|
+
// corrupted valid model fields such as AgentSemanticView.endpoints into an
|
|
32
|
+
// array of field-property values. Keep the structural normalization tied
|
|
33
|
+
// to the actual IR shape.
|
|
34
|
+
const isIrRoot = typeof newObj.serviceName === "string";
|
|
35
|
+
if (isIrRoot && newObj.endpoints && typeof newObj.endpoints === "object" && !Array.isArray(newObj.endpoints)) {
|
|
32
36
|
newObj.endpoints = Object.values(newObj.endpoints);
|
|
33
37
|
}
|
|
34
|
-
if (newObj.models &&
|
|
35
|
-
typeof newObj.models === "object" &&
|
|
36
|
-
!Array.isArray(newObj.models)) {
|
|
38
|
+
if (isIrRoot && newObj.models && typeof newObj.models === "object" && !Array.isArray(newObj.models)) {
|
|
37
39
|
newObj.models = Object.values(newObj.models);
|
|
38
40
|
}
|
|
39
|
-
if (newObj.enums &&
|
|
40
|
-
typeof newObj.enums === "object" &&
|
|
41
|
-
!Array.isArray(newObj.enums)) {
|
|
41
|
+
if (isIrRoot && newObj.enums && typeof newObj.enums === "object" && !Array.isArray(newObj.enums)) {
|
|
42
42
|
newObj.enums = Object.values(newObj.enums);
|
|
43
43
|
}
|
|
44
|
-
if (Array.isArray(newObj.models)) {
|
|
44
|
+
if (isIrRoot && Array.isArray(newObj.models)) {
|
|
45
45
|
newObj.models = newObj.models.map((model) => {
|
|
46
46
|
if (model.fields &&
|
|
47
47
|
typeof model.fields === "object" &&
|
package/dist/index.js
CHANGED
|
@@ -81,6 +81,9 @@ program
|
|
|
81
81
|
if (!rawFile.ir)
|
|
82
82
|
continue;
|
|
83
83
|
multiIr[namespace] = (0, utils_1.normalizeIr)(rawFile.ir);
|
|
84
|
+
// Domain Model v1 is carried inside `ir` and drives generated projection
|
|
85
|
+
// types. The immutable artifact remains the source of truth; the TOML
|
|
86
|
+
// file continues to contain only dependency and verification metadata.
|
|
84
87
|
// ✨ NEW: Combine IR with TOML config for the SDK generator
|
|
85
88
|
generatorPayload[namespace] = {
|
|
86
89
|
ir: multiIr[namespace],
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FILE: atmx-cli/src/generators/model-generator.ts
|
|
2
|
-
import { AxiomEnum, AxiomModel, MultiIR } from "../types";
|
|
2
|
+
import { AxiomDomainModel, AxiomEnum, AxiomModel, MultiIR } from "../types";
|
|
3
3
|
import { pascalCase, camelCase, mapTypeToTs } from "./utils";
|
|
4
4
|
|
|
5
5
|
export function generateModels(multiIr: MultiIR): string {
|
|
@@ -24,6 +24,7 @@ export function generateModels(multiIr: MultiIR): string {
|
|
|
24
24
|
modelsList.forEach((model: any) =>
|
|
25
25
|
sections.push(generateInterface(model, camelNs)),
|
|
26
26
|
);
|
|
27
|
+
sections.push(generateDomainProjectionTypes(ir.domain, modelsList));
|
|
27
28
|
|
|
28
29
|
sections.push(`}\n`);
|
|
29
30
|
}
|
|
@@ -32,15 +33,37 @@ export function generateModels(multiIr: MultiIR): string {
|
|
|
32
33
|
return sections.join("\n");
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function generateDomainProjectionTypes(domain: AxiomDomainModel | undefined, models: any[]): string {
|
|
37
|
+
if (!domain?.projections || !domain?.entities) return "";
|
|
38
|
+
const entities = domain.entities || {};
|
|
39
|
+
const projections = domain.projections || {};
|
|
40
|
+
const availableModels = new Set(models.map((model) => String(model?.name || "")));
|
|
41
|
+
const lines = [" export namespace Domain {"];
|
|
42
|
+
let count = 0;
|
|
43
|
+
for (const [name, projection] of Object.entries(projections)) {
|
|
44
|
+
const entity = entities[(projection as any).entity];
|
|
45
|
+
const model = entity?.model;
|
|
46
|
+
const fields = Array.isArray((projection as any).fields) ? (projection as any).fields : [];
|
|
47
|
+
if (!model || !availableModels.has(model) || fields.length === 0) continue;
|
|
48
|
+
const fieldUnion = fields.map((field: string) => JSON.stringify(camelCase(field))).join(" | ");
|
|
49
|
+
lines.push(` export type ${pascalCase(name)} = Pick<${pascalCase(model)}, ${fieldUnion}>;`);
|
|
50
|
+
count += 1;
|
|
51
|
+
}
|
|
52
|
+
lines.push(" }");
|
|
53
|
+
return count ? lines.join("\n") : "";
|
|
54
|
+
}
|
|
55
|
+
|
|
35
56
|
function generateEnum(en: AxiomEnum): string {
|
|
36
57
|
const name = pascalCase(en.name);
|
|
37
58
|
const values = en.values.map((v) => ` ${pascalCase(v)}: "${v}"`).join(",\n");
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
${
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
return [
|
|
60
|
+
"",
|
|
61
|
+
` export const ${name} = {`,
|
|
62
|
+
values,
|
|
63
|
+
" } as const;",
|
|
64
|
+
` export type ${name} = typeof ${name}[keyof typeof ${name}];`,
|
|
65
|
+
"",
|
|
66
|
+
].join("\n");
|
|
44
67
|
}
|
|
45
68
|
|
|
46
69
|
function generateInterface(model: AxiomModel, ns: string): string {
|
|
@@ -52,11 +75,7 @@ function generateInterface(model: AxiomModel, ns: string): string {
|
|
|
52
75
|
})
|
|
53
76
|
.join("\n");
|
|
54
77
|
|
|
55
|
-
return `
|
|
56
|
-
export interface ${name} {
|
|
57
|
-
${fields}
|
|
58
|
-
}
|
|
59
|
-
`;
|
|
78
|
+
return ["", ` export interface ${name} {`, fields, " }", ""].join("\n");
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
function generateMappers(multiIr: MultiIR): string {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AxiomIR, AxiomEndpoint, AxiomTypeRef } from "../types.js";
|
|
2
|
-
import { camelCase } from "./utils.js";
|
|
2
|
+
import { camelCase, pascalCase } from "./utils.js";
|
|
3
3
|
|
|
4
4
|
export interface ContractPayload {
|
|
5
5
|
ir: AxiomIR;
|
|
@@ -24,7 +24,8 @@ function moduleIdentifier(namespace: string): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Helper to convert Axiom TypeRef to TypeScript Types
|
|
27
|
-
function getTsType(namespace: string, typeRef?: AxiomTypeRef): string {
|
|
27
|
+
function getTsType(namespace: string, typeRef?: AxiomTypeRef, projection?: string): string {
|
|
28
|
+
if (projection) return `models.${namespaceIdentifier(namespace)}.Domain.${pascalCase(projection)}`;
|
|
28
29
|
if (!typeRef) return "any";
|
|
29
30
|
if (typeRef.kind === "named") {
|
|
30
31
|
return `models.${namespaceIdentifier(namespace)}.${typeRef.value}`;
|
|
@@ -139,8 +140,10 @@ export function generateSDKContent(
|
|
|
139
140
|
const capFnName = fnName.charAt(0).toUpperCase() + fnName.slice(1);
|
|
140
141
|
|
|
141
142
|
if (isReact) {
|
|
142
|
-
const tsType = getTsType(namespace, endpoint.returnType);
|
|
143
|
-
const decoder =
|
|
143
|
+
const tsType = getTsType(namespace, endpoint.returnType, endpoint.responseProjection);
|
|
144
|
+
const decoder = endpoint.responseProjection
|
|
145
|
+
? `(json: any) => json as ${tsType}`
|
|
146
|
+
: getDecoder(namespace, endpoint.returnType);
|
|
144
147
|
|
|
145
148
|
content += ` get${capFnName}Def(\n`;
|
|
146
149
|
content += ` args?: Record<string, any>,\n`;
|
package/src/generators/utils.ts
CHANGED
|
@@ -20,28 +20,22 @@ export function normalizeIr(obj: any): any {
|
|
|
20
20
|
const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
21
21
|
newObj[camelKey] = normalizeIr(obj[key]);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
// Only the IR root owns endpoint/model/enum maps. A previous generic
|
|
24
|
+
// conversion treated *any* field named `endpoints` as an IR map, which
|
|
25
|
+
// corrupted valid model fields such as AgentSemanticView.endpoints into an
|
|
26
|
+
// array of field-property values. Keep the structural normalization tied
|
|
27
|
+
// to the actual IR shape.
|
|
28
|
+
const isIrRoot = typeof newObj.serviceName === "string";
|
|
29
|
+
if (isIrRoot && newObj.endpoints && typeof newObj.endpoints === "object" && !Array.isArray(newObj.endpoints)) {
|
|
28
30
|
newObj.endpoints = Object.values(newObj.endpoints);
|
|
29
31
|
}
|
|
30
|
-
if (
|
|
31
|
-
newObj.models &&
|
|
32
|
-
typeof newObj.models === "object" &&
|
|
33
|
-
!Array.isArray(newObj.models)
|
|
34
|
-
) {
|
|
32
|
+
if (isIrRoot && newObj.models && typeof newObj.models === "object" && !Array.isArray(newObj.models)) {
|
|
35
33
|
newObj.models = Object.values(newObj.models);
|
|
36
34
|
}
|
|
37
|
-
if (
|
|
38
|
-
newObj.enums &&
|
|
39
|
-
typeof newObj.enums === "object" &&
|
|
40
|
-
!Array.isArray(newObj.enums)
|
|
41
|
-
) {
|
|
35
|
+
if (isIrRoot && newObj.enums && typeof newObj.enums === "object" && !Array.isArray(newObj.enums)) {
|
|
42
36
|
newObj.enums = Object.values(newObj.enums);
|
|
43
37
|
}
|
|
44
|
-
if (Array.isArray(newObj.models)) {
|
|
38
|
+
if (isIrRoot && Array.isArray(newObj.models)) {
|
|
45
39
|
newObj.models = newObj.models.map((model: any) => {
|
|
46
40
|
if (
|
|
47
41
|
model.fields &&
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,10 @@ program
|
|
|
66
66
|
|
|
67
67
|
multiIr[namespace] = normalizeIr(rawFile.ir);
|
|
68
68
|
|
|
69
|
+
// Domain Model v1 is carried inside `ir` and drives generated projection
|
|
70
|
+
// types. The immutable artifact remains the source of truth; the TOML
|
|
71
|
+
// file continues to contain only dependency and verification metadata.
|
|
72
|
+
|
|
69
73
|
// ✨ NEW: Combine IR with TOML config for the SDK generator
|
|
70
74
|
generatorPayload[namespace] = {
|
|
71
75
|
ir: multiIr[namespace],
|
package/src/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ export interface AxiomIR {
|
|
|
3
3
|
endpoints: AxiomEndpoint[];
|
|
4
4
|
models: Record<string, AxiomModel>;
|
|
5
5
|
enums: Record<string, AxiomEnum>;
|
|
6
|
+
domain?: AxiomDomainModel;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export interface AxiomEndpoint {
|
|
@@ -14,6 +15,8 @@ export interface AxiomEndpoint {
|
|
|
14
15
|
returnType: AxiomTypeRef;
|
|
15
16
|
returnIsOptional: boolean;
|
|
16
17
|
isStream: boolean;
|
|
18
|
+
requestProjection?: string;
|
|
19
|
+
responseProjection?: string;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export interface AxiomParameter {
|
|
@@ -45,6 +48,24 @@ export interface AxiomEnum {
|
|
|
45
48
|
values: string[];
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
export interface AxiomDomainModel {
|
|
52
|
+
entities: Record<string, AxiomDomainEntity>;
|
|
53
|
+
projections: Record<string, AxiomDomainProjection>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AxiomDomainEntity {
|
|
57
|
+
id?: string;
|
|
58
|
+
model: string;
|
|
59
|
+
key: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface AxiomDomainProjection {
|
|
63
|
+
id?: string;
|
|
64
|
+
entity: string;
|
|
65
|
+
fields: string[];
|
|
66
|
+
audience: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
export interface AtmxContractConfig {
|
|
49
70
|
file: string; // Path relative to the config file (e.g., "./auth.axiom")
|
|
50
71
|
baseUrl: string; // The URL for runtime (not used during code generation, but part of schema)
|