@mandujs/core 0.16.0 → 0.18.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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/paths.ts +16 -0
- package/src/resource/__tests__/backward-compat.test.ts +302 -0
- package/src/resource/__tests__/edge-cases.test.ts +514 -0
- package/src/resource/__tests__/fixtures.ts +203 -0
- package/src/resource/__tests__/generator.test.ts +324 -0
- package/src/resource/__tests__/performance.test.ts +311 -0
- package/src/resource/__tests__/schema.test.ts +184 -0
- package/src/resource/generator.ts +277 -0
- package/src/resource/generators/client.ts +199 -0
- package/src/resource/generators/contract.ts +264 -0
- package/src/resource/generators/slot.ts +193 -0
- package/src/resource/generators/types.ts +83 -0
- package/src/resource/index.ts +42 -0
- package/src/resource/parser.ts +139 -0
- package/src/resource/schema.ts +252 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Contract Generator
|
|
3
|
+
* Generate Zod contract from resource definition
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ResourceDefinition, ResourceField } from "../schema";
|
|
7
|
+
import { getPluralName, getEnabledEndpoints } from "../schema";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate contract file for resource
|
|
11
|
+
*
|
|
12
|
+
* @returns Contract file content
|
|
13
|
+
*/
|
|
14
|
+
export function generateResourceContract(definition: ResourceDefinition): string {
|
|
15
|
+
const resourceName = definition.name;
|
|
16
|
+
const pascalName = toPascalCase(resourceName);
|
|
17
|
+
const pluralName = getPluralName(definition);
|
|
18
|
+
const endpoints = getEnabledEndpoints(definition);
|
|
19
|
+
|
|
20
|
+
// Generate schema definitions
|
|
21
|
+
const schemaDefinitions = generateSchemaDefinitions(definition);
|
|
22
|
+
|
|
23
|
+
// Generate request schemas
|
|
24
|
+
const requestSchemas = generateRequestSchemas(definition, endpoints);
|
|
25
|
+
|
|
26
|
+
// Generate response schemas
|
|
27
|
+
const responseSchemas = generateResponseSchemas(definition, pascalName);
|
|
28
|
+
|
|
29
|
+
return `// 📜 Mandu Resource Contract - ${resourceName}
|
|
30
|
+
// Auto-generated from resource definition
|
|
31
|
+
// DO NOT EDIT - Regenerated on every \`mandu generate\`
|
|
32
|
+
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
import { Mandu } from "@mandujs/core";
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// 🥟 Schema Definitions
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
${schemaDefinitions}
|
|
41
|
+
|
|
42
|
+
// ============================================
|
|
43
|
+
// 📜 Contract Definition
|
|
44
|
+
// ============================================
|
|
45
|
+
|
|
46
|
+
export default Mandu.contract({
|
|
47
|
+
description: "${definition.options?.description || `${pascalName} API`}",
|
|
48
|
+
tags: ${JSON.stringify(definition.options?.tags || [resourceName])},
|
|
49
|
+
|
|
50
|
+
request: {
|
|
51
|
+
${requestSchemas}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
response: {
|
|
55
|
+
${responseSchemas}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate schema definitions for fields
|
|
63
|
+
*/
|
|
64
|
+
function generateSchemaDefinitions(definition: ResourceDefinition): string {
|
|
65
|
+
const pascalName = toPascalCase(definition.name);
|
|
66
|
+
const fields = Object.entries(definition.fields);
|
|
67
|
+
|
|
68
|
+
// Generate individual field schemas
|
|
69
|
+
const fieldSchemas = fields.map(([name, field]) => {
|
|
70
|
+
const zodSchema = generateZodSchema(name, field);
|
|
71
|
+
return ` ${name}: ${zodSchema},`;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return `/**
|
|
75
|
+
* ${pascalName} Schema
|
|
76
|
+
*/
|
|
77
|
+
const ${pascalName}Schema = z.object({
|
|
78
|
+
${fieldSchemas.join("\n")}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* ${pascalName} Create Schema (exclude id, createdAt, updatedAt)
|
|
83
|
+
*/
|
|
84
|
+
const ${pascalName}CreateSchema = ${pascalName}Schema.omit({
|
|
85
|
+
id: true,
|
|
86
|
+
createdAt: true,
|
|
87
|
+
updatedAt: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* ${pascalName} Update Schema (all fields optional except id)
|
|
92
|
+
*/
|
|
93
|
+
const ${pascalName}UpdateSchema = ${pascalName}Schema.partial().required({ id: true });`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate Zod schema for a field
|
|
98
|
+
*/
|
|
99
|
+
function generateZodSchema(fieldName: string, field: ResourceField): string {
|
|
100
|
+
// Use custom schema if provided
|
|
101
|
+
if (field.schema) {
|
|
102
|
+
return "z.unknown() /* Custom schema */";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let schema: string;
|
|
106
|
+
|
|
107
|
+
switch (field.type) {
|
|
108
|
+
case "string":
|
|
109
|
+
schema = "z.string()";
|
|
110
|
+
break;
|
|
111
|
+
case "number":
|
|
112
|
+
schema = "z.number()";
|
|
113
|
+
break;
|
|
114
|
+
case "boolean":
|
|
115
|
+
schema = "z.boolean()";
|
|
116
|
+
break;
|
|
117
|
+
case "date":
|
|
118
|
+
schema = "z.string().datetime()";
|
|
119
|
+
break;
|
|
120
|
+
case "uuid":
|
|
121
|
+
schema = "z.string().uuid()";
|
|
122
|
+
break;
|
|
123
|
+
case "email":
|
|
124
|
+
schema = "z.string().email()";
|
|
125
|
+
break;
|
|
126
|
+
case "url":
|
|
127
|
+
schema = "z.string().url()";
|
|
128
|
+
break;
|
|
129
|
+
case "json":
|
|
130
|
+
schema = "z.record(z.unknown())";
|
|
131
|
+
break;
|
|
132
|
+
case "array":
|
|
133
|
+
const itemType = field.items || "unknown";
|
|
134
|
+
schema = `z.array(${generateZodTypeFromFieldType(itemType)})`;
|
|
135
|
+
break;
|
|
136
|
+
case "object":
|
|
137
|
+
schema = "z.record(z.unknown())";
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
schema = "z.unknown()";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add optional/required
|
|
144
|
+
if (!field.required) {
|
|
145
|
+
schema += ".optional()";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Add default
|
|
149
|
+
if (field.default !== undefined) {
|
|
150
|
+
const defaultValue = JSON.stringify(field.default);
|
|
151
|
+
schema += `.default(${defaultValue})`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add description
|
|
155
|
+
if (field.description) {
|
|
156
|
+
schema += `.describe("${field.description}")`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return schema;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate Zod type from FieldType
|
|
164
|
+
*/
|
|
165
|
+
function generateZodTypeFromFieldType(type: string): string {
|
|
166
|
+
switch (type) {
|
|
167
|
+
case "string":
|
|
168
|
+
return "z.string()";
|
|
169
|
+
case "number":
|
|
170
|
+
return "z.number()";
|
|
171
|
+
case "boolean":
|
|
172
|
+
return "z.boolean()";
|
|
173
|
+
case "date":
|
|
174
|
+
return "z.string().datetime()";
|
|
175
|
+
case "uuid":
|
|
176
|
+
return "z.string().uuid()";
|
|
177
|
+
case "email":
|
|
178
|
+
return "z.string().email()";
|
|
179
|
+
case "url":
|
|
180
|
+
return "z.string().url()";
|
|
181
|
+
default:
|
|
182
|
+
return "z.unknown()";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate request schemas based on enabled endpoints
|
|
188
|
+
*/
|
|
189
|
+
function generateRequestSchemas(definition: ResourceDefinition, endpoints: string[]): string {
|
|
190
|
+
const pascalName = toPascalCase(definition.name);
|
|
191
|
+
const schemas: string[] = [];
|
|
192
|
+
|
|
193
|
+
if (endpoints.includes("list")) {
|
|
194
|
+
const defaultLimit = definition.options?.pagination?.defaultLimit || 10;
|
|
195
|
+
const maxLimit = definition.options?.pagination?.maxLimit || 100;
|
|
196
|
+
|
|
197
|
+
schemas.push(` GET: {
|
|
198
|
+
query: z.object({
|
|
199
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
200
|
+
limit: z.coerce.number().int().min(1).max(${maxLimit}).default(${defaultLimit}),
|
|
201
|
+
}),
|
|
202
|
+
}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (endpoints.includes("create")) {
|
|
206
|
+
schemas.push(` POST: {
|
|
207
|
+
body: ${pascalName}CreateSchema,
|
|
208
|
+
}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (endpoints.includes("update")) {
|
|
212
|
+
schemas.push(` PUT: {
|
|
213
|
+
body: ${pascalName}UpdateSchema,
|
|
214
|
+
}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (endpoints.includes("delete")) {
|
|
218
|
+
schemas.push(` DELETE: {
|
|
219
|
+
// No body for DELETE
|
|
220
|
+
}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return schemas.join(",\n\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Generate response schemas
|
|
228
|
+
*/
|
|
229
|
+
function generateResponseSchemas(definition: ResourceDefinition, pascalName: string): string {
|
|
230
|
+
return ` 200: z.object({
|
|
231
|
+
data: z.union([${pascalName}Schema, z.array(${pascalName}Schema)]),
|
|
232
|
+
pagination: z.object({
|
|
233
|
+
page: z.number(),
|
|
234
|
+
limit: z.number(),
|
|
235
|
+
total: z.number(),
|
|
236
|
+
}).optional(),
|
|
237
|
+
}),
|
|
238
|
+
201: z.object({
|
|
239
|
+
data: ${pascalName}Schema,
|
|
240
|
+
}),
|
|
241
|
+
400: z.object({
|
|
242
|
+
error: z.string(),
|
|
243
|
+
details: z.array(z.object({
|
|
244
|
+
type: z.string(),
|
|
245
|
+
issues: z.array(z.object({
|
|
246
|
+
path: z.string(),
|
|
247
|
+
message: z.string(),
|
|
248
|
+
})),
|
|
249
|
+
})).optional(),
|
|
250
|
+
}),
|
|
251
|
+
404: z.object({
|
|
252
|
+
error: z.string(),
|
|
253
|
+
})`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Convert string to PascalCase
|
|
258
|
+
*/
|
|
259
|
+
function toPascalCase(str: string): string {
|
|
260
|
+
return str
|
|
261
|
+
.split(/[-_]/)
|
|
262
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
263
|
+
.join("");
|
|
264
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Slot Generator
|
|
3
|
+
* Generate slot templates for resource endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ResourceDefinition } from "../schema";
|
|
7
|
+
import { getPluralName, getEnabledEndpoints } from "../schema";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate slot file for resource
|
|
11
|
+
* IMPORTANT: This should only be generated ONCE - never overwrite existing slots!
|
|
12
|
+
*
|
|
13
|
+
* @returns Slot file content
|
|
14
|
+
*/
|
|
15
|
+
export function generateResourceSlot(definition: ResourceDefinition): string {
|
|
16
|
+
const resourceName = definition.name;
|
|
17
|
+
const pascalName = toPascalCase(resourceName);
|
|
18
|
+
const pluralName = getPluralName(definition);
|
|
19
|
+
const endpoints = getEnabledEndpoints(definition);
|
|
20
|
+
|
|
21
|
+
// Generate endpoint handlers
|
|
22
|
+
const handlers = generateHandlers(definition, endpoints, pascalName);
|
|
23
|
+
|
|
24
|
+
return `// 🥟 Mandu Filling - ${resourceName} Resource
|
|
25
|
+
// Pattern: /api/${pluralName}
|
|
26
|
+
// 이 파일에서 비즈니스 로직을 구현하세요.
|
|
27
|
+
|
|
28
|
+
import { Mandu } from "@mandujs/core";
|
|
29
|
+
import contract from "../contracts/${resourceName}.contract";
|
|
30
|
+
|
|
31
|
+
export default Mandu.filling()
|
|
32
|
+
${handlers}
|
|
33
|
+
|
|
34
|
+
// 💡 Contract 기반 사용법:
|
|
35
|
+
// ctx.input(contract, "GET") - Contract로 요청 검증 + 정규화
|
|
36
|
+
// ctx.output(contract, 200, data) - Contract로 응답 검증
|
|
37
|
+
// ctx.okContract(contract, data) - 200 OK (Contract 검증)
|
|
38
|
+
// ctx.createdContract(contract, data) - 201 Created (Contract 검증)
|
|
39
|
+
//
|
|
40
|
+
// 💡 데이터베이스 연동 예시:
|
|
41
|
+
// const { data } = await db.select().from(${pluralName}).where(eq(${pluralName}.id, id));
|
|
42
|
+
// return ctx.output(contract, 200, { data });
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate handlers for enabled endpoints
|
|
48
|
+
*/
|
|
49
|
+
function generateHandlers(
|
|
50
|
+
definition: ResourceDefinition,
|
|
51
|
+
endpoints: string[],
|
|
52
|
+
pascalName: string
|
|
53
|
+
): string {
|
|
54
|
+
const handlers: string[] = [];
|
|
55
|
+
|
|
56
|
+
if (endpoints.includes("list")) {
|
|
57
|
+
handlers.push(generateListHandler(definition, pascalName));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (endpoints.includes("get")) {
|
|
61
|
+
handlers.push(generateGetHandler(definition, pascalName));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (endpoints.includes("create")) {
|
|
65
|
+
handlers.push(generateCreateHandler(definition, pascalName));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (endpoints.includes("update")) {
|
|
69
|
+
handlers.push(generateUpdateHandler(definition, pascalName));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (endpoints.includes("delete")) {
|
|
73
|
+
handlers.push(generateDeleteHandler(definition, pascalName));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return handlers.join("\n\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate LIST handler (GET /api/resources)
|
|
81
|
+
*/
|
|
82
|
+
function generateListHandler(definition: ResourceDefinition, pascalName: string): string {
|
|
83
|
+
return ` // 📋 List ${pascalName}s
|
|
84
|
+
.get(async (ctx) => {
|
|
85
|
+
const input = await ctx.input(contract, "GET", ctx.params);
|
|
86
|
+
const { page, limit } = input;
|
|
87
|
+
|
|
88
|
+
// TODO: Implement database query
|
|
89
|
+
// const offset = (page - 1) * limit;
|
|
90
|
+
// const items = await db.select().from(${definition.name}s).limit(limit).offset(offset);
|
|
91
|
+
// const total = await db.select({ count: count() }).from(${definition.name}s);
|
|
92
|
+
|
|
93
|
+
const mockData = {
|
|
94
|
+
data: [], // Replace with actual data
|
|
95
|
+
pagination: {
|
|
96
|
+
page,
|
|
97
|
+
limit,
|
|
98
|
+
total: 0,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return ctx.output(contract, 200, mockData);
|
|
103
|
+
})`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate GET handler (GET /api/resources/:id)
|
|
108
|
+
*/
|
|
109
|
+
function generateGetHandler(definition: ResourceDefinition, pascalName: string): string {
|
|
110
|
+
return ` // 📄 Get Single ${pascalName}
|
|
111
|
+
.get(async (ctx) => {
|
|
112
|
+
const { id } = ctx.params;
|
|
113
|
+
|
|
114
|
+
// TODO: Implement database query
|
|
115
|
+
// const item = await db.select().from(${definition.name}s).where(eq(${definition.name}s.id, id)).limit(1);
|
|
116
|
+
// if (!item) return ctx.notFound("${pascalName} not found");
|
|
117
|
+
|
|
118
|
+
const mockData = {
|
|
119
|
+
data: { id, message: "${pascalName} details" }, // Replace with actual data
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return ctx.output(contract, 200, mockData);
|
|
123
|
+
})`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generate CREATE handler (POST /api/resources)
|
|
128
|
+
*/
|
|
129
|
+
function generateCreateHandler(definition: ResourceDefinition, pascalName: string): string {
|
|
130
|
+
return ` // ➕ Create ${pascalName}
|
|
131
|
+
.post(async (ctx) => {
|
|
132
|
+
const input = await ctx.input(contract, "POST", ctx.params);
|
|
133
|
+
|
|
134
|
+
// TODO: Implement database insertion
|
|
135
|
+
// const [created] = await db.insert(${definition.name}s).values(input).returning();
|
|
136
|
+
|
|
137
|
+
const mockData = {
|
|
138
|
+
data: { id: "new-id", ...input }, // Replace with actual created data
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return ctx.output(contract, 201, mockData);
|
|
142
|
+
})`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate UPDATE handler (PUT /api/resources/:id)
|
|
147
|
+
*/
|
|
148
|
+
function generateUpdateHandler(definition: ResourceDefinition, pascalName: string): string {
|
|
149
|
+
return ` // ✏️ Update ${pascalName}
|
|
150
|
+
.put(async (ctx) => {
|
|
151
|
+
const { id } = ctx.params;
|
|
152
|
+
const input = await ctx.input(contract, "PUT", ctx.params);
|
|
153
|
+
|
|
154
|
+
// TODO: Implement database update
|
|
155
|
+
// const [updated] = await db.update(${definition.name}s)
|
|
156
|
+
// .set(input)
|
|
157
|
+
// .where(eq(${definition.name}s.id, id))
|
|
158
|
+
// .returning();
|
|
159
|
+
// if (!updated) return ctx.notFound("${pascalName} not found");
|
|
160
|
+
|
|
161
|
+
const mockData = {
|
|
162
|
+
data: { id, ...input }, // Replace with actual updated data
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return ctx.output(contract, 200, mockData);
|
|
166
|
+
})`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Generate DELETE handler (DELETE /api/resources/:id)
|
|
171
|
+
*/
|
|
172
|
+
function generateDeleteHandler(definition: ResourceDefinition, pascalName: string): string {
|
|
173
|
+
return ` // 🗑️ Delete ${pascalName}
|
|
174
|
+
.delete(async (ctx) => {
|
|
175
|
+
const { id } = ctx.params;
|
|
176
|
+
|
|
177
|
+
// TODO: Implement database deletion
|
|
178
|
+
// const deleted = await db.delete(${definition.name}s).where(eq(${definition.name}s.id, id));
|
|
179
|
+
// if (!deleted) return ctx.notFound("${pascalName} not found");
|
|
180
|
+
|
|
181
|
+
return ctx.output(contract, 200, { data: { message: "${pascalName} deleted" } });
|
|
182
|
+
})`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Convert string to PascalCase
|
|
187
|
+
*/
|
|
188
|
+
function toPascalCase(str: string): string {
|
|
189
|
+
return str
|
|
190
|
+
.split(/[-_]/)
|
|
191
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
192
|
+
.join("");
|
|
193
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource TypeScript Types Generator
|
|
3
|
+
* Generate TypeScript type definitions from resource
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ResourceDefinition } from "../schema";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate TypeScript types for resource
|
|
10
|
+
*
|
|
11
|
+
* @returns Types file content
|
|
12
|
+
*/
|
|
13
|
+
export function generateResourceTypes(definition: ResourceDefinition): string {
|
|
14
|
+
const pascalName = toPascalCase(definition.name);
|
|
15
|
+
const contractPath = `../contracts/${definition.name}.contract`;
|
|
16
|
+
|
|
17
|
+
return `// 🎯 Mandu Resource Types - ${definition.name}
|
|
18
|
+
// Auto-generated from resource definition
|
|
19
|
+
// DO NOT EDIT - Regenerated on every \`mandu generate\`
|
|
20
|
+
|
|
21
|
+
import type { InferContract, InferQuery, InferBody, InferParams, InferResponse } from "@mandujs/core";
|
|
22
|
+
import contract from "${contractPath}";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Full contract type for ${definition.name}
|
|
26
|
+
*/
|
|
27
|
+
export type ${pascalName}Contract = InferContract<typeof contract>;
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// Request Types
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
/** GET query parameters */
|
|
34
|
+
export type ${pascalName}GetQuery = InferQuery<typeof contract, "GET">;
|
|
35
|
+
|
|
36
|
+
/** POST request body */
|
|
37
|
+
export type ${pascalName}PostBody = InferBody<typeof contract, "POST">;
|
|
38
|
+
|
|
39
|
+
/** PUT request body */
|
|
40
|
+
export type ${pascalName}PutBody = InferBody<typeof contract, "PUT">;
|
|
41
|
+
|
|
42
|
+
/** PATCH request body */
|
|
43
|
+
export type ${pascalName}PatchBody = InferBody<typeof contract, "PATCH">;
|
|
44
|
+
|
|
45
|
+
/** DELETE query parameters */
|
|
46
|
+
export type ${pascalName}DeleteQuery = InferQuery<typeof contract, "DELETE">;
|
|
47
|
+
|
|
48
|
+
/** Path parameters (if any) */
|
|
49
|
+
export type ${pascalName}Params = InferParams<typeof contract, "GET">;
|
|
50
|
+
|
|
51
|
+
// ============================================
|
|
52
|
+
// Response Types
|
|
53
|
+
// ============================================
|
|
54
|
+
|
|
55
|
+
/** 200 OK response */
|
|
56
|
+
export type ${pascalName}Response200 = InferResponse<typeof contract, 200>;
|
|
57
|
+
|
|
58
|
+
/** 201 Created response */
|
|
59
|
+
export type ${pascalName}Response201 = InferResponse<typeof contract, 201>;
|
|
60
|
+
|
|
61
|
+
/** 204 No Content response */
|
|
62
|
+
export type ${pascalName}Response204 = InferResponse<typeof contract, 204>;
|
|
63
|
+
|
|
64
|
+
/** 400 Bad Request response */
|
|
65
|
+
export type ${pascalName}Response400 = InferResponse<typeof contract, 400>;
|
|
66
|
+
|
|
67
|
+
/** 404 Not Found response */
|
|
68
|
+
export type ${pascalName}Response404 = InferResponse<typeof contract, 404>;
|
|
69
|
+
|
|
70
|
+
// Re-export contract for runtime use
|
|
71
|
+
export { contract };
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convert string to PascalCase
|
|
77
|
+
*/
|
|
78
|
+
function toPascalCase(str: string): string {
|
|
79
|
+
return str
|
|
80
|
+
.split(/[-_]/)
|
|
81
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
82
|
+
.join("");
|
|
83
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource-Centric Architecture
|
|
3
|
+
* Public API exports
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Schema API
|
|
7
|
+
export {
|
|
8
|
+
defineResource,
|
|
9
|
+
validateResourceDefinition,
|
|
10
|
+
getPluralName,
|
|
11
|
+
getEnabledEndpoints,
|
|
12
|
+
isFieldRequired,
|
|
13
|
+
getFieldDefault,
|
|
14
|
+
FieldTypes,
|
|
15
|
+
} from "./schema";
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
ResourceDefinition,
|
|
19
|
+
ResourceField,
|
|
20
|
+
ResourceOptions,
|
|
21
|
+
FieldType,
|
|
22
|
+
} from "./schema";
|
|
23
|
+
|
|
24
|
+
// Parser API
|
|
25
|
+
export { parseResourceSchema, parseResourceSchemas, validateResourceUniqueness } from "./parser";
|
|
26
|
+
|
|
27
|
+
export type { ParsedResource } from "./parser";
|
|
28
|
+
|
|
29
|
+
// Generator API
|
|
30
|
+
export {
|
|
31
|
+
generateResourceArtifacts,
|
|
32
|
+
generateResourcesArtifacts,
|
|
33
|
+
logGeneratorResult,
|
|
34
|
+
} from "./generator";
|
|
35
|
+
|
|
36
|
+
export type { GeneratorOptions, GeneratorResult } from "./generator";
|
|
37
|
+
|
|
38
|
+
// Individual Generators (for advanced use)
|
|
39
|
+
export { generateResourceContract } from "./generators/contract";
|
|
40
|
+
export { generateResourceTypes } from "./generators/types";
|
|
41
|
+
export { generateResourceSlot } from "./generators/slot";
|
|
42
|
+
export { generateResourceClient } from "./generators/client";
|