@codexsploitx/schemaapi 1.0.0 → 1.0.2

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +1 -1
  3. package/readme.md +125 -48
  4. package/docs/adapters/deno.md +0 -51
  5. package/docs/adapters/express.md +0 -67
  6. package/docs/adapters/fastify.md +0 -64
  7. package/docs/adapters/hapi.md +0 -67
  8. package/docs/adapters/koa.md +0 -61
  9. package/docs/adapters/nest.md +0 -66
  10. package/docs/adapters/next.md +0 -66
  11. package/docs/adapters/remix.md +0 -72
  12. package/docs/cli.md +0 -18
  13. package/docs/consepts.md +0 -18
  14. package/docs/getting_started.md +0 -149
  15. package/docs/sdk.md +0 -25
  16. package/docs/validation.md +0 -228
  17. package/docs/versioning.md +0 -28
  18. package/eslint.config.mjs +0 -34
  19. package/rollup.config.js +0 -19
  20. package/src/adapters/deno.ts +0 -139
  21. package/src/adapters/express.ts +0 -134
  22. package/src/adapters/fastify.ts +0 -133
  23. package/src/adapters/hapi.ts +0 -140
  24. package/src/adapters/index.ts +0 -9
  25. package/src/adapters/koa.ts +0 -128
  26. package/src/adapters/nest.ts +0 -122
  27. package/src/adapters/next.ts +0 -175
  28. package/src/adapters/remix.ts +0 -145
  29. package/src/adapters/ws.ts +0 -132
  30. package/src/core/client.ts +0 -104
  31. package/src/core/contract.ts +0 -534
  32. package/src/core/versioning.test.ts +0 -174
  33. package/src/docs.ts +0 -535
  34. package/src/index.ts +0 -5
  35. package/src/playground.test.ts +0 -98
  36. package/src/playground.ts +0 -13
  37. package/src/sdk.ts +0 -17
  38. package/tests/adapters.deno.test.ts +0 -70
  39. package/tests/adapters.express.test.ts +0 -67
  40. package/tests/adapters.fastify.test.ts +0 -63
  41. package/tests/adapters.hapi.test.ts +0 -66
  42. package/tests/adapters.koa.test.ts +0 -58
  43. package/tests/adapters.nest.test.ts +0 -85
  44. package/tests/adapters.next.test.ts +0 -39
  45. package/tests/adapters.remix.test.ts +0 -52
  46. package/tests/adapters.ws.test.ts +0 -91
  47. package/tests/cli.test.ts +0 -156
  48. package/tests/client.test.ts +0 -110
  49. package/tests/contract.handle.test.ts +0 -267
  50. package/tests/docs.test.ts +0 -96
  51. package/tests/sdk.test.ts +0 -34
  52. package/tsconfig.json +0 -15
@@ -1,139 +0,0 @@
1
- import type { createContract } from "../core/contract";
2
- import { buildErrorPayload } from "../core/contract";
3
-
4
- type AnyContract = ReturnType<typeof createContract>;
5
-
6
- type MethodSchemaLike = {
7
- media?: {
8
- kind?: string;
9
- contentTypes?: string[];
10
- maxSize?: number;
11
- };
12
- };
13
-
14
- type DownloadResult =
15
- | {
16
- data: unknown;
17
- contentType?: string;
18
- filename?: string;
19
- }
20
- | unknown;
21
-
22
- export function handleContract(
23
- contract: AnyContract,
24
- handlers: Record<
25
- string,
26
- (ctx: Record<string, unknown>) => unknown | Promise<unknown>
27
- >
28
- ) {
29
- // Retorna una función (req: Request) => Promise<Response> compatible con Deno.serve
30
- return async (req: Request): Promise<Response> => {
31
- const url = new URL(req.url);
32
- const path = url.pathname;
33
- const method = req.method;
34
-
35
- const schema = contract.schema as Record<string, Record<string, unknown>>;
36
-
37
- for (const routePattern of Object.keys(schema)) {
38
- // Regex simple para matching: /users/:id -> /users/([^/]+)
39
- // Agregamos ^ y $ para match exacto
40
- const regexPattern = routePattern.replace(/:[a-zA-Z0-9_]+/g, "([^/]+)");
41
- const regex = new RegExp(`^${regexPattern}$`);
42
-
43
- const match = path.match(regex);
44
- if (match) {
45
- const routeMethods = schema[routePattern] as Record<
46
- string,
47
- unknown
48
- >;
49
- if (routeMethods[method]) {
50
- const endpoint = `${method} ${routePattern}`;
51
- const implementation = handlers[endpoint];
52
- const methodSchema =
53
- (routeMethods as Record<string, unknown>)[method] as MethodSchemaLike | undefined;
54
-
55
- if (!implementation) {
56
- return new Response("Not Implemented", { status: 501 });
57
- }
58
-
59
- // Extraer params
60
- // Necesitamos saber los nombres de los parámetros para mapearlos
61
- // routePattern: /users/:id/posts/:postId
62
- // match: [..., "123", "456"]
63
- const paramNames = (routePattern.match(/:[a-zA-Z0-9_]+/g) || []).map(
64
- (p) => p.substring(1)
65
- );
66
- const params: Record<string, string> = {};
67
- paramNames.forEach((name, index) => {
68
- params[name] = match[index + 1];
69
- });
70
-
71
- const wrapped = contract.handle(endpoint, implementation);
72
-
73
- try {
74
- let body = undefined;
75
- if (method !== "GET" && method !== "HEAD") {
76
- try {
77
- body = await req.json();
78
- } catch {}
79
- }
80
-
81
- const context = {
82
- params,
83
- query: Object.fromEntries(url.searchParams.entries()),
84
- body,
85
- headers: Object.fromEntries(req.headers.entries()),
86
- };
87
-
88
- const result = await wrapped(context);
89
- const media = methodSchema?.media;
90
- if (media && media.kind === "download") {
91
- const download = result as DownloadResult;
92
- const data =
93
- download &&
94
- typeof download === "object" &&
95
- "data" in download
96
- ? (download as { data: unknown }).data
97
- : download;
98
- const contentType =
99
- download &&
100
- typeof download === "object" &&
101
- "contentType" in download
102
- ? (download as { contentType: string }).contentType
103
- : "application/octet-stream";
104
- const filename =
105
- download &&
106
- typeof download === "object" &&
107
- "filename" in download
108
- ? (download as { filename: string }).filename
109
- : undefined;
110
-
111
- const headers: Record<string, string> = {
112
- "Content-Type": String(contentType),
113
- };
114
- if (filename) {
115
- headers["Content-Disposition"] = `attachment; filename="${filename}"`;
116
- }
117
-
118
- return new Response(data as BodyInit, {
119
- headers,
120
- });
121
- }
122
-
123
- return new Response(JSON.stringify(result), {
124
- headers: { "Content-Type": "application/json" },
125
- });
126
- } catch (err: unknown) {
127
- const payload = buildErrorPayload(err);
128
- return new Response(JSON.stringify(payload), {
129
- status: payload.status,
130
- headers: { "Content-Type": "application/json" },
131
- });
132
- }
133
- }
134
- }
135
- }
136
-
137
- return new Response("Not Found", { status: 404 });
138
- };
139
- }
@@ -1,134 +0,0 @@
1
- import type { createContract } from "../core/contract";
2
-
3
- type AnyContract = ReturnType<typeof createContract>;
4
-
5
- type MethodSchemaLike = {
6
- media?: {
7
- kind?: string;
8
- contentTypes?: string[];
9
- maxSize?: number;
10
- };
11
- };
12
-
13
- type DownloadResult =
14
- | {
15
- data: unknown;
16
- contentType?: string;
17
- filename?: string;
18
- }
19
- | unknown;
20
-
21
- export interface ExpressLikeRequest {
22
- params: Record<string, string>;
23
- query: Record<string, string>;
24
- body: unknown;
25
- headers: Record<string, string>;
26
- user?: unknown;
27
- [key: string]: unknown;
28
- }
29
-
30
- export interface ExpressLikeResponse {
31
- json: (body: unknown) => void;
32
- send?: (body: unknown) => void;
33
- setHeader?: (name: string, value: string) => void;
34
- }
35
-
36
- export type ExpressLikeHandler = (
37
- req: ExpressLikeRequest,
38
- res: ExpressLikeResponse,
39
- next: (err?: unknown) => void
40
- ) => void | Promise<void>;
41
-
42
- export type ExpressLikeApp = {
43
- [method: string]: (path: string, handler: ExpressLikeHandler) => void;
44
- };
45
-
46
- export function handleContract(
47
- app: ExpressLikeApp,
48
- contract: AnyContract,
49
- handlers: Record<
50
- string,
51
- (ctx: Record<string, unknown>) => unknown | Promise<unknown>
52
- >
53
- ) {
54
- const schema = contract.schema as Record<string, Record<string, unknown>>;
55
-
56
- Object.keys(schema).forEach((route) => {
57
- const methods = schema[route] as Record<string, unknown>;
58
-
59
- Object.keys(methods).forEach((method) => {
60
- const endpoint = `${method} ${route}`;
61
- const implementation = handlers[endpoint];
62
- const methodSchema = (methods as Record<string, unknown>)[
63
- method
64
- ] as MethodSchemaLike | undefined;
65
-
66
- if (!implementation) {
67
- return;
68
- }
69
-
70
- const wrapped = contract.handle(endpoint, implementation);
71
- const httpMethod = method.toLowerCase();
72
- const register = (app as Record<string, unknown>)[
73
- httpMethod
74
- ] as ExpressLikeApp[keyof ExpressLikeApp] | undefined;
75
-
76
- if (!register) {
77
- return;
78
- }
79
-
80
- register(route, async (req, res, next) => {
81
- try {
82
- const context: Record<string, unknown> = {
83
- params: req.params || {},
84
- query: req.query || {},
85
- body: req.body,
86
- headers: req.headers || {},
87
- user: req.user,
88
- };
89
-
90
- const result = await wrapped(context);
91
- const media = methodSchema?.media;
92
- if (media && media.kind === "download") {
93
- const download = result as DownloadResult;
94
- const data =
95
- download &&
96
- typeof download === "object" &&
97
- "data" in download
98
- ? (download as { data: unknown }).data
99
- : download;
100
- const contentType =
101
- download &&
102
- typeof download === "object" &&
103
- "contentType" in download
104
- ? (download as { contentType: string }).contentType
105
- : "application/octet-stream";
106
- const filename =
107
- download &&
108
- typeof download === "object" &&
109
- "filename" in download
110
- ? (download as { filename: string }).filename
111
- : undefined;
112
-
113
- if (typeof res.setHeader === "function") {
114
- res.setHeader("Content-Type", contentType);
115
- if (filename) {
116
- res.setHeader(
117
- "Content-Disposition",
118
- `attachment; filename="${filename}"`
119
- );
120
- }
121
- }
122
- if (typeof res.send === "function") {
123
- res.send(data);
124
- return;
125
- }
126
- }
127
- res.json(result);
128
- } catch (error) {
129
- next(error);
130
- }
131
- });
132
- });
133
- });
134
- }
@@ -1,133 +0,0 @@
1
- import type { createContract } from "../core/contract";
2
- import { buildErrorPayload } from "../core/contract";
3
-
4
- type AnyContract = ReturnType<typeof createContract>;
5
-
6
- type MethodSchemaLike = {
7
- media?: {
8
- kind?: string;
9
- contentTypes?: string[];
10
- maxSize?: number;
11
- };
12
- };
13
-
14
- type DownloadResult =
15
- | {
16
- data: unknown;
17
- contentType?: string;
18
- filename?: string;
19
- }
20
- | unknown;
21
-
22
- interface FastifyLikeRequest {
23
- params: Record<string, string>;
24
- query: Record<string, string>;
25
- body: unknown;
26
- headers: Record<string, string>;
27
- user?: unknown;
28
- }
29
-
30
- interface FastifyLikeReply {
31
- status: (code: number) => FastifyLikeReply;
32
- send: (payload: unknown) => void;
33
- header: (key: string, value: string) => FastifyLikeReply;
34
- }
35
-
36
- interface FastifyInstance {
37
- route: (options: {
38
- method: string | string[];
39
- url: string;
40
- handler: (
41
- request: FastifyLikeRequest,
42
- reply: FastifyLikeReply
43
- ) => Promise<void> | void;
44
- }) => void;
45
- }
46
-
47
- export function handleContract(
48
- fastify: FastifyInstance,
49
- contract: AnyContract,
50
- handlers: Record<
51
- string,
52
- (ctx: Record<string, unknown>) => unknown | Promise<unknown>
53
- >
54
- ) {
55
- const schema = contract.schema as Record<string, Record<string, unknown>>;
56
-
57
- Object.keys(schema).forEach((route) => {
58
- const methods = schema[route] as Record<string, unknown>;
59
-
60
- Object.keys(methods).forEach((method) => {
61
- const endpoint = `${method} ${route}`;
62
- const implementation = handlers[endpoint];
63
- const methodSchema = (methods as Record<string, unknown>)[
64
- method
65
- ] as MethodSchemaLike | undefined;
66
-
67
- if (!implementation) {
68
- return;
69
- }
70
-
71
- const wrapped = contract.handle(endpoint, implementation);
72
-
73
- fastify.route({
74
- method: method,
75
- url: route, // Fastify soporta /:id sintaxis
76
- handler: async (
77
- request: FastifyLikeRequest,
78
- reply: FastifyLikeReply
79
- ) => {
80
- try {
81
- const context: Record<string, unknown> = {
82
- params: request.params || {},
83
- query: request.query || {},
84
- body: request.body,
85
- headers: request.headers || {},
86
- user: request.user,
87
- };
88
-
89
- const result = await wrapped(context);
90
- const media = methodSchema?.media;
91
- if (media && media.kind === "download") {
92
- const download = result as DownloadResult;
93
- const data =
94
- download &&
95
- typeof download === "object" &&
96
- "data" in download
97
- ? (download as { data: unknown }).data
98
- : download;
99
- const contentType =
100
- download &&
101
- typeof download === "object" &&
102
- "contentType" in download
103
- ? (download as { contentType: string }).contentType
104
- : "application/octet-stream";
105
- const filename =
106
- download &&
107
- typeof download === "object" &&
108
- "filename" in download
109
- ? (download as { filename: string }).filename
110
- : undefined;
111
-
112
- if (typeof reply.header === "function") {
113
- reply.header("Content-Type", contentType);
114
- if (filename) {
115
- reply.header(
116
- "Content-Disposition",
117
- `attachment; filename="${filename}"`
118
- );
119
- }
120
- }
121
- reply.send(data);
122
- return;
123
- }
124
- reply.send(result);
125
- } catch (error: unknown) {
126
- const payload = buildErrorPayload(error);
127
- reply.status(payload.status).send(payload);
128
- }
129
- },
130
- });
131
- });
132
- });
133
- }
@@ -1,140 +0,0 @@
1
- import type { createContract } from "../core/contract";
2
- import { buildErrorPayload } from "../core/contract";
3
-
4
- type AnyContract = ReturnType<typeof createContract>;
5
-
6
- type MethodSchemaLike = {
7
- media?: {
8
- kind?: string;
9
- contentTypes?: string[];
10
- maxSize?: number;
11
- };
12
- };
13
-
14
- type DownloadResult =
15
- | {
16
- data: unknown;
17
- contentType?: string;
18
- filename?: string;
19
- }
20
- | unknown;
21
-
22
- interface HapiLikeRequest {
23
- params: Record<string, string>;
24
- query: Record<string, string>;
25
- payload: unknown;
26
- headers: Record<string, string>;
27
- auth?: { credentials?: unknown };
28
- }
29
-
30
- interface HapiLikeResponseObject {
31
- type: (contentType: string) => void;
32
- header: (key: string, value: string) => void;
33
- code: (statusCode: number) => void;
34
- }
35
-
36
- interface HapiLikeResponseToolkit {
37
- response: (data: unknown) => HapiLikeResponseObject;
38
- }
39
-
40
- interface HapiServer {
41
- route: (options: {
42
- method: string;
43
- path: string;
44
- handler: (
45
- request: HapiLikeRequest,
46
- h: HapiLikeResponseToolkit
47
- ) => Promise<unknown> | unknown;
48
- }) => void;
49
- }
50
-
51
- export function handleContract(
52
- server: HapiServer,
53
- contract: AnyContract,
54
- handlers: Record<
55
- string,
56
- (ctx: Record<string, unknown>) => unknown | Promise<unknown>
57
- >
58
- ) {
59
- const schema = contract.schema as Record<string, Record<string, unknown>>;
60
-
61
- Object.keys(schema).forEach((route) => {
62
- const methods = schema[route] as Record<string, unknown>;
63
-
64
- // Convertir ruta express-style :param a hapi-style {param}
65
- const hapiRoute = route.replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
66
-
67
- Object.keys(methods).forEach((method) => {
68
- const endpoint = `${method} ${route}`;
69
- const implementation = handlers[endpoint];
70
- const methodSchema = (methods as Record<string, unknown>)[
71
- method
72
- ] as MethodSchemaLike | undefined;
73
-
74
- if (!implementation) {
75
- return;
76
- }
77
-
78
- const wrapped = contract.handle(endpoint, implementation);
79
-
80
- server.route({
81
- method: method,
82
- path: hapiRoute,
83
- handler: async (
84
- request: HapiLikeRequest,
85
- h: HapiLikeResponseToolkit
86
- ) => {
87
- try {
88
- const context: Record<string, unknown> = {
89
- params: request.params || {},
90
- query: request.query || {},
91
- body: request.payload,
92
- headers: request.headers || {},
93
- user: request.auth?.credentials, // Common Hapi auth pattern
94
- };
95
-
96
- const result = await wrapped(context);
97
- const media = methodSchema?.media;
98
- if (media && media.kind === "download") {
99
- const download = result as DownloadResult;
100
- const data =
101
- download &&
102
- typeof download === "object" &&
103
- "data" in download
104
- ? (download as { data: unknown }).data
105
- : download;
106
- const contentType =
107
- download &&
108
- typeof download === "object" &&
109
- "contentType" in download
110
- ? (download as { contentType: string }).contentType
111
- : "application/octet-stream";
112
- const filename =
113
- download &&
114
- typeof download === "object" &&
115
- "filename" in download
116
- ? (download as { filename: string }).filename
117
- : undefined;
118
-
119
- const response = h.response(data);
120
- response.type(contentType);
121
- if (filename) {
122
- response.header(
123
- "Content-Disposition",
124
- `attachment; filename="${filename}"`
125
- );
126
- }
127
- return response;
128
- }
129
- return result;
130
- } catch (error: unknown) {
131
- const payload = buildErrorPayload(error);
132
- const response = h.response(payload);
133
- response.code(payload.status);
134
- return response;
135
- }
136
- },
137
- });
138
- });
139
- });
140
- }
@@ -1,9 +0,0 @@
1
- export * as express from "./express";
2
- export * as next from "./next";
3
- export * as fastify from "./fastify";
4
- export * as nest from "./nest";
5
- export * as koa from "./koa";
6
- export * as hapi from "./hapi";
7
- export * as remix from "./remix";
8
- export * as deno from "./deno";
9
- export * as ws from "./ws";
@@ -1,128 +0,0 @@
1
- import type { createContract } from "../core/contract";
2
- import { buildErrorPayload } from "../core/contract";
3
-
4
- type AnyContract = ReturnType<typeof createContract>;
5
-
6
- type MethodSchemaLike = {
7
- media?: {
8
- kind?: string;
9
- contentTypes?: string[];
10
- maxSize?: number;
11
- };
12
- };
13
-
14
- type DownloadResult =
15
- | {
16
- data: unknown;
17
- contentType?: string;
18
- filename?: string;
19
- }
20
- | unknown;
21
-
22
- interface KoaLikeContext {
23
- params: Record<string, string>;
24
- query: Record<string, string>;
25
- request: { body?: unknown };
26
- headers: Record<string, string>;
27
- state?: { user?: unknown };
28
- body?: unknown;
29
- type?: string;
30
- set: (key: string, value: string) => void;
31
- status?: number;
32
- }
33
-
34
- interface KoaRouter {
35
- register: (
36
- path: string,
37
- methods: string[],
38
- middleware: (
39
- ctx: KoaLikeContext,
40
- next: () => Promise<unknown>
41
- ) => Promise<unknown>
42
- ) => void;
43
- }
44
-
45
- export function handleContract(
46
- router: KoaRouter,
47
- contract: AnyContract,
48
- handlers: Record<
49
- string,
50
- (ctx: Record<string, unknown>) => unknown | Promise<unknown>
51
- >
52
- ) {
53
- const schema = contract.schema as Record<string, Record<string, unknown>>;
54
-
55
- Object.keys(schema).forEach((route) => {
56
- const methods = schema[route] as Record<string, unknown>;
57
-
58
- Object.keys(methods).forEach((method) => {
59
- const endpoint = `${method} ${route}`;
60
- const implementation = handlers[endpoint];
61
- const methodSchema = (methods as Record<string, unknown>)[
62
- method
63
- ] as MethodSchemaLike | undefined;
64
-
65
- if (!implementation) {
66
- return;
67
- }
68
-
69
- const wrapped = contract.handle(endpoint, implementation);
70
-
71
- // Koa Router register
72
- router.register(
73
- route,
74
- [method],
75
- async (ctx: KoaLikeContext, _next: () => Promise<unknown>) => {
76
- try {
77
- const context: Record<string, unknown> = {
78
- params: ctx.params || {},
79
- query: ctx.query || {},
80
- body: ctx.request.body, // requires koa-bodyparser or koa-body
81
- headers: ctx.headers || {},
82
- user: ctx.state?.user, // common pattern
83
- };
84
-
85
- const result = await wrapped(context);
86
- const media = methodSchema?.media;
87
- if (media && media.kind === "download") {
88
- const download = result as DownloadResult;
89
- const data =
90
- download &&
91
- typeof download === "object" &&
92
- "data" in download
93
- ? (download as { data: unknown }).data
94
- : download;
95
- const contentType =
96
- download &&
97
- typeof download === "object" &&
98
- "contentType" in download
99
- ? (download as { contentType: string }).contentType
100
- : "application/octet-stream";
101
- const filename =
102
- download &&
103
- typeof download === "object" &&
104
- "filename" in download
105
- ? (download as { filename: string }).filename
106
- : undefined;
107
-
108
- ctx.body = data;
109
- ctx.type = contentType;
110
- if (filename) {
111
- ctx.set(
112
- "Content-Disposition",
113
- `attachment; filename="${filename}"`
114
- );
115
- }
116
- } else {
117
- ctx.body = result;
118
- }
119
- } catch (error: unknown) {
120
- const payload = buildErrorPayload(error);
121
- ctx.status = payload.status;
122
- ctx.body = payload;
123
- }
124
- }
125
- );
126
- });
127
- });
128
- }