@codexsploitx/schemaapi 1.0.0 → 1.0.1

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.
Files changed (50) hide show
  1. package/package.json +1 -1
  2. package/docs/adapters/deno.md +0 -51
  3. package/docs/adapters/express.md +0 -67
  4. package/docs/adapters/fastify.md +0 -64
  5. package/docs/adapters/hapi.md +0 -67
  6. package/docs/adapters/koa.md +0 -61
  7. package/docs/adapters/nest.md +0 -66
  8. package/docs/adapters/next.md +0 -66
  9. package/docs/adapters/remix.md +0 -72
  10. package/docs/cli.md +0 -18
  11. package/docs/consepts.md +0 -18
  12. package/docs/getting_started.md +0 -149
  13. package/docs/sdk.md +0 -25
  14. package/docs/validation.md +0 -228
  15. package/docs/versioning.md +0 -28
  16. package/eslint.config.mjs +0 -34
  17. package/rollup.config.js +0 -19
  18. package/src/adapters/deno.ts +0 -139
  19. package/src/adapters/express.ts +0 -134
  20. package/src/adapters/fastify.ts +0 -133
  21. package/src/adapters/hapi.ts +0 -140
  22. package/src/adapters/index.ts +0 -9
  23. package/src/adapters/koa.ts +0 -128
  24. package/src/adapters/nest.ts +0 -122
  25. package/src/adapters/next.ts +0 -175
  26. package/src/adapters/remix.ts +0 -145
  27. package/src/adapters/ws.ts +0 -132
  28. package/src/core/client.ts +0 -104
  29. package/src/core/contract.ts +0 -534
  30. package/src/core/versioning.test.ts +0 -174
  31. package/src/docs.ts +0 -535
  32. package/src/index.ts +0 -5
  33. package/src/playground.test.ts +0 -98
  34. package/src/playground.ts +0 -13
  35. package/src/sdk.ts +0 -17
  36. package/tests/adapters.deno.test.ts +0 -70
  37. package/tests/adapters.express.test.ts +0 -67
  38. package/tests/adapters.fastify.test.ts +0 -63
  39. package/tests/adapters.hapi.test.ts +0 -66
  40. package/tests/adapters.koa.test.ts +0 -58
  41. package/tests/adapters.nest.test.ts +0 -85
  42. package/tests/adapters.next.test.ts +0 -39
  43. package/tests/adapters.remix.test.ts +0 -52
  44. package/tests/adapters.ws.test.ts +0 -91
  45. package/tests/cli.test.ts +0 -156
  46. package/tests/client.test.ts +0 -110
  47. package/tests/contract.handle.test.ts +0 -267
  48. package/tests/docs.test.ts +0 -96
  49. package/tests/sdk.test.ts +0 -34
  50. package/tsconfig.json +0 -15
@@ -1,70 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { handleContract } from "../src/adapters/deno";
5
-
6
- describe("adapters.deno.handleContract", () => {
7
- it("maneja una petición GET devolviendo JSON según el contrato", async () => {
8
- const contract = createContract({
9
- "/api/data/:id": {
10
- GET: {
11
- params: z.object({ id: z.string() }),
12
- response: z.object({ id: z.string(), ok: z.boolean() }),
13
- },
14
- },
15
- });
16
-
17
- const handlers = {
18
- "GET /api/data/:id": async (ctx: any) => ({
19
- id: ctx.params.id as string,
20
- ok: true,
21
- }),
22
- };
23
-
24
- const handler = handleContract(contract, handlers);
25
-
26
- const req = new Request("http://localhost/api/data/123", {
27
- method: "GET",
28
- });
29
-
30
- const res = await handler(req);
31
- expect(res.status).toBe(200);
32
- const json = await res.json();
33
- expect(json).toEqual({ id: "123", ok: true });
34
- });
35
-
36
- it("maneja una ruta de download devolviendo binario y headers correctos", async () => {
37
- const contract = createContract({
38
- "/file": {
39
- GET: {
40
- media: {
41
- kind: "download",
42
- },
43
- },
44
- },
45
- });
46
-
47
- const handlers = {
48
- "GET /file": async () => ({
49
- data: new TextEncoder().encode("hello"),
50
- contentType: "text/plain",
51
- filename: "hello.txt",
52
- }),
53
- };
54
-
55
- const handler = handleContract(contract, handlers);
56
-
57
- const req = new Request("http://localhost/file", {
58
- method: "GET",
59
- });
60
-
61
- const res = await handler(req);
62
- expect(res.status).toBe(200);
63
- expect(res.headers.get("Content-Type")).toBe("text/plain");
64
- expect(res.headers.get("Content-Disposition")).toBe(
65
- 'attachment; filename="hello.txt"'
66
- );
67
- const text = await res.text();
68
- expect(text).toBe("hello");
69
- });
70
- });
@@ -1,67 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { handleContract, ExpressLikeHandler, ExpressLikeRequest } from "../src/adapters/express";
5
-
6
- type StoredRoute = {
7
- method: string;
8
- path: string;
9
- handler: ExpressLikeHandler;
10
- };
11
-
12
- describe("adapters.express.handleContract", () => {
13
- it("registra rutas y ejecuta handlers usando createContract", async () => {
14
- const routes: StoredRoute[] = [];
15
-
16
- const app = {
17
- get(path: string, handler: StoredRoute["handler"]) {
18
- routes.push({ method: "get", path, handler });
19
- },
20
- };
21
-
22
- const contract = createContract({
23
- "/users/:id": {
24
- GET: {
25
- params: z.object({ id: z.string() }),
26
- response: z.object({ id: z.string() }),
27
- },
28
- },
29
- });
30
-
31
- handleContract(app, contract, {
32
- "GET /users/:id": async (ctx) => ({
33
- id: (ctx.params as { id: string }).id,
34
- }),
35
- });
36
-
37
- expect(routes).toHaveLength(1);
38
- const route = routes[0];
39
- expect(route.method).toBe("get");
40
- expect(route.path).toBe("/users/:id");
41
-
42
- let jsonResult: unknown;
43
- const req = {
44
- params: { id: "123" },
45
- query: {},
46
- body: undefined,
47
- headers: {},
48
- user: undefined,
49
- } as unknown as ExpressLikeRequest;
50
- const res = {
51
- json(body: unknown) {
52
- jsonResult = body;
53
- },
54
- };
55
-
56
- const next = (err?: unknown) => {
57
- if (err) {
58
- throw err;
59
- }
60
- };
61
-
62
- await route.handler(req, res, next);
63
-
64
- expect(jsonResult).toEqual({ id: "123" });
65
- });
66
- });
67
-
@@ -1,63 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { handleContract } from "../src/adapters/fastify";
5
-
6
- type StoredRoute = {
7
- method: string | string[];
8
- url: string;
9
- handler: (request: any, reply: any) => void | Promise<void>;
10
- };
11
-
12
- describe("adapters.fastify.handleContract", () => {
13
- it("registra una ruta y ejecuta el handler con contexto", async () => {
14
- const routes: StoredRoute[] = [];
15
-
16
- const fastify = {
17
- route(options: StoredRoute) {
18
- routes.push(options);
19
- },
20
- };
21
-
22
- const contract = createContract({
23
- "/ping": {
24
- GET: {
25
- response: z.object({ pong: z.boolean() }),
26
- },
27
- },
28
- });
29
-
30
- handleContract(fastify, contract, {
31
- "GET /ping": async () => ({ pong: true }),
32
- });
33
-
34
- expect(routes).toHaveLength(1);
35
- const route = routes[0];
36
- expect(route.method).toBe("GET");
37
- expect(route.url).toBe("/ping");
38
-
39
- let sent: unknown;
40
- const request = {
41
- params: {},
42
- query: {},
43
- body: undefined,
44
- headers: {},
45
- user: undefined,
46
- };
47
- const reply = {
48
- statusCode: 200,
49
- status(code: number) {
50
- this.statusCode = code;
51
- return this;
52
- },
53
- send(body: unknown) {
54
- sent = body;
55
- },
56
- };
57
-
58
- await route.handler(request, reply);
59
- expect(reply.statusCode).toBe(200);
60
- expect(sent).toEqual({ pong: true });
61
- });
62
- });
63
-
@@ -1,66 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { handleContract } from "../src/adapters/hapi";
5
-
6
- type StoredRoute = {
7
- method: string;
8
- path: string;
9
- handler: (request: any, h: any) => Promise<any> | any;
10
- };
11
-
12
- describe("adapters.hapi.handleContract", () => {
13
- it("registra una ruta y devuelve la respuesta correcta", async () => {
14
- const routes: StoredRoute[] = [];
15
-
16
- const server = {
17
- route(options: StoredRoute) {
18
- routes.push(options);
19
- },
20
- };
21
-
22
- const contract = createContract({
23
- "/hello/:name": {
24
- GET: {
25
- params: z.object({ name: z.string() }),
26
- response: z.object({ message: z.string() }),
27
- },
28
- },
29
- });
30
-
31
- handleContract(server, contract, {
32
- "GET /hello/:name": async (ctx: any) => ({
33
- message: `Hello ${ctx.params.name as string}`,
34
- }),
35
- });
36
-
37
- expect(routes).toHaveLength(1);
38
- const route = routes[0];
39
- expect(route.method).toBe("GET");
40
- expect(route.path).toBe("/hello/{name}");
41
-
42
- const request = {
43
- params: { name: "Ada" },
44
- query: {},
45
- payload: undefined,
46
- headers: {},
47
- auth: { credentials: undefined },
48
- };
49
-
50
- const h = {
51
- response(body: unknown) {
52
- return {
53
- body,
54
- statusCode: 200,
55
- code(code: number) {
56
- this.statusCode = code;
57
- },
58
- };
59
- },
60
- };
61
-
62
- const result = await route.handler(request, h);
63
- expect(result).toEqual({ message: "Hello Ada" });
64
- });
65
- });
66
-
@@ -1,58 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { handleContract } from "../src/adapters/koa";
5
-
6
- type StoredRoute = {
7
- path: string;
8
- methods: string[];
9
- middleware: (ctx: any, next: () => Promise<void>) => Promise<void>;
10
- };
11
-
12
- describe("adapters.koa.handleContract", () => {
13
- it("registra una ruta y fija ctx.body con el resultado", async () => {
14
- const routes: StoredRoute[] = [];
15
-
16
- const router = {
17
- register(path: string, methods: string[], middleware: StoredRoute["middleware"]) {
18
- routes.push({ path, methods, middleware });
19
- },
20
- };
21
-
22
- const contract = createContract({
23
- "/users/:id": {
24
- GET: {
25
- params: z.object({ id: z.string() }),
26
- response: z.object({ id: z.string() }),
27
- },
28
- },
29
- });
30
-
31
- handleContract(router, contract, {
32
- "GET /users/:id": async (ctx: any) => ({
33
- id: ctx.params.id as string,
34
- }),
35
- });
36
-
37
- expect(routes).toHaveLength(1);
38
- const route = routes[0];
39
- expect(route.path).toBe("/users/:id");
40
- expect(route.methods).toEqual(["GET"]);
41
-
42
- const ctx: any = {
43
- params: { id: "7" },
44
- query: {},
45
- request: { body: undefined },
46
- headers: {},
47
- state: {},
48
- body: undefined,
49
- status: 200,
50
- };
51
-
52
- await route.middleware(ctx, async () => {});
53
-
54
- expect(ctx.status).toBe(200);
55
- expect(ctx.body).toEqual({ id: "7" });
56
- });
57
- });
58
-
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { SchemaApiModule } from "../src/adapters/nest";
5
-
6
- type StoredRoute = {
7
- method: string;
8
- path: string;
9
- handler: (req: any, res: any, next: () => void) => void | Promise<void>;
10
- };
11
-
12
- describe("adapters.nest.SchemaApiModule.register", () => {
13
- it("registra rutas en el adaptador HTTP y ejecuta el handler", async () => {
14
- const routes: StoredRoute[] = [];
15
-
16
- const httpAdapter = {
17
- get(path: string, handler: StoredRoute["handler"]) {
18
- routes.push({ method: "get", path, handler });
19
- },
20
- };
21
-
22
- const app = {
23
- getHttpAdapter() {
24
- return httpAdapter;
25
- },
26
- };
27
-
28
- const contract = createContract({
29
- "/items/:id": {
30
- GET: {
31
- params: z.object({ id: z.string() }),
32
- response: z.object({ id: z.string() }),
33
- },
34
- },
35
- });
36
-
37
- SchemaApiModule.register(app as any, contract, {
38
- "GET /items/:id": async (ctx: any) => ({
39
- id: ctx.params.id as string,
40
- }),
41
- });
42
-
43
- expect(routes).toHaveLength(1);
44
- const route = routes[0];
45
- expect(route.method).toBe("get");
46
- expect(route.path).toBe("/items/:id");
47
-
48
- let sent: unknown;
49
- const req = {
50
- params: { id: "10" },
51
- query: {},
52
- body: undefined,
53
- headers: {},
54
- user: undefined,
55
- };
56
- const res = {
57
- value: undefined as unknown,
58
- statusCode: 200,
59
- send(body: unknown) {
60
- this.value = body;
61
- },
62
- json(body: unknown) {
63
- this.value = body;
64
- },
65
- end(body: string) {
66
- this.value = JSON.parse(body);
67
- },
68
- status(code: number) {
69
- this.statusCode = code;
70
- return this;
71
- },
72
- code(code: number) {
73
- this.statusCode = code;
74
- return this;
75
- },
76
- };
77
-
78
- await route.handler(req, res, () => {});
79
- sent = res.value;
80
-
81
- expect(res.statusCode).toBe(200);
82
- expect(sent).toEqual({ id: "10" });
83
- });
84
- });
85
-
@@ -1,39 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { handleContract } from "../src/adapters/next";
5
-
6
- describe("adapters.next.handleContract", () => {
7
- it("resuelve una ruta GET usando contrato y params", async () => {
8
- const contract = createContract({
9
- "/users/:id": {
10
- GET: {
11
- params: z.object({ id: z.string() }),
12
- response: z.object({
13
- id: z.string(),
14
- search: z.string().optional(),
15
- }),
16
- },
17
- },
18
- });
19
-
20
- const handlers = {
21
- "GET /users/:id": async (ctx: any) => ({
22
- id: ctx.params.id as string,
23
- search: (ctx.query as { search?: string }).search,
24
- }),
25
- };
26
-
27
- const routes = handleContract(contract, handlers);
28
-
29
- const req = new Request("http://localhost/api/users/42?search=test", {
30
- method: "GET",
31
- });
32
-
33
- const res = await routes.GET(req, { params: { id: "42" } });
34
- expect(res.status).toBe(200);
35
- const json = await res.json();
36
- expect(json).toEqual({ id: "42", search: "test" });
37
- });
38
- });
39
-
@@ -1,52 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { z } from "zod";
3
- import { createContract } from "../src/core/contract";
4
- import { createRouteHandlers } from "../src/adapters/remix";
5
-
6
- describe("adapters.remix.createRouteHandlers", () => {
7
- it("usa loader para GET y action para POST basados en contrato", async () => {
8
- const contract = createContract({
9
- "/users/:id": {
10
- GET: {
11
- params: z.object({ id: z.string() }),
12
- response: z.object({ id: z.string(), source: z.literal("loader") }),
13
- },
14
- POST: {
15
- params: z.object({ id: z.string() }),
16
- body: z.object({ name: z.string() }),
17
- response: z.object({ id: z.string(), name: z.string(), source: z.literal("action") }),
18
- },
19
- },
20
- });
21
-
22
- const handlers = {
23
- "GET /users/:id": async (ctx: any) => ({
24
- id: ctx.params.id as string,
25
- source: "loader",
26
- }),
27
- "POST /users/:id": async (ctx: any) => ({
28
- id: ctx.params.id as string,
29
- name: (ctx.body as { name: string }).name,
30
- source: "action",
31
- }),
32
- };
33
-
34
- const { loader, action } = createRouteHandlers(contract, handlers, "/users/:id");
35
-
36
- const getRequest = new Request("http://localhost/users/5", { method: "GET" });
37
- const getResponse = await loader({ request: getRequest, params: { id: "5" } });
38
- expect(getResponse.status).toBe(200);
39
- const getJson = await getResponse.json();
40
- expect(getJson).toEqual({ id: "5", source: "loader" });
41
-
42
- const postRequest = new Request("http://localhost/users/9", {
43
- method: "POST",
44
- body: JSON.stringify({ name: "John" }),
45
- headers: { "Content-Type": "application/json" },
46
- });
47
- const postResponse = await action({ request: postRequest, params: { id: "9" } });
48
- expect(postResponse.status).toBe(200);
49
- const postJson = await postResponse.json();
50
- expect(postJson).toEqual({ id: "9", name: "John", source: "action" });
51
- });
52
- });
@@ -1,91 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { WebSocketServer, WebSocket } from "ws";
3
- import { createContract } from "../src/core/contract";
4
- import { z } from "zod";
5
- import * as adapters from "../src/adapters";
6
- import { createServer } from "http";
7
- import type { AddressInfo } from "net";
8
-
9
- describe("Adapter: WS", () => {
10
- it("maneja conexiones websocket correctamente", async () => {
11
- // 1. Definir contrato
12
- const contract = createContract({
13
- "/chat": {
14
- WS: {
15
- clientMessages: z.object({ text: z.string() }),
16
- serverMessages: z.object({ text: z.string() }),
17
- },
18
- },
19
- });
20
-
21
- // 2. Setup Server
22
- const server = createServer();
23
- const wss = new WebSocketServer({ server });
24
-
25
- // 3. Implementar Handler
26
- const handlers = {
27
- "WS /chat": (ctx: any) => {
28
- // Usamos ctx.onMessage helper que ya parsea y valida
29
- ctx.onMessage((msg: any) => {
30
- // Usamos ctx.send helper
31
- ctx.send({ text: `echo: ${msg.text}` });
32
- });
33
- },
34
- };
35
-
36
- adapters.ws.handleContract(wss, contract, handlers);
37
-
38
- await new Promise<void>((resolve) => server.listen(0, resolve));
39
- const port = (server.address() as AddressInfo).port;
40
-
41
- // 4. Test Client
42
- const ws = new WebSocket(`ws://localhost:${port}/chat`);
43
-
44
- const messages: any[] = [];
45
- ws.on("message", (data) => {
46
- messages.push(JSON.parse(data.toString()));
47
- });
48
-
49
- await new Promise((resolve) => ws.on("open", resolve));
50
- ws.send(JSON.stringify({ text: "hello" }));
51
-
52
- // Esperar respuesta
53
- await new Promise((resolve) => setTimeout(resolve, 100));
54
-
55
- expect(messages).toHaveLength(1);
56
- expect(messages[0]).toEqual({ text: "echo: hello" });
57
-
58
- ws.close();
59
- wss.close(); // Important to close wss
60
- server.close();
61
- });
62
-
63
- it("rechaza rutas no definidas", async () => {
64
- const contract = createContract({});
65
- const server = createServer();
66
- const wss = new WebSocketServer({ server });
67
- adapters.ws.handleContract(wss, contract, {});
68
-
69
- await new Promise<void>((resolve) => server.listen(0, resolve));
70
- const port = (server.address() as AddressInfo).port;
71
-
72
- const ws = new WebSocket(`ws://localhost:${port}/unknown`);
73
-
74
- const closePromise = new Promise<number>((resolve) => {
75
- ws.on('close', (code) => resolve(code));
76
- });
77
-
78
- // Wait for close
79
- const code = await closePromise;
80
- // 404 is technically an HTTP code, but WS handshake failure usually results in connection drop or close.
81
- // In my implementation: ws.close(404, "Not Found") -> 404 is not a valid WS close code (must be 1000-4999).
82
- // Ah, I used 404 in implementation. That might throw or be invalid.
83
- // Standard custom close codes are 4000-4999.
84
- // Or 1002 (Protocol Error).
85
-
86
- // Let's check implementation again.
87
-
88
- wss.close();
89
- server.close();
90
- });
91
- });