@mpen/routekit 0.1.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/README.md +526 -0
- package/dist/bin.mjs +574 -0
- package/package.json +95 -0
- package/src/bin/gen-api-client.test.ts +70 -0
- package/src/bin/gen-api-client.ts +986 -0
- package/src/client/headers.ts +31 -0
- package/src/client/index.ts +8 -0
- package/src/client/promise.ts +11 -0
- package/src/client/react/index.test.tsx +266 -0
- package/src/client/react/index.ts +431 -0
- package/src/client/responses.test.ts +151 -0
- package/src/client/responses.ts +278 -0
- package/src/client/transport.ts +74 -0
- package/src/client/transports/body-codec.ts +61 -0
- package/src/client/transports/fetch.ts +113 -0
- package/src/client/tsconfig.json +9 -0
- package/src/client/types.ts +15 -0
- package/src/client/url.ts +31 -0
- package/src/index.ts +63 -0
- package/src/router/fetch-types.ts +13 -0
- package/src/router/handlers/index.ts +2 -0
- package/src/router/handlers/openapi/index.ts +2 -0
- package/src/router/handlers/openapi/openapi.ts +293 -0
- package/src/router/integration/zod-openapi.test.ts +74 -0
- package/src/router/lib/charset.test.ts +22 -0
- package/src/router/lib/charset.ts +133 -0
- package/src/router/lib/collections.ts +3 -0
- package/src/router/lib/format.test.ts +67 -0
- package/src/router/lib/format.ts +35 -0
- package/src/router/lib/host.ts +4 -0
- package/src/router/lib/json-schema.ts +6 -0
- package/src/router/lib/media-type.test.ts +122 -0
- package/src/router/lib/media-type.ts +289 -0
- package/src/router/lib/pathname.test.ts +18 -0
- package/src/router/lib/pathname.ts +19 -0
- package/src/router/lib/route-names.ts +70 -0
- package/src/router/lib/route-normalize.test.ts +36 -0
- package/src/router/lib/route-normalize.ts +67 -0
- package/src/router/lib/schema-merge.ts +56 -0
- package/src/router/middleware/accept-ctx.test.ts +33 -0
- package/src/router/middleware/accept-ctx.ts +12 -0
- package/src/router/middleware/body-limit.test.ts +112 -0
- package/src/router/middleware/body-limit.ts +121 -0
- package/src/router/middleware/content-type-context.ts +0 -0
- package/src/router/middleware/cors.test.ts +269 -0
- package/src/router/middleware/cors.ts +490 -0
- package/src/router/middleware/csrf.test.ts +106 -0
- package/src/router/middleware/csrf.ts +192 -0
- package/src/router/middleware/define.ts +249 -0
- package/src/router/middleware/index.ts +34 -0
- package/src/router/middleware/jsxhtml-response.ts +0 -0
- package/src/router/middleware/oas-swagger.ts +0 -0
- package/src/router/middleware/rate-limit.test.ts +886 -0
- package/src/router/middleware/rate-limit.ts +920 -0
- package/src/router/middleware/request-id-ctx.test.ts +183 -0
- package/src/router/middleware/request-id-ctx.ts +135 -0
- package/src/router/middleware/request-logger-format.test.ts +16 -0
- package/src/router/middleware/request-logger-format.ts +269 -0
- package/src/router/middleware/request-logger.test.ts +267 -0
- package/src/router/middleware/request-logger.ts +131 -0
- package/src/router/middleware/start-time-ctx.ts +5 -0
- package/src/router/request.ts +611 -0
- package/src/router/response/core.ts +181 -0
- package/src/router/response/directives.ts +233 -0
- package/src/router/response/formats/content/bodyless.ts +54 -0
- package/src/router/response/formats/content/content.ts +79 -0
- package/src/router/response/formats/content/index.ts +2 -0
- package/src/router/response/formats/json-rpc/index.ts +2 -0
- package/src/router/response/formats/problem/badRequest.ts +90 -0
- package/src/router/response/formats/problem/conflict.ts +90 -0
- package/src/router/response/formats/problem/created.ts +40 -0
- package/src/router/response/formats/problem/index.ts +27 -0
- package/src/router/response/formats/problem/notFound.ts +90 -0
- package/src/router/response/formats/problem/permissionDenied.ts +90 -0
- package/src/router/response/formats/problem/problem.test.ts +888 -0
- package/src/router/response/formats/problem/rateLimited.ts +90 -0
- package/src/router/response/formats/problem/responses.ts +219 -0
- package/src/router/response/formats/problem/root-errors.ts +48 -0
- package/src/router/response/formats/problem/sessionExpired.ts +90 -0
- package/src/router/response/formats/problem/types.ts +170 -0
- package/src/router/response/formats/problem/unauthenticated.ts +90 -0
- package/src/router/response/formats/problem/valibot.ts +410 -0
- package/src/router/response/formats/status/index.ts +1 -0
- package/src/router/response/formats/status/responses.ts +59 -0
- package/src/router/response/formats/status/status.test.ts +21 -0
- package/src/router/response/framers.ts +85 -0
- package/src/router/response/index.ts +28 -0
- package/src/router/response/openapi.test.ts +96 -0
- package/src/router/response/openapi.ts +1 -0
- package/src/router/response/serializers.ts +66 -0
- package/src/router/response/stream.ts +35 -0
- package/src/router/router.test.ts +1571 -0
- package/src/router/router.ts +1965 -0
- package/src/router/routes/index.ts +46 -0
- package/src/router/routes/valibot/index.ts +18 -0
- package/src/router/routes/valibot/valibot.ts +1393 -0
- package/src/router/routes/valibot.test.ts +286 -0
- package/src/router/routes/zod/index.ts +18 -0
- package/src/router/routes/zod/zod.ts +1318 -0
- package/src/router/routes/zod.test.ts +280 -0
- package/src/router/server-interface.ts +31 -0
- package/src/router/types.ts +657 -0
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
|
+
import { HttpMethod } from "@mpen/http";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { parseArgs } from "util";
|
|
7
|
+
import { $ } from "bun";
|
|
8
|
+
import { compile } from "json-schema-to-typescript";
|
|
9
|
+
//#region src/bin/gen-api-client.ts
|
|
10
|
+
const DEFAULT_RESPONSE_TYPE = "ApiResponsePromise";
|
|
11
|
+
const DEFAULT_FORMAT = "rk-api-client";
|
|
12
|
+
const OUTPUT_FORMATS = ["rk-api-client", "ts-query-rk-problem"];
|
|
13
|
+
function printHelp() {
|
|
14
|
+
console.log(`Usage: bun run packages/routekit/src/bin/gen-api-client.ts <router-file> [options]
|
|
15
|
+
|
|
16
|
+
Generate a typed API client from a routekit router module.
|
|
17
|
+
|
|
18
|
+
Arguments:
|
|
19
|
+
router-file Router module that exports a router with getRoutes()
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
-o, --output <file> File to write. Prints to stdout when omitted.
|
|
23
|
+
-w, --write Write to <router-file>.gen.ts beside the router file.
|
|
24
|
+
-p, --pretty Format written output with Prettier.
|
|
25
|
+
-f, --format <format> Output format: rk-api-client or ts-query-rk-problem. Defaults to rk-api-client.
|
|
26
|
+
--client-name <Name> Generated client class name. Defaults to ApiClient.
|
|
27
|
+
--import-type <Type:module> Import a type used by generated schemas. Can be repeated.
|
|
28
|
+
--response-type <Type> Generic response wrapper type. Defaults to ApiResponsePromise.
|
|
29
|
+
--help Show this help message.`);
|
|
30
|
+
}
|
|
31
|
+
function routeTypeBaseName(route) {
|
|
32
|
+
const parts = route.name.length > 0 ? route.name : ["index"];
|
|
33
|
+
return upperFirst(route.method.toLowerCase()) + parts.map(upperFirst).join("");
|
|
34
|
+
}
|
|
35
|
+
function getPathParamNames(routePath) {
|
|
36
|
+
return (routePath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((match) => match.slice(1));
|
|
37
|
+
}
|
|
38
|
+
function buildRouteTree(routes) {
|
|
39
|
+
const root = {
|
|
40
|
+
routes: [],
|
|
41
|
+
children: /* @__PURE__ */ new Map()
|
|
42
|
+
};
|
|
43
|
+
for (const route of routes) {
|
|
44
|
+
let node = root;
|
|
45
|
+
for (const segment of route.name) {
|
|
46
|
+
if (!node.children.has(segment)) node.children.set(segment, {
|
|
47
|
+
routes: [],
|
|
48
|
+
children: /* @__PURE__ */ new Map()
|
|
49
|
+
});
|
|
50
|
+
node = node.children.get(segment);
|
|
51
|
+
}
|
|
52
|
+
node.routes.push(route);
|
|
53
|
+
}
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
function classNameForParts(parts, baseName) {
|
|
57
|
+
if (parts.length === 0) return baseName;
|
|
58
|
+
return `${baseName}_${parts.map(upperFirst).join("_")}`;
|
|
59
|
+
}
|
|
60
|
+
function parseImportTypeOption(value) {
|
|
61
|
+
const colonIdx = value.indexOf(":");
|
|
62
|
+
if (colonIdx === -1) throw new Error(`Invalid --import-type value: "${value}"`);
|
|
63
|
+
const names = value.slice(0, colonIdx);
|
|
64
|
+
const module = value.slice(colonIdx + 1).trim();
|
|
65
|
+
const parsedNames = names.split(",").map((name) => name.trim()).filter(Boolean);
|
|
66
|
+
if (parsedNames.length === 0 || module.length === 0) throw new Error(`Invalid --import-type value: "${value}"`);
|
|
67
|
+
return {
|
|
68
|
+
names: parsedNames,
|
|
69
|
+
module
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function normalizeClientName(name) {
|
|
73
|
+
if (!name) return "ApiClient";
|
|
74
|
+
return name.replace(/[^A-Za-z0-9]+/g, " ").trim().split(/\s+/).map(upperFirst).join("") || "ApiClient";
|
|
75
|
+
}
|
|
76
|
+
function parseOutputFormat(value) {
|
|
77
|
+
const format = value?.trim() || DEFAULT_FORMAT;
|
|
78
|
+
if (OUTPUT_FORMATS.includes(format)) return format;
|
|
79
|
+
throw new Error(`Invalid --format value: "${value}". Expected one of: ${OUTPUT_FORMATS.join(", ")}`);
|
|
80
|
+
}
|
|
81
|
+
function upperFirst(str) {
|
|
82
|
+
return str.slice(0, 1).toUpperCase() + str.slice(1);
|
|
83
|
+
}
|
|
84
|
+
function patternToUrlTemplate(routePath, pathVar = "path") {
|
|
85
|
+
const templated = routePath.replace(/:([a-zA-Z0-9_]+)/g, `\${encodeURIComponent(String(${pathVar}.$1))}`);
|
|
86
|
+
if (templated.includes("${")) return "`" + templated + "`";
|
|
87
|
+
return `"${routePath}"`;
|
|
88
|
+
}
|
|
89
|
+
function isUnknown(text) {
|
|
90
|
+
return text === "unknown" || text === "any";
|
|
91
|
+
}
|
|
92
|
+
function isUnconstrainedJsonSchema(schema) {
|
|
93
|
+
return schema === true || schema !== false && typeof schema === "object" && !Array.isArray(schema) && Object.keys(schema).length === 0;
|
|
94
|
+
}
|
|
95
|
+
function isNeverJsonSchema(schema) {
|
|
96
|
+
return schema === false || schema !== true && typeof schema === "object" && !Array.isArray(schema) && Object.keys(schema).length === 1 && typeof schema.not === "object" && schema.not !== null && !Array.isArray(schema.not) && Object.keys(schema.not).length === 0;
|
|
97
|
+
}
|
|
98
|
+
function isJsonSchemaObject(schema) {
|
|
99
|
+
return schema !== true && schema !== false && typeof schema === "object" && !Array.isArray(schema);
|
|
100
|
+
}
|
|
101
|
+
function isNumericStringSchema(schema) {
|
|
102
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return false;
|
|
103
|
+
const schemaObject = schema;
|
|
104
|
+
return schemaObject.type === "string" && typeof schemaObject.pattern === "string" && ["^\\d+$", "^[0-9]+$"].includes(schemaObject.pattern);
|
|
105
|
+
}
|
|
106
|
+
function widenNumericStringPathParams(schema, pathParams) {
|
|
107
|
+
if (!isJsonSchemaObject(schema)) return schema;
|
|
108
|
+
const properties = schema.properties;
|
|
109
|
+
if (!properties || typeof properties !== "object" || Array.isArray(properties)) return schema;
|
|
110
|
+
const nextProperties = { ...properties };
|
|
111
|
+
let widened = false;
|
|
112
|
+
for (const pathParam of pathParams) {
|
|
113
|
+
const propertySchema = nextProperties[pathParam];
|
|
114
|
+
if (!isNumericStringSchema(propertySchema)) continue;
|
|
115
|
+
nextProperties[pathParam] = { anyOf: [propertySchema, { type: "number" }] };
|
|
116
|
+
widened = true;
|
|
117
|
+
}
|
|
118
|
+
if (!widened) return schema;
|
|
119
|
+
return {
|
|
120
|
+
...schema,
|
|
121
|
+
properties: nextProperties
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function normalizeMethod(routeMethod) {
|
|
125
|
+
if (!routeMethod) return [HttpMethod.GET];
|
|
126
|
+
return Array.isArray(routeMethod) ? routeMethod : [routeMethod];
|
|
127
|
+
}
|
|
128
|
+
function extractRoutes(routes) {
|
|
129
|
+
const extracted = [];
|
|
130
|
+
for (const route of routes) for (const method of normalizeMethod(route.method)) extracted.push({
|
|
131
|
+
name: route.name,
|
|
132
|
+
method,
|
|
133
|
+
path: route.path.pathname,
|
|
134
|
+
...route.schema?.request ? { requestSchema: route.schema.request } : {},
|
|
135
|
+
...route.schema?.response?.body ? { responseBodySchemas: route.schema.response.body } : {}
|
|
136
|
+
});
|
|
137
|
+
return extracted;
|
|
138
|
+
}
|
|
139
|
+
function resolveRouterModule(module) {
|
|
140
|
+
const candidates = [
|
|
141
|
+
module.default,
|
|
142
|
+
module.router,
|
|
143
|
+
...Object.values(module)
|
|
144
|
+
];
|
|
145
|
+
for (const candidate of candidates) if (candidate && typeof candidate === "object" && typeof candidate.getRoutes === "function") return candidate;
|
|
146
|
+
throw new Error("Unable to find an exported router with a getRoutes() method");
|
|
147
|
+
}
|
|
148
|
+
async function loadRuntimeRoutes(routerPath) {
|
|
149
|
+
return resolveRouterModule(await import(pathToFileURL(routerPath).href)).getRoutes();
|
|
150
|
+
}
|
|
151
|
+
async function compileSchemaType(name, schema) {
|
|
152
|
+
if (isUnconstrainedJsonSchema(schema)) return `export type ${name} = unknown`;
|
|
153
|
+
if (isNeverJsonSchema(schema)) return `export type ${name} = never`;
|
|
154
|
+
return (await compile(schema, name, {
|
|
155
|
+
bannerComment: "",
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
style: { singleQuote: true }
|
|
158
|
+
})).trim().replace(/\bNoName\b/g, `${name}Schema`);
|
|
159
|
+
}
|
|
160
|
+
async function formatWithPrettier(source, outputPath) {
|
|
161
|
+
let prettier;
|
|
162
|
+
try {
|
|
163
|
+
prettier = await import("prettier");
|
|
164
|
+
} catch (cause) {
|
|
165
|
+
throw new Error("The --pretty option requires prettier to be installed.", { cause });
|
|
166
|
+
}
|
|
167
|
+
const options = await prettier.resolveConfig(outputPath) ?? {};
|
|
168
|
+
return prettier.format(source, {
|
|
169
|
+
...options,
|
|
170
|
+
filepath: outputPath
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function responseStatusAlias(typeBase, status) {
|
|
174
|
+
return `${typeBase}Response${status}`;
|
|
175
|
+
}
|
|
176
|
+
function isSuccessfulStatus(status) {
|
|
177
|
+
const statusNumber = Number(status);
|
|
178
|
+
return Number.isInteger(statusNumber) && statusNumber >= 200 && statusNumber < 300;
|
|
179
|
+
}
|
|
180
|
+
function responseStatusAliases(route, statusFilter) {
|
|
181
|
+
if (!route.responseBodySchemas) return [];
|
|
182
|
+
return Object.entries(route.responseBodySchemas).filter(([status, responseSchema]) => responseSchema !== void 0 && statusFilter(status)).map(([status]) => responseStatusAlias(route.typeBase, status));
|
|
183
|
+
}
|
|
184
|
+
function unionType(types, fallback) {
|
|
185
|
+
if (types.length === 0) return fallback;
|
|
186
|
+
if (types.length === 1) return types[0];
|
|
187
|
+
return types.join(" | ");
|
|
188
|
+
}
|
|
189
|
+
async function generateRouteTypes(route) {
|
|
190
|
+
const generated = {
|
|
191
|
+
route,
|
|
192
|
+
responseTypeSources: []
|
|
193
|
+
};
|
|
194
|
+
if (route.requestSchema?.path) generated.pathTypeSource = await compileSchemaType(`${route.typeBase}PathParams`, widenNumericStringPathParams(route.requestSchema.path, route.pathParams));
|
|
195
|
+
if (route.requestSchema?.query) generated.queryTypeSource = await compileSchemaType(`${route.typeBase}Query`, route.requestSchema.query);
|
|
196
|
+
if (route.requestSchema?.body !== void 0) generated.requestTypeSource = await compileSchemaType(`${route.typeBase}Request`, route.requestSchema.body);
|
|
197
|
+
if (route.responseBodySchemas && Object.keys(route.responseBodySchemas).length > 0) {
|
|
198
|
+
const responseTypesByStatus = [];
|
|
199
|
+
for (const [status, responseSchema] of Object.entries(route.responseBodySchemas)) {
|
|
200
|
+
if (responseSchema === void 0) continue;
|
|
201
|
+
const alias = responseStatusAlias(route.typeBase, status);
|
|
202
|
+
generated.responseTypeSources.push(await compileSchemaType(alias, responseSchema));
|
|
203
|
+
responseTypesByStatus.push(` ${JSON.stringify(status)}: ${alias}`);
|
|
204
|
+
}
|
|
205
|
+
generated.responseTypesByStatusSource = responseTypesByStatus.length > 0 ? [
|
|
206
|
+
`export interface ${route.typeBase}ResponsesByStatus {`,
|
|
207
|
+
...responseTypesByStatus,
|
|
208
|
+
`}`,
|
|
209
|
+
`export type ${route.typeBase}Response = ${route.typeBase}ResponsesByStatus[keyof ${route.typeBase}ResponsesByStatus]`
|
|
210
|
+
].join("\n") : `export type ${route.typeBase}Response = unknown`;
|
|
211
|
+
} else {
|
|
212
|
+
const fallback = route.method === HttpMethod.HEAD ? "never" : "unknown";
|
|
213
|
+
generated.responseTypesByStatusSource = `export type ${route.typeBase}Response = ${fallback}`;
|
|
214
|
+
}
|
|
215
|
+
return generated;
|
|
216
|
+
}
|
|
217
|
+
function buildMethodLines(route, options, indent) {
|
|
218
|
+
const lines = [];
|
|
219
|
+
const methodName = route.method.toLowerCase();
|
|
220
|
+
const bodyType = route.requestSchema?.body !== void 0 ? `${route.typeBase}Request` : void 0;
|
|
221
|
+
const queryType = route.requestSchema?.query ? `${route.typeBase}Query` : void 0;
|
|
222
|
+
const hasPathParams = route.pathParams.length > 0;
|
|
223
|
+
const hasSinglePathParam = route.pathParams.length === 1;
|
|
224
|
+
let pathVar = "path";
|
|
225
|
+
if (hasPathParams) {
|
|
226
|
+
if (hasSinglePathParam) pathVar = "_path";
|
|
227
|
+
}
|
|
228
|
+
const shouldResolveApiResponse = options.responseType === DEFAULT_RESPONSE_TYPE;
|
|
229
|
+
const hasResponsesByStatus = !!route.responseBodySchemas && Object.keys(route.responseBodySchemas).length > 0;
|
|
230
|
+
const returnType = shouldResolveApiResponse && hasResponsesByStatus ? `ApiResponseByStatusPromise<${route.typeBase}ResponsesByStatus>` : `${options.responseType}<${route.typeBase}Response>`;
|
|
231
|
+
const params = [];
|
|
232
|
+
if (hasPathParams) {
|
|
233
|
+
let pathParamType = pathTypeForRoute(route);
|
|
234
|
+
if (hasSinglePathParam) {
|
|
235
|
+
const singleParamType = route.requestSchema?.path ? `SinglePathParam<${route.typeBase}PathParams, "${route.pathParams[0]}">` : "any";
|
|
236
|
+
pathParamType = `${pathParamType} | ${singleParamType}`;
|
|
237
|
+
}
|
|
238
|
+
params.push(`path: ${pathParamType}`);
|
|
239
|
+
}
|
|
240
|
+
if (queryType) params.push(`query: ${queryType}`);
|
|
241
|
+
if (bodyType) params.push(`body: ${bodyType}`);
|
|
242
|
+
lines.push(`${indent}${methodName} = (${params.join(", ")}): ${returnType} => {`);
|
|
243
|
+
if (hasSinglePathParam) lines.push(`${indent} const _path = typeof path === 'object' && path !== null && !Array.isArray(path) ? path : { ${route.pathParams[0]}: path } as any`);
|
|
244
|
+
const urlExpr = patternToUrlTemplate(route.path, pathVar);
|
|
245
|
+
const finalUrlExpr = queryType ? `withQuery(${urlExpr}, query)` : urlExpr;
|
|
246
|
+
const resolverExpression = shouldResolveApiResponse && hasResponsesByStatus ? `resolveApiResponseByStatus<${route.typeBase}ResponsesByStatus>` : "resolveApiResponse";
|
|
247
|
+
lines.push(`${indent} return ${shouldResolveApiResponse ? `${resolverExpression}(` : ""}this.transport.request({`);
|
|
248
|
+
lines.push(`${indent} url: ${finalUrlExpr},`);
|
|
249
|
+
lines.push(`${indent} init: {`);
|
|
250
|
+
lines.push(`${indent} method: "${route.method}",`);
|
|
251
|
+
lines.push(`${indent} },`);
|
|
252
|
+
if (bodyType) lines.push(`${indent} body,`);
|
|
253
|
+
if (shouldResolveApiResponse) lines.push(`${indent} }))`);
|
|
254
|
+
else lines.push(`${indent} }) as ${returnType}`);
|
|
255
|
+
lines.push(`${indent}}`);
|
|
256
|
+
return lines;
|
|
257
|
+
}
|
|
258
|
+
function pathTypeForRoute(route) {
|
|
259
|
+
return route.requestSchema?.path ? `${route.typeBase}PathParams` : "any";
|
|
260
|
+
}
|
|
261
|
+
function pathParameterTypeForRoute(route) {
|
|
262
|
+
let pathParamType = pathTypeForRoute(route);
|
|
263
|
+
if (route.pathParams.length === 1) {
|
|
264
|
+
const singleParamType = route.requestSchema?.path ? `SinglePathParam<${route.typeBase}PathParams, "${route.pathParams[0]}">` : "any";
|
|
265
|
+
pathParamType = `${pathParamType} | ${singleParamType}`;
|
|
266
|
+
}
|
|
267
|
+
return pathParamType;
|
|
268
|
+
}
|
|
269
|
+
function routeRequestComponents(route) {
|
|
270
|
+
const components = [];
|
|
271
|
+
if (route.pathParams.length > 0) components.push({
|
|
272
|
+
name: "path",
|
|
273
|
+
type: pathParameterTypeForRoute(route)
|
|
274
|
+
});
|
|
275
|
+
if (route.requestSchema?.query) components.push({
|
|
276
|
+
name: "query",
|
|
277
|
+
type: `${route.typeBase}Query`
|
|
278
|
+
});
|
|
279
|
+
if (route.requestSchema?.body !== void 0) components.push({
|
|
280
|
+
name: "body",
|
|
281
|
+
type: `${route.typeBase}Request`
|
|
282
|
+
});
|
|
283
|
+
return components;
|
|
284
|
+
}
|
|
285
|
+
function emitClass(node, parts, options, lines) {
|
|
286
|
+
const className = classNameForParts(parts, options.clientName);
|
|
287
|
+
const exportKeyword = parts.length === 0 ? "export " : "";
|
|
288
|
+
const childNames = Array.from(node.children.keys());
|
|
289
|
+
lines.push(`${exportKeyword}class ${className} {`);
|
|
290
|
+
if (parts.length === 0) lines.push(` private readonly transport: ClientTransport`);
|
|
291
|
+
for (const childName of childNames) {
|
|
292
|
+
const childClass = classNameForParts([...parts, childName], options.clientName);
|
|
293
|
+
lines.push(` private _${childName}?: ${childClass}`);
|
|
294
|
+
}
|
|
295
|
+
if (parts.length === 0 || childNames.length > 0) lines.push(``);
|
|
296
|
+
if (parts.length === 0) {
|
|
297
|
+
lines.push(` constructor(transport?: ClientTransport) {`);
|
|
298
|
+
lines.push(` this.transport = transport ?? new FetchTransport()`);
|
|
299
|
+
lines.push(` }`);
|
|
300
|
+
} else lines.push(` constructor(private readonly transport: ClientTransport) {}`);
|
|
301
|
+
if (node.children.size > 0) {
|
|
302
|
+
lines.push(``);
|
|
303
|
+
childNames.forEach((childName, idx) => {
|
|
304
|
+
const childClass = classNameForParts([...parts, childName], options.clientName);
|
|
305
|
+
lines.push(` get ${childName}(): ${childClass} {`);
|
|
306
|
+
lines.push(` return (this._${childName} ??= new ${childClass}(this.transport))`);
|
|
307
|
+
lines.push(` }`);
|
|
308
|
+
if (idx !== childNames.length - 1 || node.routes.length > 0) lines.push(``);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (node.routes.length > 0) node.routes.forEach((route, idx) => {
|
|
312
|
+
const methodLines = buildMethodLines(route, options, " ");
|
|
313
|
+
for (const line of methodLines) lines.push(line);
|
|
314
|
+
if (idx !== node.routes.length - 1) lines.push(``);
|
|
315
|
+
});
|
|
316
|
+
lines.push(`}`);
|
|
317
|
+
lines.push(``);
|
|
318
|
+
for (const [childName, childNode] of node.children) emitClass(childNode, [...parts, childName], options, lines);
|
|
319
|
+
}
|
|
320
|
+
function emitGeneratedRouteTypeSources(generatedTypes, lines) {
|
|
321
|
+
for (const generated of generatedTypes) {
|
|
322
|
+
if (generated.pathTypeSource && !isUnknown(generated.pathTypeSource)) lines.push(generated.pathTypeSource, ``);
|
|
323
|
+
if (generated.queryTypeSource && !isUnknown(generated.queryTypeSource)) lines.push(generated.queryTypeSource, ``);
|
|
324
|
+
if (generated.requestTypeSource && !isUnknown(generated.requestTypeSource)) lines.push(generated.requestTypeSource, ``);
|
|
325
|
+
for (const responseTypeSource of generated.responseTypeSources) if (!isUnknown(responseTypeSource)) lines.push(responseTypeSource, ``);
|
|
326
|
+
if (generated.responseTypesByStatusSource) lines.push(generated.responseTypesByStatusSource, ``);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function routeSuccessBodyType(route) {
|
|
330
|
+
return unionType(responseStatusAliases(route, isSuccessfulStatus), "unknown");
|
|
331
|
+
}
|
|
332
|
+
function routeProblemBodyType(route) {
|
|
333
|
+
return unionType(responseStatusAliases(route, (status) => !isSuccessfulStatus(status)), "unknown");
|
|
334
|
+
}
|
|
335
|
+
function isQueryRoute(route) {
|
|
336
|
+
return route.method === HttpMethod.GET || route.method === HttpMethod.HEAD;
|
|
337
|
+
}
|
|
338
|
+
function emitTsQueryRouteTypeAliases(generatedTypes, lines) {
|
|
339
|
+
for (const { route } of generatedTypes) {
|
|
340
|
+
const successBodyType = routeSuccessBodyType(route);
|
|
341
|
+
const problemBodyType = routeProblemBodyType(route);
|
|
342
|
+
lines.push(`export type ${route.typeBase}Data = RoutekitProblemSuccessData<${successBodyType}>`);
|
|
343
|
+
lines.push(`export type ${route.typeBase}Problem = ${problemBodyType}`);
|
|
344
|
+
lines.push(`export type ${route.typeBase}Error = RoutekitProblemError<${route.typeBase}Problem>`);
|
|
345
|
+
lines.push(``);
|
|
346
|
+
const components = routeRequestComponents(route);
|
|
347
|
+
if (!isQueryRoute(route) && components.length > 1) {
|
|
348
|
+
lines.push(`export interface ${route.typeBase}Variables {`);
|
|
349
|
+
for (const component of components) lines.push(` ${component.name}: ${component.type}`);
|
|
350
|
+
lines.push(`}`);
|
|
351
|
+
lines.push(``);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function buildTransportRequestLines(route, indent, pathVar, queryVar, bodyVar) {
|
|
356
|
+
const lines = [];
|
|
357
|
+
const urlExpr = patternToUrlTemplate(route.path, pathVar);
|
|
358
|
+
const finalUrlExpr = queryVar ? `withQuery(${urlExpr}, ${queryVar})` : urlExpr;
|
|
359
|
+
lines.push(`${indent}transport.request({`);
|
|
360
|
+
lines.push(`${indent} url: ${finalUrlExpr},`);
|
|
361
|
+
lines.push(`${indent} init: {`);
|
|
362
|
+
lines.push(`${indent} method: "${route.method}",`);
|
|
363
|
+
lines.push(`${indent} },`);
|
|
364
|
+
if (bodyVar) lines.push(`${indent} body: ${bodyVar},`);
|
|
365
|
+
lines.push(`${indent}})`);
|
|
366
|
+
return lines;
|
|
367
|
+
}
|
|
368
|
+
function pushSinglePathParamNormalization(route, lines, indent) {
|
|
369
|
+
if (route.pathParams.length !== 1) return "path";
|
|
370
|
+
lines.push(`${indent}const _path = typeof path === 'object' && path !== null && !Array.isArray(path) ? path : { ${route.pathParams[0]}: path } as any`);
|
|
371
|
+
return "_path";
|
|
372
|
+
}
|
|
373
|
+
function tsQueryKey(route, components) {
|
|
374
|
+
return [
|
|
375
|
+
JSON.stringify("routekit"),
|
|
376
|
+
JSON.stringify(route.method),
|
|
377
|
+
JSON.stringify(route.path),
|
|
378
|
+
...components
|
|
379
|
+
].join(", ");
|
|
380
|
+
}
|
|
381
|
+
function emitQueryHelper(route, lines, indent) {
|
|
382
|
+
const components = routeRequestComponents(route);
|
|
383
|
+
const params = components.map((component) => `${component.name}: ${component.type}`).join(", ");
|
|
384
|
+
const methodName = route.method.toLowerCase();
|
|
385
|
+
const successBodyType = routeSuccessBodyType(route);
|
|
386
|
+
const queryKeyComponents = [];
|
|
387
|
+
lines.push(`${indent}${methodName}: (${params}) => {`);
|
|
388
|
+
const pathVar = pushSinglePathParamNormalization(route, lines, `${indent} `);
|
|
389
|
+
for (const component of components) queryKeyComponents.push(component.name === "path" ? pathVar : component.name);
|
|
390
|
+
lines.push(`${indent} return queryOptions<${route.typeBase}Data, ${route.typeBase}Error>({`);
|
|
391
|
+
lines.push(`${indent} queryKey: [${tsQueryKey(route, queryKeyComponents)}] as const,`);
|
|
392
|
+
lines.push(`${indent} queryFn: (): Promise<${route.typeBase}Data> => {`);
|
|
393
|
+
lines.push(`${indent} return resolveRoutekitProblemData<${successBodyType}, ${route.typeBase}Problem>(`);
|
|
394
|
+
for (const requestLine of buildTransportRequestLines(route, `${indent} `, pathVar, route.requestSchema?.query ? "query" : void 0, route.requestSchema?.body !== void 0 ? "body" : void 0)) lines.push(requestLine);
|
|
395
|
+
lines.push(`${indent} )`);
|
|
396
|
+
lines.push(`${indent} },`);
|
|
397
|
+
lines.push(`${indent} })`);
|
|
398
|
+
lines.push(`${indent}},`);
|
|
399
|
+
}
|
|
400
|
+
function emitMutationHelper(route, lines, indent) {
|
|
401
|
+
const components = routeRequestComponents(route);
|
|
402
|
+
const methodName = route.method.toLowerCase();
|
|
403
|
+
const successBodyType = routeSuccessBodyType(route);
|
|
404
|
+
const variablesType = components.length === 0 ? "void" : components.length === 1 ? components[0].type : `${route.typeBase}Variables`;
|
|
405
|
+
lines.push(`${indent}${methodName}: () => {`);
|
|
406
|
+
lines.push(`${indent} return mutationOptions<${route.typeBase}Data, ${route.typeBase}Error, ${variablesType}>({`);
|
|
407
|
+
lines.push(`${indent} mutationKey: [${tsQueryKey(route, [])}] as const,`);
|
|
408
|
+
let mutationParam = "";
|
|
409
|
+
if (components.length === 1) {
|
|
410
|
+
const component = components[0];
|
|
411
|
+
mutationParam = `${component.name}: ${component.type}`;
|
|
412
|
+
} else if (components.length > 1) mutationParam = `variables: ${route.typeBase}Variables`;
|
|
413
|
+
lines.push(`${indent} mutationFn: (${mutationParam}): Promise<${route.typeBase}Data> => {`);
|
|
414
|
+
if (components.length > 1) for (const component of components) lines.push(`${indent} const ${component.name} = variables.${component.name}`);
|
|
415
|
+
const pathVar = components.some((component) => component.name === "path") && route.pathParams.length === 1 ? pushSinglePathParamNormalization(route, lines, `${indent} `) : "path";
|
|
416
|
+
lines.push(`${indent} return resolveRoutekitProblemData<${successBodyType}, ${route.typeBase}Problem>(`);
|
|
417
|
+
for (const requestLine of buildTransportRequestLines(route, `${indent} `, pathVar, route.requestSchema?.query ? "query" : void 0, route.requestSchema?.body !== void 0 ? "body" : void 0)) lines.push(requestLine);
|
|
418
|
+
lines.push(`${indent} )`);
|
|
419
|
+
lines.push(`${indent} },`);
|
|
420
|
+
lines.push(`${indent} })`);
|
|
421
|
+
lines.push(`${indent}},`);
|
|
422
|
+
}
|
|
423
|
+
function emitTsQueryNodeProperties(node, lines, indent) {
|
|
424
|
+
for (const [childName, childNode] of node.children) {
|
|
425
|
+
lines.push(`${indent}${childName}: {`);
|
|
426
|
+
emitTsQueryNodeProperties(childNode, lines, `${indent} `);
|
|
427
|
+
lines.push(`${indent}},`);
|
|
428
|
+
}
|
|
429
|
+
for (const route of node.routes) if (isQueryRoute(route)) emitQueryHelper(route, lines, indent);
|
|
430
|
+
else emitMutationHelper(route, lines, indent);
|
|
431
|
+
}
|
|
432
|
+
async function buildApiClientSource(routes, options) {
|
|
433
|
+
const processedRoutes = routes.map((route) => ({
|
|
434
|
+
...route,
|
|
435
|
+
typeBase: routeTypeBaseName(route),
|
|
436
|
+
pathParams: getPathParamNames(route.path)
|
|
437
|
+
}));
|
|
438
|
+
const needsSinglePathHelper = processedRoutes.some((route) => route.pathParams.length === 1);
|
|
439
|
+
const needsQueryHelper = processedRoutes.some((route) => route.requestSchema?.query);
|
|
440
|
+
const needsResponseByStatusHelper = processedRoutes.some((route) => route.responseBodySchemas && Object.keys(route.responseBodySchemas).length > 0);
|
|
441
|
+
const needsDefaultResponsePromise = processedRoutes.some((route) => !route.responseBodySchemas || Object.keys(route.responseBodySchemas).length === 0);
|
|
442
|
+
const generatedTypes = await Promise.all(processedRoutes.map(generateRouteTypes));
|
|
443
|
+
const lines = [];
|
|
444
|
+
lines.push(`// Do not modify this file. It was auto-generated with the following command:`);
|
|
445
|
+
lines.push(`// $ ${options.commandText}`);
|
|
446
|
+
lines.push(``);
|
|
447
|
+
for (const importType of options.importTypes) lines.push(`import type { ${importType.names.join(", ")} } from '${importType.module}'`);
|
|
448
|
+
const clientImports = [
|
|
449
|
+
"FetchTransport",
|
|
450
|
+
options.responseType === DEFAULT_RESPONSE_TYPE && needsDefaultResponsePromise ? "resolveApiResponse" : void 0,
|
|
451
|
+
options.responseType === DEFAULT_RESPONSE_TYPE && needsResponseByStatusHelper ? "resolveApiResponseByStatus" : void 0,
|
|
452
|
+
needsQueryHelper ? "withQuery" : void 0,
|
|
453
|
+
"type ClientTransport",
|
|
454
|
+
options.responseType === DEFAULT_RESPONSE_TYPE && needsResponseByStatusHelper ? "type ApiResponseByStatusPromise" : void 0,
|
|
455
|
+
options.responseType === DEFAULT_RESPONSE_TYPE && needsDefaultResponsePromise ? `type ${options.responseType}` : void 0,
|
|
456
|
+
needsSinglePathHelper ? "type SinglePathParam" : void 0
|
|
457
|
+
].filter(Boolean);
|
|
458
|
+
lines.push(`import { ${clientImports.join(", ")} } from '@mpen/routekit/client'`);
|
|
459
|
+
if (options.importTypes.length > 0) lines.push(``);
|
|
460
|
+
lines.push(``);
|
|
461
|
+
lines.push(``);
|
|
462
|
+
emitGeneratedRouteTypeSources(generatedTypes, lines);
|
|
463
|
+
emitClass(buildRouteTree(processedRoutes), [], options, lines);
|
|
464
|
+
return lines.join("\n");
|
|
465
|
+
}
|
|
466
|
+
async function buildTsQueryRkProblemSource(routes, options) {
|
|
467
|
+
const processedRoutes = routes.map((route) => ({
|
|
468
|
+
...route,
|
|
469
|
+
typeBase: routeTypeBaseName(route),
|
|
470
|
+
pathParams: getPathParamNames(route.path)
|
|
471
|
+
}));
|
|
472
|
+
const needsSinglePathHelper = processedRoutes.some((route) => route.pathParams.length === 1);
|
|
473
|
+
const needsQueryHelper = processedRoutes.some((route) => route.requestSchema?.query);
|
|
474
|
+
const generatedTypes = await Promise.all(processedRoutes.map(generateRouteTypes));
|
|
475
|
+
const lines = [];
|
|
476
|
+
lines.push(`// Do not modify this file. It was auto-generated with the following command:`);
|
|
477
|
+
lines.push(`// $ ${options.commandText}`);
|
|
478
|
+
lines.push(``);
|
|
479
|
+
for (const importType of options.importTypes) lines.push(`import type { ${importType.names.join(", ")} } from '${importType.module}'`);
|
|
480
|
+
lines.push(`import { mutationOptions, queryOptions } from '@tanstack/react-query'`);
|
|
481
|
+
const clientImports = [
|
|
482
|
+
"FetchTransport",
|
|
483
|
+
"resolveRoutekitProblemData",
|
|
484
|
+
needsQueryHelper ? "withQuery" : void 0,
|
|
485
|
+
"type ClientTransport",
|
|
486
|
+
"type RoutekitProblemError",
|
|
487
|
+
"type RoutekitProblemSuccessData",
|
|
488
|
+
needsSinglePathHelper ? "type SinglePathParam" : void 0
|
|
489
|
+
].filter(Boolean);
|
|
490
|
+
lines.push(`import { ${clientImports.join(", ")} } from '@mpen/routekit/client'`);
|
|
491
|
+
lines.push(``);
|
|
492
|
+
emitGeneratedRouteTypeSources(generatedTypes, lines);
|
|
493
|
+
emitTsQueryRouteTypeAliases(generatedTypes, lines);
|
|
494
|
+
const tree = buildRouteTree(processedRoutes);
|
|
495
|
+
lines.push(`export function createApiQueryHelpers(transport: ClientTransport = new FetchTransport()) {`);
|
|
496
|
+
lines.push(` return {`);
|
|
497
|
+
emitTsQueryNodeProperties(tree, lines, ` `);
|
|
498
|
+
lines.push(` }`);
|
|
499
|
+
lines.push(`}`);
|
|
500
|
+
lines.push(``);
|
|
501
|
+
return lines.join("\n");
|
|
502
|
+
}
|
|
503
|
+
async function main() {
|
|
504
|
+
const { positionals, values } = parseArgs({
|
|
505
|
+
allowPositionals: true,
|
|
506
|
+
strict: true,
|
|
507
|
+
options: {
|
|
508
|
+
output: {
|
|
509
|
+
type: "string",
|
|
510
|
+
short: "o"
|
|
511
|
+
},
|
|
512
|
+
write: {
|
|
513
|
+
type: "boolean",
|
|
514
|
+
short: "w"
|
|
515
|
+
},
|
|
516
|
+
pretty: {
|
|
517
|
+
type: "boolean",
|
|
518
|
+
short: "p"
|
|
519
|
+
},
|
|
520
|
+
format: {
|
|
521
|
+
type: "string",
|
|
522
|
+
short: "f"
|
|
523
|
+
},
|
|
524
|
+
"client-name": { type: "string" },
|
|
525
|
+
"import-type": {
|
|
526
|
+
type: "string",
|
|
527
|
+
multiple: true
|
|
528
|
+
},
|
|
529
|
+
"response-type": { type: "string" },
|
|
530
|
+
help: { type: "boolean" }
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
if (values.help) {
|
|
534
|
+
printHelp();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const [routerPathArg] = positionals;
|
|
538
|
+
if (!routerPathArg) {
|
|
539
|
+
printHelp();
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
const format = parseOutputFormat(values.format);
|
|
543
|
+
const clientName = normalizeClientName(values["client-name"]);
|
|
544
|
+
const responseType = values["response-type"]?.trim() || DEFAULT_RESPONSE_TYPE;
|
|
545
|
+
const importTypes = values["import-type"]?.map(parseImportTypeOption) ?? [];
|
|
546
|
+
const routerPath = path.resolve(routerPathArg);
|
|
547
|
+
let outputPath;
|
|
548
|
+
if (values.output) outputPath = path.resolve(values.output);
|
|
549
|
+
else if (values.write) outputPath = path.join(path.dirname(routerPath), `${path.basename(routerPath, path.extname(routerPath))}.gen.ts`);
|
|
550
|
+
const routes = extractRoutes(await loadRuntimeRoutes(routerPath));
|
|
551
|
+
const rawArgs = process.argv.slice(1);
|
|
552
|
+
if (rawArgs[0] && path.isAbsolute(rawArgs[0])) rawArgs[0] = path.relative(process.cwd(), rawArgs[0]).replace(/\\/g, "/");
|
|
553
|
+
const commandText = ["bun", ...rawArgs.map((arg) => $.escape(arg))].join(" ");
|
|
554
|
+
let client = format === "rk-api-client" ? await buildApiClientSource(routes, {
|
|
555
|
+
clientName,
|
|
556
|
+
responseType,
|
|
557
|
+
importTypes,
|
|
558
|
+
commandText
|
|
559
|
+
}) : await buildTsQueryRkProblemSource(routes, {
|
|
560
|
+
importTypes,
|
|
561
|
+
commandText
|
|
562
|
+
});
|
|
563
|
+
if (outputPath) {
|
|
564
|
+
if (values.pretty) client = await formatWithPrettier(client, outputPath);
|
|
565
|
+
fs.writeFileSync(outputPath, client, "utf8");
|
|
566
|
+
console.log(`Wrote API client to ${path.relative(process.cwd(), outputPath)}`);
|
|
567
|
+
} else console.log(client);
|
|
568
|
+
}
|
|
569
|
+
if (import.meta.main) main().catch((err) => {
|
|
570
|
+
console.error(err);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
});
|
|
573
|
+
//#endregion
|
|
574
|
+
export { main };
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mpen/routekit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed server-side routing utilities for Fetch-compatible runtimes.",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dist/index.mjs",
|
|
7
|
+
"./bin": "./dist/bin.mjs",
|
|
8
|
+
"./client": "./dist/client.mjs",
|
|
9
|
+
"./client/react": "./dist/client/react.mjs",
|
|
10
|
+
"./handlers": "./dist/handlers.mjs",
|
|
11
|
+
"./middleware": "./dist/middleware.mjs",
|
|
12
|
+
"./response/content": "./dist/response/content.mjs",
|
|
13
|
+
"./response/json-rpc": "./dist/response/json-rpc.mjs",
|
|
14
|
+
"./response/problem": "./dist/response/problem.mjs",
|
|
15
|
+
"./response/problem/valibot": "./dist/response/problem/valibot.mjs",
|
|
16
|
+
"./response/status": "./dist/response/status.mjs",
|
|
17
|
+
"./routes": "./dist/routes.mjs",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"routekit": "./dist/bin.mjs"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@mpen/ts-types": "0.1.5",
|
|
30
|
+
"@types/bun": "latest",
|
|
31
|
+
"@types/react": "^19",
|
|
32
|
+
"@types/react-dom": "^19",
|
|
33
|
+
"@tanstack/react-query": "^5",
|
|
34
|
+
"@valibot/to-json-schema": "^1.6.0",
|
|
35
|
+
"bun-types": "latest",
|
|
36
|
+
"happy-dom": "^20.9.0",
|
|
37
|
+
"maxmind": "^5",
|
|
38
|
+
"react": "^19",
|
|
39
|
+
"react-dom": "^19",
|
|
40
|
+
"tsdown": "^0.21",
|
|
41
|
+
"valibot": "^1.4.0",
|
|
42
|
+
"zod": "^4.3.5"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@valibot/to-json-schema": "^1.6.0",
|
|
46
|
+
"maxmind": "^5",
|
|
47
|
+
"prettier": "^3.8.3",
|
|
48
|
+
"react": "^19",
|
|
49
|
+
"@tanstack/react-query": "^5",
|
|
50
|
+
"typescript": "^5",
|
|
51
|
+
"valibot": "^1.4.0",
|
|
52
|
+
"zod": "^4.3.5"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"@valibot/to-json-schema": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"maxmind": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"valibot": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"zod": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"prettier": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"react": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@tanstack/react-query": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"@mpen/http": "0.1.0",
|
|
79
|
+
"@mpen/logger": "0.2.0",
|
|
80
|
+
"json-schema-to-typescript": "^15.0.4"
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "tsdown",
|
|
84
|
+
"gen": "bun ./src/bin/gen-api-client.ts ./examples/example1/server/router-instance.ts -o ./examples/example1/client/api-client.gen.ts",
|
|
85
|
+
"gen3": "bun ./src/bin/gen-api-client.ts ./examples/example3/server/router.ts -o ./examples/example3/client/router.gen.ts -p",
|
|
86
|
+
"react:build": "bun ./examples/react/scripts/build-client.ts",
|
|
87
|
+
"react:dev": "bun ./examples/react/dev.ts",
|
|
88
|
+
"react:gen": "bun ./src/bin/gen-api-client.ts ./examples/react/server/api-router.ts -o ./examples/react/client/api/router.gen.ts -p"
|
|
89
|
+
},
|
|
90
|
+
"main": "./dist/index.mjs",
|
|
91
|
+
"types": "./dist/index.d.mts",
|
|
92
|
+
"publishConfig": {
|
|
93
|
+
"access": "public"
|
|
94
|
+
}
|
|
95
|
+
}
|