@codexsploitx/schemaapi 1.0.5 → 1.1.7
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/bin/schemaapi +17 -331
- package/dist/adapters/deno.d.ts +4 -0
- package/dist/adapters/express.d.ts +21 -0
- package/dist/adapters/fastify.d.ts +23 -0
- package/dist/adapters/hapi.d.ts +28 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/koa.d.ts +22 -0
- package/dist/adapters/nest.d.ts +35 -0
- package/dist/adapters/next.d.ts +30 -0
- package/dist/adapters/remix.d.ts +14 -0
- package/dist/adapters/ws.d.ts +14 -0
- package/dist/cli/commands/docs.d.ts +1 -0
- package/dist/cli/commands/generate.d.ts +1 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/templates/contract.d.ts +1 -0
- package/dist/cli/templates/deno.d.ts +1 -0
- package/dist/cli/templates/express.d.ts +1 -0
- package/dist/cli/templates/fastify.d.ts +1 -0
- package/dist/cli/templates/hapi.d.ts +1 -0
- package/dist/cli/templates/koa.d.ts +1 -0
- package/dist/cli/templates/nest.d.ts +2 -0
- package/dist/cli/templates/next.d.ts +1 -0
- package/dist/cli/templates/remix.d.ts +1 -0
- package/dist/cli/utils.d.ts +6 -0
- package/dist/cli.js +819 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/client.d.ts +16 -0
- package/dist/core/contract.d.ts +74 -3
- package/dist/core/versioning.test.d.ts +1 -0
- package/dist/docs.d.ts +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/playground.d.ts +65 -0
- package/dist/playground.test.d.ts +1 -0
- package/dist/schemaapi.cjs.js +1713 -2
- package/dist/schemaapi.cjs.js.map +1 -1
- package/dist/schemaapi.esm.js +1705 -3
- package/dist/schemaapi.esm.js.map +1 -1
- package/dist/schemaapi.umd.js +1715 -6
- package/dist/schemaapi.umd.js.map +1 -1
- package/dist/sdk.d.ts +8 -0
- package/package.json +1 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
|
|
7
|
+
function _interopNamespaceDefault(e) {
|
|
8
|
+
var n = Object.create(null);
|
|
9
|
+
if (e) {
|
|
10
|
+
Object.keys(e).forEach(function (k) {
|
|
11
|
+
if (k !== 'default') {
|
|
12
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () { return e[k]; }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
n.default = e;
|
|
21
|
+
return Object.freeze(n);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
25
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
26
|
+
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
const cwd = process.cwd();
|
|
29
|
+
const configPath = path__namespace.join(cwd, "schemaapi.config.json");
|
|
30
|
+
if (!fs__namespace.existsSync(configPath)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const raw = fs__namespace.readFileSync(configPath, "utf8");
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function ensureDir(dir) {
|
|
42
|
+
if (!fs__namespace.existsSync(dir)) {
|
|
43
|
+
fs__namespace.mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function writeFile(filePath, content) {
|
|
47
|
+
ensureDir(path__namespace.dirname(filePath));
|
|
48
|
+
fs__namespace.writeFileSync(filePath, content, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
function toPascalCase(str) {
|
|
51
|
+
return str.replace(/(^\w|-\w)/g, (clear) => clear.replace(/-/, "").toUpperCase());
|
|
52
|
+
}
|
|
53
|
+
function toCamelCase(str) {
|
|
54
|
+
return str.replace(/-\w/g, (clear) => clear[1].toUpperCase());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeAdapter(value) {
|
|
58
|
+
if (!value)
|
|
59
|
+
return "generic";
|
|
60
|
+
const v = String(value).toLowerCase();
|
|
61
|
+
if (v === "next" || v === "nextjs")
|
|
62
|
+
return "next";
|
|
63
|
+
if (v === "remix")
|
|
64
|
+
return "remix";
|
|
65
|
+
if (v === "express")
|
|
66
|
+
return "express";
|
|
67
|
+
if (v === "fastify")
|
|
68
|
+
return "fastify";
|
|
69
|
+
if (v === "nest" || v === "nestjs")
|
|
70
|
+
return "nest";
|
|
71
|
+
if (v === "koa")
|
|
72
|
+
return "koa";
|
|
73
|
+
if (v === "hapi")
|
|
74
|
+
return "hapi";
|
|
75
|
+
if (v === "deno")
|
|
76
|
+
return "deno";
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
79
|
+
function handleInit(adapterArg) {
|
|
80
|
+
const adapter = normalizeAdapter(adapterArg);
|
|
81
|
+
const cwd = process.cwd();
|
|
82
|
+
const contractsDir = path__namespace.join(cwd, "contracts");
|
|
83
|
+
ensureDir(contractsDir);
|
|
84
|
+
const usersContractPath = path__namespace.join(contractsDir, "usersContract.ts");
|
|
85
|
+
if (!fs__namespace.existsSync(usersContractPath)) {
|
|
86
|
+
const usersContractContent = [
|
|
87
|
+
'import { createContract } from "@codexsploitx/schemaapi";',
|
|
88
|
+
'import { z } from "zod";',
|
|
89
|
+
"",
|
|
90
|
+
"export const usersContract = createContract({",
|
|
91
|
+
' "/users/:id": {',
|
|
92
|
+
" GET: {",
|
|
93
|
+
" params: z.object({ id: z.string() }),",
|
|
94
|
+
" response: z.object({ id: z.string(), name: z.string() }),",
|
|
95
|
+
" },",
|
|
96
|
+
" },",
|
|
97
|
+
"});",
|
|
98
|
+
"",
|
|
99
|
+
].join("\n");
|
|
100
|
+
writeFile(usersContractPath, usersContractContent);
|
|
101
|
+
}
|
|
102
|
+
const indexPath = path__namespace.join(contractsDir, "index.ts");
|
|
103
|
+
if (!fs__namespace.existsSync(indexPath)) {
|
|
104
|
+
writeFile(indexPath, 'export * from "./usersContract";\n');
|
|
105
|
+
}
|
|
106
|
+
if (adapter === "next") {
|
|
107
|
+
const appApiUsersDir = path__namespace.join(cwd, "app", "api", "users");
|
|
108
|
+
ensureDir(appApiUsersDir);
|
|
109
|
+
const routePath = path__namespace.join(appApiUsersDir, "route.ts");
|
|
110
|
+
if (!fs__namespace.existsSync(routePath)) {
|
|
111
|
+
const nextRouteContent = [
|
|
112
|
+
'import { adapters } from "@codexsploitx/schemaapi";',
|
|
113
|
+
'import { usersContract } from "../../../contracts";',
|
|
114
|
+
"",
|
|
115
|
+
"const handlers = adapters.next.handleContract(usersContract, {",
|
|
116
|
+
' "GET /users/:id": async ({ params }) => {',
|
|
117
|
+
" return {",
|
|
118
|
+
" id: String(params && params.id),",
|
|
119
|
+
' name: "John Doe",',
|
|
120
|
+
" };",
|
|
121
|
+
" },",
|
|
122
|
+
"});",
|
|
123
|
+
"",
|
|
124
|
+
"export const GET = handlers.GET;",
|
|
125
|
+
"",
|
|
126
|
+
].join("\n");
|
|
127
|
+
writeFile(routePath, nextRouteContent);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const configPath = path__namespace.join(cwd, "schemaapi.config.json");
|
|
131
|
+
let existingConfig = null;
|
|
132
|
+
if (fs__namespace.existsSync(configPath)) {
|
|
133
|
+
try {
|
|
134
|
+
const raw = fs__namespace.readFileSync(configPath, "utf8");
|
|
135
|
+
existingConfig = JSON.parse(raw);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
existingConfig = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const config = {
|
|
142
|
+
...(existingConfig || {}),
|
|
143
|
+
adapter,
|
|
144
|
+
contractsDir: existingConfig?.contractsDir || "contracts",
|
|
145
|
+
};
|
|
146
|
+
writeFile(configPath, JSON.stringify(config, null, 2));
|
|
147
|
+
console.log(`Initialized SchemaApi with adapter "${adapter}"`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function generateContract(name) {
|
|
151
|
+
const camelName = name.replace(/^[A-Z]/, c => c.toLowerCase());
|
|
152
|
+
return `import { createContract } from "@codexsploitx/schemaapi";
|
|
153
|
+
import { z } from "zod";
|
|
154
|
+
|
|
155
|
+
const ${camelName}Schema = z.object({
|
|
156
|
+
id: z.string(),
|
|
157
|
+
name: z.string(),
|
|
158
|
+
createdAt: z.string().datetime(),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
export const ${camelName}Contract = createContract({
|
|
162
|
+
"/${name}": {
|
|
163
|
+
GET: {
|
|
164
|
+
query: z.object({
|
|
165
|
+
limit: z.string().optional().transform(Number),
|
|
166
|
+
offset: z.string().optional().transform(Number),
|
|
167
|
+
}),
|
|
168
|
+
response: z.array(${camelName}Schema),
|
|
169
|
+
},
|
|
170
|
+
POST: {
|
|
171
|
+
body: ${camelName}Schema.omit({ id: true, createdAt: true }),
|
|
172
|
+
response: ${camelName}Schema,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
"/${name}/:id": {
|
|
176
|
+
GET: {
|
|
177
|
+
params: z.object({ id: z.string() }),
|
|
178
|
+
response: ${camelName}Schema,
|
|
179
|
+
},
|
|
180
|
+
PUT: {
|
|
181
|
+
params: z.object({ id: z.string() }),
|
|
182
|
+
body: ${camelName}Schema.partial(),
|
|
183
|
+
response: ${camelName}Schema,
|
|
184
|
+
},
|
|
185
|
+
DELETE: {
|
|
186
|
+
params: z.object({ id: z.string() }),
|
|
187
|
+
response: z.object({ success: z.boolean() }),
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function generateNestModule(name) {
|
|
195
|
+
const pascalName = toPascalCase(name);
|
|
196
|
+
const camelName = toCamelCase(name);
|
|
197
|
+
return `import { Module, OnModuleInit, INestApplication } from '@nestjs/common';
|
|
198
|
+
import { ${camelName}Service } from './${name}.service';
|
|
199
|
+
import { adapters } from '@codexsploitx/schemaapi';
|
|
200
|
+
import { ${camelName}Contract } from '../../contracts/${name}.contract';
|
|
201
|
+
|
|
202
|
+
@Module({
|
|
203
|
+
providers: [${pascalName}Service],
|
|
204
|
+
exports: [${pascalName}Service],
|
|
205
|
+
})
|
|
206
|
+
export class ${pascalName}Module implements OnModuleInit {
|
|
207
|
+
constructor(private readonly service: ${pascalName}Service) {}
|
|
208
|
+
|
|
209
|
+
onModuleInit() {
|
|
210
|
+
// Note: SchemaApi registration requires the app instance.
|
|
211
|
+
// Usually done in main.ts or via a global module.
|
|
212
|
+
// This is a placeholder for the implementation logic.
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static register(app: INestApplication) {
|
|
216
|
+
const service = app.get(${pascalName}Service);
|
|
217
|
+
|
|
218
|
+
adapters.nest.SchemaApiModule.register(app, ${camelName}Contract, {
|
|
219
|
+
"GET /${name}": async (ctx) => service.findAll(ctx.query),
|
|
220
|
+
"POST /${name}": async (ctx) => service.create(ctx.body),
|
|
221
|
+
"GET /${name}/:id": async (ctx) => service.findOne(ctx.params.id),
|
|
222
|
+
"PUT /${name}/:id": async (ctx) => service.update(ctx.params.id, ctx.body),
|
|
223
|
+
"DELETE /${name}/:id": async (ctx) => service.remove(ctx.params.id),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
function generateNestService(name) {
|
|
230
|
+
const pascalName = toPascalCase(name);
|
|
231
|
+
toCamelCase(name);
|
|
232
|
+
return `import { Injectable, NotFoundException } from '@nestjs/common';
|
|
233
|
+
|
|
234
|
+
@Injectable()
|
|
235
|
+
export class ${pascalName}Service {
|
|
236
|
+
private items = [];
|
|
237
|
+
|
|
238
|
+
async findAll(query: any) {
|
|
239
|
+
return this.items;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async create(data: any) {
|
|
243
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...data };
|
|
244
|
+
this.items.push(newItem);
|
|
245
|
+
return newItem;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async findOne(id: string) {
|
|
249
|
+
const item = this.items.find(i => i.id === id);
|
|
250
|
+
if (!item) throw new NotFoundException();
|
|
251
|
+
return item;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async update(id: string, data: any) {
|
|
255
|
+
const index = this.items.findIndex(i => i.id === id);
|
|
256
|
+
if (index === -1) throw new NotFoundException();
|
|
257
|
+
this.items[index] = { ...this.items[index], ...data };
|
|
258
|
+
return this.items[index];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async remove(id: string) {
|
|
262
|
+
const index = this.items.findIndex(i => i.id === id);
|
|
263
|
+
if (index === -1) throw new NotFoundException();
|
|
264
|
+
this.items.splice(index, 1);
|
|
265
|
+
return { success: true };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function generateExpressRoute(name) {
|
|
272
|
+
const camelName = toCamelCase(name);
|
|
273
|
+
return `import { Router } from "express";
|
|
274
|
+
import { adapters } from "@codexsploitx/schemaapi";
|
|
275
|
+
import { ${camelName}Contract } from "../../contracts/${name}Contract";
|
|
276
|
+
|
|
277
|
+
export const ${camelName}Router = Router();
|
|
278
|
+
|
|
279
|
+
// Mock database
|
|
280
|
+
const items: any[] = [];
|
|
281
|
+
|
|
282
|
+
adapters.express.handleContract(${camelName}Router, ${camelName}Contract, {
|
|
283
|
+
"GET /${name}": async ({ query }) => {
|
|
284
|
+
return items;
|
|
285
|
+
},
|
|
286
|
+
"POST /${name}": async ({ body }) => {
|
|
287
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...body };
|
|
288
|
+
items.push(newItem);
|
|
289
|
+
return newItem;
|
|
290
|
+
},
|
|
291
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
292
|
+
const item = items.find((i) => i.id === params.id);
|
|
293
|
+
if (!item) throw { status: 404, message: "Not Found" };
|
|
294
|
+
return item;
|
|
295
|
+
},
|
|
296
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
297
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
298
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
299
|
+
items[index] = { ...items[index], ...body };
|
|
300
|
+
return items[index];
|
|
301
|
+
},
|
|
302
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
303
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
304
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
305
|
+
items.splice(index, 1);
|
|
306
|
+
return { success: true };
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function generateFastifyRoute(name) {
|
|
313
|
+
const camelName = toCamelCase(name);
|
|
314
|
+
return `import { FastifyInstance } from "fastify";
|
|
315
|
+
import { adapters } from "@codexsploitx/schemaapi";
|
|
316
|
+
import { ${camelName}Contract } from "../../contracts/${name}Contract";
|
|
317
|
+
|
|
318
|
+
// Mock database
|
|
319
|
+
const items: any[] = [];
|
|
320
|
+
|
|
321
|
+
export async function ${camelName}Routes(fastify: FastifyInstance) {
|
|
322
|
+
adapters.fastify.handleContract(fastify, ${camelName}Contract, {
|
|
323
|
+
"GET /${name}": async ({ query }) => {
|
|
324
|
+
return items;
|
|
325
|
+
},
|
|
326
|
+
"POST /${name}": async ({ body }) => {
|
|
327
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...body };
|
|
328
|
+
items.push(newItem);
|
|
329
|
+
return newItem;
|
|
330
|
+
},
|
|
331
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
332
|
+
const item = items.find((i) => i.id === params.id);
|
|
333
|
+
if (!item) throw { status: 404, message: "Not Found" };
|
|
334
|
+
return item;
|
|
335
|
+
},
|
|
336
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
337
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
338
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
339
|
+
items[index] = { ...items[index], ...body };
|
|
340
|
+
return items[index];
|
|
341
|
+
},
|
|
342
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
343
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
344
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
345
|
+
items.splice(index, 1);
|
|
346
|
+
return { success: true };
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function generateKoaRoute(name) {
|
|
354
|
+
const camelName = toCamelCase(name);
|
|
355
|
+
return `import Router from "@koa/router";
|
|
356
|
+
import { adapters } from "@codexsploitx/schemaapi";
|
|
357
|
+
import { ${camelName}Contract } from "../../contracts/${name}Contract";
|
|
358
|
+
|
|
359
|
+
export const ${camelName}Router = new Router();
|
|
360
|
+
|
|
361
|
+
// Mock database
|
|
362
|
+
const items: any[] = [];
|
|
363
|
+
|
|
364
|
+
adapters.koa.handleContract(${camelName}Router, ${camelName}Contract, {
|
|
365
|
+
"GET /${name}": async ({ query }) => {
|
|
366
|
+
return items;
|
|
367
|
+
},
|
|
368
|
+
"POST /${name}": async ({ body }) => {
|
|
369
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...body };
|
|
370
|
+
items.push(newItem);
|
|
371
|
+
return newItem;
|
|
372
|
+
},
|
|
373
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
374
|
+
const item = items.find((i) => i.id === params.id);
|
|
375
|
+
if (!item) throw { status: 404, message: "Not Found" };
|
|
376
|
+
return item;
|
|
377
|
+
},
|
|
378
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
379
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
380
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
381
|
+
items[index] = { ...items[index], ...body };
|
|
382
|
+
return items[index];
|
|
383
|
+
},
|
|
384
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
385
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
386
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
387
|
+
items.splice(index, 1);
|
|
388
|
+
return { success: true };
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function generateHapiRoute(name) {
|
|
395
|
+
const camelName = toCamelCase(name);
|
|
396
|
+
const pascalName = toPascalCase(name);
|
|
397
|
+
return `import Hapi from "@hapi/hapi";
|
|
398
|
+
import { adapters } from "@codexsploitx/schemaapi";
|
|
399
|
+
import { ${camelName}Contract } from "../../contracts/${name}Contract";
|
|
400
|
+
|
|
401
|
+
// Mock database
|
|
402
|
+
const items: any[] = [];
|
|
403
|
+
|
|
404
|
+
export async function register${pascalName}Routes(server: Hapi.Server) {
|
|
405
|
+
adapters.hapi.handleContract(server, ${camelName}Contract, {
|
|
406
|
+
"GET /${name}": async ({ query }) => {
|
|
407
|
+
return items;
|
|
408
|
+
},
|
|
409
|
+
"POST /${name}": async ({ body }) => {
|
|
410
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...body };
|
|
411
|
+
items.push(newItem);
|
|
412
|
+
return newItem;
|
|
413
|
+
},
|
|
414
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
415
|
+
const item = items.find((i) => i.id === params.id);
|
|
416
|
+
if (!item) throw { status: 404, message: "Not Found" };
|
|
417
|
+
return item;
|
|
418
|
+
},
|
|
419
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
420
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
421
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
422
|
+
items[index] = { ...items[index], ...body };
|
|
423
|
+
return items[index];
|
|
424
|
+
},
|
|
425
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
426
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
427
|
+
if (index === -1) throw { status: 404, message: "Not Found" };
|
|
428
|
+
items.splice(index, 1);
|
|
429
|
+
return { success: true };
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function generateDenoRoute(name) {
|
|
437
|
+
const camelName = toCamelCase(name);
|
|
438
|
+
return `import { adapters } from "@codexsploitx/schemaapi";
|
|
439
|
+
import { ${camelName}Contract } from "../../contracts/${name}Contract.ts";
|
|
440
|
+
|
|
441
|
+
// Mock database
|
|
442
|
+
const items: any[] = [];
|
|
443
|
+
|
|
444
|
+
export const handler = adapters.deno.handleContract(${camelName}Contract, {
|
|
445
|
+
"GET /${name}": async ({ query }) => {
|
|
446
|
+
return items;
|
|
447
|
+
},
|
|
448
|
+
"POST /${name}": async ({ body }) => {
|
|
449
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...body };
|
|
450
|
+
items.push(newItem);
|
|
451
|
+
return newItem;
|
|
452
|
+
},
|
|
453
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
454
|
+
const item = items.find((i) => i.id === params.id);
|
|
455
|
+
if (!item) return new Response("Not Found", { status: 404 });
|
|
456
|
+
return item;
|
|
457
|
+
},
|
|
458
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
459
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
460
|
+
if (index === -1) return new Response("Not Found", { status: 404 });
|
|
461
|
+
items[index] = { ...items[index], ...body };
|
|
462
|
+
return items[index];
|
|
463
|
+
},
|
|
464
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
465
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
466
|
+
if (index === -1) return new Response("Not Found", { status: 404 });
|
|
467
|
+
items.splice(index, 1);
|
|
468
|
+
return { success: true };
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function generateRemixRoute(name) {
|
|
475
|
+
const camelName = toCamelCase(name);
|
|
476
|
+
return `import { adapters } from "@codexsploitx/schemaapi";
|
|
477
|
+
import { ${camelName}Contract } from "../../contracts/${name}Contract";
|
|
478
|
+
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
|
|
479
|
+
|
|
480
|
+
// Mock database
|
|
481
|
+
const items: any[] = [];
|
|
482
|
+
|
|
483
|
+
const handlers = {
|
|
484
|
+
"GET /${name}": async ({ query }) => {
|
|
485
|
+
return items;
|
|
486
|
+
},
|
|
487
|
+
"POST /${name}": async ({ body }) => {
|
|
488
|
+
const newItem = { id: Date.now().toString(), createdAt: new Date().toISOString(), ...body };
|
|
489
|
+
items.push(newItem);
|
|
490
|
+
return newItem;
|
|
491
|
+
},
|
|
492
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
493
|
+
const item = items.find((i) => i.id === params.id);
|
|
494
|
+
if (!item) throw new Response("Not Found", { status: 404 });
|
|
495
|
+
return item;
|
|
496
|
+
},
|
|
497
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
498
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
499
|
+
if (index === -1) throw new Response("Not Found", { status: 404 });
|
|
500
|
+
items[index] = { ...items[index], ...body };
|
|
501
|
+
return items[index];
|
|
502
|
+
},
|
|
503
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
504
|
+
const index = items.findIndex((i) => i.id === params.id);
|
|
505
|
+
if (index === -1) throw new Response("Not Found", { status: 404 });
|
|
506
|
+
items.splice(index, 1);
|
|
507
|
+
return { success: true };
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const { loader, action } = adapters.remix.createRouteHandlers(
|
|
512
|
+
${camelName}Contract,
|
|
513
|
+
handlers,
|
|
514
|
+
"/${name}" // Adjust route pattern if needed
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
export { loader, action };
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function generateNextRoute(name) {
|
|
522
|
+
const camelName = toCamelCase(name);
|
|
523
|
+
return `import { adapters } from "@codexsploitx/schemaapi";
|
|
524
|
+
import { ${camelName}Contract } from "../../../contracts/${name}Contract";
|
|
525
|
+
|
|
526
|
+
const handlers = adapters.next.handleContract(${camelName}Contract, {
|
|
527
|
+
"GET /${name}": async ({ query }) => {
|
|
528
|
+
return [];
|
|
529
|
+
},
|
|
530
|
+
"POST /${name}": async ({ body }) => {
|
|
531
|
+
return { id: "1", ...body, createdAt: new Date().toISOString() };
|
|
532
|
+
},
|
|
533
|
+
"GET /${name}/:id": async ({ params }) => {
|
|
534
|
+
return { id: params.id, name: "Item " + params.id };
|
|
535
|
+
},
|
|
536
|
+
"PUT /${name}/:id": async ({ params, body }) => {
|
|
537
|
+
return { id: params.id, ...body };
|
|
538
|
+
},
|
|
539
|
+
"DELETE /${name}/:id": async ({ params }) => {
|
|
540
|
+
return { success: true };
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
export const { GET, POST, PUT, DELETE } = handlers;
|
|
545
|
+
`;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Helper to load the main library
|
|
549
|
+
function loadSchemaApi() {
|
|
550
|
+
try {
|
|
551
|
+
// When running from dist/cli.js, .. resolves to project root
|
|
552
|
+
// which should load the main entry point defined in package.json
|
|
553
|
+
return require(path__namespace.resolve(__dirname, '../../..'));
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return {};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function loadContractsModule(contractsDir) {
|
|
560
|
+
const cwd = process.cwd();
|
|
561
|
+
const baseDir = path__namespace.join(cwd, contractsDir || "contracts");
|
|
562
|
+
const candidates = ["index.js", "index.cjs", "index.ts"];
|
|
563
|
+
// In a CLI context, we might be running against TS source (via ts-node) or compiled JS.
|
|
564
|
+
// The original CLI looked for compiled files. Let's try to support TS if registered,
|
|
565
|
+
// but primarily look for what the user has.
|
|
566
|
+
// Note: requiring .ts files natively without ts-node registration won't work in standard node.
|
|
567
|
+
// The original CLI used require() so it likely expected the user to have compiled contracts
|
|
568
|
+
// OR was running in a dev environment that supported it.
|
|
569
|
+
// For safety, we'll try standard require.
|
|
570
|
+
for (const file of candidates) {
|
|
571
|
+
const fullPath = path__namespace.join(baseDir, file);
|
|
572
|
+
if (fs__namespace.existsSync(fullPath)) {
|
|
573
|
+
try {
|
|
574
|
+
return require(fullPath);
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
// If it's a syntax error due to TS, we might want to warn
|
|
578
|
+
console.error(`Failed to load contracts module at ${fullPath}:`, (error && error.message) || error);
|
|
579
|
+
// Continue to next candidate? No, if found but failed, it's an error.
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// If we didn't find index.js/cjs, maybe the user is in a TS project and hasn't compiled contracts yet?
|
|
585
|
+
// The original CLI errored here.
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
function renderDocsHtmlFallback(docs) {
|
|
589
|
+
const items = Array.isArray(docs.routes)
|
|
590
|
+
? docs.routes
|
|
591
|
+
.map((route) => {
|
|
592
|
+
const method = route && route.method ? String(route.method) : "";
|
|
593
|
+
const pathValue = route && route.path ? String(route.path) : "";
|
|
594
|
+
return `<li><code>${method} ${pathValue}</code></li>`;
|
|
595
|
+
})
|
|
596
|
+
.join("\n")
|
|
597
|
+
: "";
|
|
598
|
+
return `<!doctype html>
|
|
599
|
+
<html lang="en">
|
|
600
|
+
<head>
|
|
601
|
+
<meta charset="utf-8" />
|
|
602
|
+
<title>SchemaApi Docs</title>
|
|
603
|
+
</head>
|
|
604
|
+
<body>
|
|
605
|
+
<h1>SchemaApi Docs</h1>
|
|
606
|
+
<ul>
|
|
607
|
+
${items}
|
|
608
|
+
</ul>
|
|
609
|
+
</body>
|
|
610
|
+
</html>`;
|
|
611
|
+
}
|
|
612
|
+
function generateDocs(config) {
|
|
613
|
+
const adapter = config && config.adapter ? String(config.adapter) : null;
|
|
614
|
+
const contractsDir = config && config.contractsDir ? String(config.contractsDir) : "contracts";
|
|
615
|
+
console.log("Generating docs from contract");
|
|
616
|
+
if (adapter || contractsDir) {
|
|
617
|
+
console.log(`Using config: adapter=${adapter || "unknown"}, contractsDir=${contractsDir}`);
|
|
618
|
+
}
|
|
619
|
+
const contractsModule = loadContractsModule(contractsDir);
|
|
620
|
+
if (!contractsModule) {
|
|
621
|
+
console.error(`Could not find contracts entry in ${contractsDir}. Expected index.js or index.cjs`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const candidates = Object.values(contractsModule).filter((value) => value &&
|
|
625
|
+
typeof value === "object" &&
|
|
626
|
+
typeof value.docs === "function");
|
|
627
|
+
if (candidates.length === 0) {
|
|
628
|
+
console.error("No contracts with a docs() method were found in the contracts entry file.");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const combinedDocs = {
|
|
632
|
+
routes: candidates.flatMap((contract) => {
|
|
633
|
+
try {
|
|
634
|
+
const docs = contract.docs();
|
|
635
|
+
return Array.isArray(docs.routes) ? docs.routes : [];
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
}),
|
|
641
|
+
};
|
|
642
|
+
if (!combinedDocs.routes.length) {
|
|
643
|
+
console.error("Contracts loaded, but no routes were found when calling docs().");
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const outDir = path__namespace.join(process.cwd(), "schemaapi-docs");
|
|
647
|
+
if (!fs__namespace.existsSync(outDir)) {
|
|
648
|
+
fs__namespace.mkdirSync(outDir, { recursive: true });
|
|
649
|
+
}
|
|
650
|
+
const outPath = path__namespace.join(outDir, "index.html");
|
|
651
|
+
const schemaapi = loadSchemaApi();
|
|
652
|
+
const hasRenderDocs = schemaapi && typeof schemaapi.renderDocs === "function";
|
|
653
|
+
const html = hasRenderDocs
|
|
654
|
+
? schemaapi.renderDocs(combinedDocs, {
|
|
655
|
+
format: "html",
|
|
656
|
+
title: "SchemaApi Docs",
|
|
657
|
+
theme: "dark",
|
|
658
|
+
})
|
|
659
|
+
: renderDocsHtmlFallback(combinedDocs);
|
|
660
|
+
fs__namespace.writeFileSync(outPath, html, "utf8");
|
|
661
|
+
console.log(`Docs generated at: ${path__namespace.relative(process.cwd(), outPath)}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function handleGenerate(args) {
|
|
665
|
+
const [type, name] = args;
|
|
666
|
+
const config = loadConfig();
|
|
667
|
+
let adapter = config?.adapter;
|
|
668
|
+
if (type === 'docs') {
|
|
669
|
+
generateDocs(config || {});
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (type === 'sdk') {
|
|
673
|
+
console.log("Generating SDK from contract");
|
|
674
|
+
// Placeholder restoration
|
|
675
|
+
if (adapter || config?.contractsDir) {
|
|
676
|
+
console.log(`Using config: adapter=${adapter || "unknown"}, contractsDir=${config?.contractsDir || "contracts"}`);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (type === 'tests') {
|
|
681
|
+
console.log("Generating tests from contract");
|
|
682
|
+
// Placeholder restoration
|
|
683
|
+
if (adapter || config?.contractsDir) {
|
|
684
|
+
console.log(`Using config: adapter=${adapter || "unknown"}, contractsDir=${config?.contractsDir || "contracts"}`);
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (type !== 'resource') {
|
|
689
|
+
console.error(`Unknown generate type: ${type}. Supported: resource, docs, sdk, tests`);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (!name) {
|
|
693
|
+
console.error('Usage: schemaapi generate resource <name>');
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (!adapter) {
|
|
697
|
+
console.warn("⚠️ No schemaapi.config.json found. Defaulting to 'express'.");
|
|
698
|
+
console.warn(" Run 'npx schemaapi init <framework>' to set your preference permanently.");
|
|
699
|
+
adapter = 'express';
|
|
700
|
+
}
|
|
701
|
+
const cwd = process.cwd();
|
|
702
|
+
console.log(`Generating resource "${name}" for adapter "${adapter}"...`);
|
|
703
|
+
// 1. Generate Contract
|
|
704
|
+
const contractsDir = path__namespace.join(cwd, config?.contractsDir || 'contracts');
|
|
705
|
+
const contractContent = generateContract(name);
|
|
706
|
+
const contractPath = path__namespace.join(contractsDir, `${name}Contract.ts`);
|
|
707
|
+
if (fs__namespace.existsSync(contractPath)) {
|
|
708
|
+
console.warn(`Contract ${contractPath} already exists. Skipping.`);
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
writeFile(contractPath, contractContent);
|
|
712
|
+
console.log(`Created contract: ${contractPath}`);
|
|
713
|
+
// Update index.ts in contracts if exists
|
|
714
|
+
const indexContractPath = path__namespace.join(contractsDir, 'index.ts');
|
|
715
|
+
if (fs__namespace.existsSync(indexContractPath)) {
|
|
716
|
+
const indexContent = fs__namespace.readFileSync(indexContractPath, 'utf8');
|
|
717
|
+
if (!indexContent.includes(`./${name}Contract`)) {
|
|
718
|
+
fs__namespace.appendFileSync(indexContractPath, `export * from "./${name}Contract";\n`);
|
|
719
|
+
console.log(`Updated contracts/index.ts`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// 2. Generate Implementation based on adapter
|
|
724
|
+
switch (adapter) {
|
|
725
|
+
case 'nest': {
|
|
726
|
+
const srcDir = path__namespace.join(cwd, 'src', name);
|
|
727
|
+
const serviceContent = generateNestService(name);
|
|
728
|
+
const moduleContent = generateNestModule(name);
|
|
729
|
+
writeFile(path__namespace.join(srcDir, `${name}.service.ts`), serviceContent);
|
|
730
|
+
writeFile(path__namespace.join(srcDir, `${name}.module.ts`), moduleContent);
|
|
731
|
+
console.log(`Created NestJS resources in src/${name}`);
|
|
732
|
+
console.log(`\nIMPORTANT: Register ${name}Module.register(app) in your main.ts or AppModule!`);
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
case 'express': {
|
|
736
|
+
const routesDir = path__namespace.join(cwd, 'src', 'routes');
|
|
737
|
+
const routeContent = generateExpressRoute(name);
|
|
738
|
+
writeFile(path__namespace.join(routesDir, `${name}.ts`), routeContent);
|
|
739
|
+
console.log(`Created Express router in src/routes/${name}.ts`);
|
|
740
|
+
console.log(`\nIMPORTANT: Mount the router in your app: app.use(routes.${name}Router)`);
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
case 'fastify': {
|
|
744
|
+
const routesDir = path__namespace.join(cwd, 'src', 'routes');
|
|
745
|
+
const routeContent = generateFastifyRoute(name);
|
|
746
|
+
writeFile(path__namespace.join(routesDir, `${name}.ts`), routeContent);
|
|
747
|
+
console.log(`Created Fastify routes in src/routes/${name}.ts`);
|
|
748
|
+
console.log(`\nIMPORTANT: Register the routes in your app: fastify.register(${name}Routes)`);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
case 'koa': {
|
|
752
|
+
const routesDir = path__namespace.join(cwd, 'src', 'routes');
|
|
753
|
+
const routeContent = generateKoaRoute(name);
|
|
754
|
+
writeFile(path__namespace.join(routesDir, `${name}.ts`), routeContent);
|
|
755
|
+
console.log(`Created Koa router in src/routes/${name}.ts`);
|
|
756
|
+
console.log(`\nIMPORTANT: Use the router in your app: app.use(${name}Router.routes())`);
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
case 'hapi': {
|
|
760
|
+
const routesDir = path__namespace.join(cwd, 'src', 'routes');
|
|
761
|
+
const routeContent = generateHapiRoute(name);
|
|
762
|
+
writeFile(path__namespace.join(routesDir, `${name}.ts`), routeContent);
|
|
763
|
+
console.log(`Created Hapi routes in src/routes/${name}.ts`);
|
|
764
|
+
console.log(`\nIMPORTANT: Register routes: await register${name.charAt(0).toUpperCase() + name.slice(1)}Routes(server)`);
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
case 'deno': {
|
|
768
|
+
const routesDir = path__namespace.join(cwd, 'src', 'handlers');
|
|
769
|
+
const routeContent = generateDenoRoute(name);
|
|
770
|
+
writeFile(path__namespace.join(routesDir, `${name}.ts`), routeContent);
|
|
771
|
+
console.log(`Created Deno handler in src/handlers/${name}.ts`);
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
case 'remix': {
|
|
775
|
+
const routesDir = path__namespace.join(cwd, 'app', 'routes');
|
|
776
|
+
const routeContent = generateRemixRoute(name);
|
|
777
|
+
writeFile(path__namespace.join(routesDir, `${name}.tsx`), routeContent);
|
|
778
|
+
console.log(`Created Remix route in app/routes/${name}.tsx`);
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
case 'next': {
|
|
782
|
+
const routeDir = path__namespace.join(cwd, 'app', 'api', name);
|
|
783
|
+
const routeContent = generateNextRoute(name);
|
|
784
|
+
writeFile(path__namespace.join(routeDir, 'route.ts'), routeContent);
|
|
785
|
+
console.log(`Created Next.js route in app/api/${name}/route.ts`);
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
default:
|
|
789
|
+
console.warn(`Adapter "${adapter}" scaffolding is not yet fully supported.`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const args = process.argv.slice(2);
|
|
794
|
+
const command = args[0];
|
|
795
|
+
switch (command) {
|
|
796
|
+
case 'init':
|
|
797
|
+
handleInit(args[1]);
|
|
798
|
+
break;
|
|
799
|
+
case 'generate':
|
|
800
|
+
handleGenerate(args.slice(1));
|
|
801
|
+
break;
|
|
802
|
+
case 'audit':
|
|
803
|
+
console.log('Running SchemaApi audit');
|
|
804
|
+
// Minimal implementation for now, as it was in the original
|
|
805
|
+
break;
|
|
806
|
+
default:
|
|
807
|
+
console.log(`
|
|
808
|
+
SchemaApi CLI
|
|
809
|
+
|
|
810
|
+
Usage:
|
|
811
|
+
npx schemaapi init <framework> Initialize configuration
|
|
812
|
+
npx schemaapi generate resource <name> Generate a new resource
|
|
813
|
+
npx schemaapi generate docs Generate documentation
|
|
814
|
+
npx schemaapi generate sdk Generate SDK (coming soon)
|
|
815
|
+
npx schemaapi generate tests Generate tests (coming soon)
|
|
816
|
+
npx schemaapi audit Audit contracts
|
|
817
|
+
`);
|
|
818
|
+
}
|
|
819
|
+
//# sourceMappingURL=cli.js.map
|