@tuyau/core 1.0.0-beta.2 → 1.0.0-beta.4
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/build/backend/generate_registry.js +114 -16
- package/build/client/index.d.ts +17 -19
- package/build/client/index.js +25 -23
- package/build/client/types/index.d.ts +98 -62
- package/package.json +1 -2
|
@@ -31,6 +31,46 @@ function generateRouteParams(route) {
|
|
|
31
31
|
const paramsTuple = dynamicParams.map(() => "string").join(", ");
|
|
32
32
|
return { paramsType, paramsTuple };
|
|
33
33
|
}
|
|
34
|
+
function toCamelCase(str) {
|
|
35
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
function buildTreeStructure(routes) {
|
|
38
|
+
const tree = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const route of routes) {
|
|
40
|
+
const segments = route.name.split(".");
|
|
41
|
+
let current = tree;
|
|
42
|
+
for (let i = 0; i < segments.length; i++) {
|
|
43
|
+
const segment = toCamelCase(segments[i]);
|
|
44
|
+
const isLast = i === segments.length - 1;
|
|
45
|
+
if (isLast) {
|
|
46
|
+
current.set(segment, { routeName: route.name, route });
|
|
47
|
+
} else {
|
|
48
|
+
if (!current.has(segment)) {
|
|
49
|
+
current.set(segment, /* @__PURE__ */ new Map());
|
|
50
|
+
}
|
|
51
|
+
current = current.get(segment);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return tree;
|
|
56
|
+
}
|
|
57
|
+
function generateTreeInterface(tree, indent = 2) {
|
|
58
|
+
const spaces = " ".repeat(indent);
|
|
59
|
+
const lines = [];
|
|
60
|
+
for (const [key, value] of tree) {
|
|
61
|
+
if (value instanceof Map) {
|
|
62
|
+
lines.push(`${spaces}${key}: {`);
|
|
63
|
+
lines.push(generateTreeInterface(value, indent + 2));
|
|
64
|
+
lines.push(`${spaces}}`);
|
|
65
|
+
} else {
|
|
66
|
+
lines.push(`${spaces}${key}: Registry['${value.routeName}']`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
function normalizeImportPaths(typeString) {
|
|
72
|
+
return typeString.replace(/import\('app\//g, "import('#app/").replace(/\.ts'\)/g, "')");
|
|
73
|
+
}
|
|
34
74
|
function generateRuntimeRegistryEntry(route) {
|
|
35
75
|
const routeName = route.name;
|
|
36
76
|
return ` '${routeName}': {
|
|
@@ -40,51 +80,97 @@ function generateRuntimeRegistryEntry(route) {
|
|
|
40
80
|
types: placeholder as Registry['${routeName}']['types'],
|
|
41
81
|
}`;
|
|
42
82
|
}
|
|
83
|
+
function wrapResponseType(responseType) {
|
|
84
|
+
if (responseType === "unknown" || responseType === "{}") return responseType;
|
|
85
|
+
if (responseType.startsWith("ReturnType<")) {
|
|
86
|
+
return `Awaited<${responseType}>`;
|
|
87
|
+
}
|
|
88
|
+
return responseType;
|
|
89
|
+
}
|
|
90
|
+
function determineBodyAndQueryTypes(options) {
|
|
91
|
+
const { methods, requestType } = options;
|
|
92
|
+
const primaryMethod = methods[0];
|
|
93
|
+
const isGetLike = primaryMethod === "GET" || primaryMethod === "HEAD";
|
|
94
|
+
const hasValidator = requestType !== "{}";
|
|
95
|
+
if (!hasValidator) {
|
|
96
|
+
return { bodyType: "{}", queryType: "{}" };
|
|
97
|
+
}
|
|
98
|
+
if (isGetLike) {
|
|
99
|
+
return { bodyType: "{}", queryType: requestType };
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
bodyType: `ExtractBody<${requestType}>`,
|
|
103
|
+
queryType: `ExtractQuery<${requestType}>`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
43
106
|
function generateTypesRegistryEntry(route) {
|
|
44
|
-
const requestType = route.request?.type || "{}";
|
|
45
|
-
const responseType = route.response?.type || "unknown";
|
|
107
|
+
const requestType = normalizeImportPaths(route.request?.type || "{}");
|
|
108
|
+
const responseType = wrapResponseType(route.response?.type || "unknown");
|
|
46
109
|
const { paramsType, paramsTuple } = generateRouteParams(route);
|
|
47
110
|
const routeName = route.name;
|
|
111
|
+
const { bodyType, queryType } = determineBodyAndQueryTypes({
|
|
112
|
+
methods: route.methods,
|
|
113
|
+
requestType
|
|
114
|
+
});
|
|
48
115
|
return ` '${routeName}': {
|
|
49
116
|
methods: ${JSON.stringify(route.methods)}
|
|
50
117
|
pattern: '${route.pattern}'
|
|
51
118
|
types: {
|
|
52
|
-
body: ${
|
|
119
|
+
body: ${bodyType}
|
|
53
120
|
paramsTuple: [${paramsTuple}]
|
|
54
121
|
params: ${paramsType ? `{ ${paramsType} }` : "{}"}
|
|
55
|
-
query: {}
|
|
122
|
+
query: ${queryType}
|
|
56
123
|
response: ${responseType}
|
|
57
124
|
}
|
|
58
125
|
}`;
|
|
59
126
|
}
|
|
60
127
|
function generateRegistryEntry(route) {
|
|
61
|
-
const requestType = route.request?.type || "{}";
|
|
62
|
-
const responseType = route.response?.type || "unknown";
|
|
128
|
+
const requestType = normalizeImportPaths(route.request?.type || "{}");
|
|
129
|
+
const responseType = wrapResponseType(route.response?.type || "unknown");
|
|
63
130
|
const { paramsType, paramsTuple } = generateRouteParams(route);
|
|
64
131
|
const routeName = route.name;
|
|
132
|
+
const { bodyType, queryType } = determineBodyAndQueryTypes({
|
|
133
|
+
methods: route.methods,
|
|
134
|
+
requestType
|
|
135
|
+
});
|
|
65
136
|
return ` '${routeName}': {
|
|
66
137
|
methods: ${JSON.stringify(route.methods)},
|
|
67
138
|
pattern: '${route.pattern}',
|
|
68
139
|
tokens: ${JSON.stringify(route.tokens)},
|
|
69
140
|
types: placeholder as {
|
|
70
|
-
body: ${
|
|
141
|
+
body: ${bodyType}
|
|
71
142
|
paramsTuple: [${paramsTuple}]
|
|
72
143
|
params: ${paramsType ? `{ ${paramsType} }` : "{}"}
|
|
73
|
-
query: {}
|
|
144
|
+
query: ${queryType}
|
|
74
145
|
response: ${responseType}
|
|
75
146
|
},
|
|
76
147
|
}`;
|
|
77
148
|
}
|
|
78
149
|
function generateRuntimeContent(routes) {
|
|
79
150
|
const registryEntries = routes.map(generateRuntimeRegistryEntry).join(",\n");
|
|
151
|
+
const tree = buildTreeStructure(routes);
|
|
152
|
+
const treeInterface = generateTreeInterface(tree);
|
|
80
153
|
return `/* eslint-disable prettier/prettier */
|
|
81
|
-
import {
|
|
82
|
-
import type { Registry } from './registry.schema'
|
|
154
|
+
import type { AdonisEndpoint } from '@tuyau/core/types'
|
|
155
|
+
import type { Registry } from './registry.schema.d.ts'
|
|
83
156
|
const placeholder: any = {}
|
|
84
157
|
|
|
85
|
-
export
|
|
86
|
-
${
|
|
158
|
+
export interface ApiDefinition {
|
|
159
|
+
${treeInterface}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const routes = {
|
|
163
|
+
${registryEntries},
|
|
87
164
|
} as const satisfies Record<string, AdonisEndpoint>
|
|
165
|
+
|
|
166
|
+
export const registry = {
|
|
167
|
+
routes,
|
|
168
|
+
$tree: {} as ApiDefinition,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
declare module '@tuyau/core/types' {
|
|
172
|
+
export interface UserApiDefinition extends ApiDefinition {}
|
|
173
|
+
}
|
|
88
174
|
`;
|
|
89
175
|
}
|
|
90
176
|
function generateTypesContent(routes) {
|
|
@@ -92,7 +178,7 @@ function generateTypesContent(routes) {
|
|
|
92
178
|
return `/* eslint-disable prettier/prettier */
|
|
93
179
|
/// <reference path="../../adonisrc.ts" />
|
|
94
180
|
|
|
95
|
-
import type {
|
|
181
|
+
import type { ExtractBody, ExtractQuery } from '@tuyau/core/types'
|
|
96
182
|
import type { Infer } from '@vinejs/vine/types'
|
|
97
183
|
|
|
98
184
|
export interface Registry {
|
|
@@ -106,17 +192,29 @@ declare module '@tuyau/core/types' {
|
|
|
106
192
|
}
|
|
107
193
|
function generateRegistryContent(routes) {
|
|
108
194
|
const registryEntries = routes.map(generateRegistryEntry).join(",\n");
|
|
195
|
+
const tree = buildTreeStructure(routes);
|
|
196
|
+
const treeInterface = generateTreeInterface(tree);
|
|
109
197
|
return `/* eslint-disable prettier/prettier */
|
|
110
|
-
import type { AdonisEndpoint } from '@tuyau/core/types'
|
|
198
|
+
import type { AdonisEndpoint, ExtractBody, ExtractQuery } from '@tuyau/core/types'
|
|
111
199
|
import type { Infer } from '@vinejs/vine/types'
|
|
112
200
|
|
|
113
201
|
const placeholder: any = {}
|
|
114
|
-
|
|
202
|
+
|
|
203
|
+
interface ApiDefinition {
|
|
204
|
+
${treeInterface}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const routes = {
|
|
115
208
|
${registryEntries}
|
|
116
209
|
} as const satisfies Record<string, AdonisEndpoint>
|
|
117
210
|
|
|
211
|
+
export const registry = {
|
|
212
|
+
routes,
|
|
213
|
+
$tree: {} as ApiDefinition,
|
|
214
|
+
}
|
|
215
|
+
|
|
118
216
|
declare module '@tuyau/core/types' {
|
|
119
|
-
type Registry = typeof
|
|
217
|
+
type Registry = typeof routes
|
|
120
218
|
export interface UserRegistry extends Registry {}
|
|
121
219
|
}
|
|
122
220
|
`;
|
package/build/client/index.d.ts
CHANGED
|
@@ -1,64 +1,62 @@
|
|
|
1
1
|
import { UrlFor } from '@adonisjs/http-server/client/url_builder';
|
|
2
|
-
import {
|
|
2
|
+
import { TuyauRegistry, TuyauConfiguration, AdonisEndpoint, InferRoutes, TransformApiDefinition, InferTree, RegistryGroupedByMethod, PatternsByMethod, RequestArgs, EndpointByMethodPattern, StrKeys, Method } from './types/index.js';
|
|
3
3
|
import { KyResponse, KyRequest, HTTPError } from 'ky';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Main client class for making HTTP requests to AdonisJS endpoints
|
|
7
7
|
* Provides both fluent API and direct method calling capabilities
|
|
8
|
+
*
|
|
9
|
+
* @typeParam Reg - The full registry containing routes and $tree
|
|
10
|
+
* @typeParam Routes - The routes record extracted from the registry
|
|
8
11
|
*/
|
|
9
|
-
declare class Tuyau<
|
|
12
|
+
declare class Tuyau<Reg extends TuyauRegistry, Routes extends Record<string, AdonisEndpoint> = InferRoutes<Reg>> {
|
|
10
13
|
#private;
|
|
11
|
-
|
|
12
|
-
readonly
|
|
13
|
-
readonly urlFor: UrlFor<RegistryGroupedByMethod<R>>;
|
|
14
|
+
readonly api: TransformApiDefinition<InferTree<Reg>>;
|
|
15
|
+
readonly urlFor: UrlFor<RegistryGroupedByMethod<Routes>>;
|
|
14
16
|
/**
|
|
15
17
|
* Initializes the Tuyau client with provided configuration
|
|
16
18
|
*/
|
|
17
|
-
constructor(config: TuyauConfiguration<
|
|
19
|
+
constructor(config: TuyauConfiguration<Reg>);
|
|
18
20
|
/**
|
|
19
21
|
* Makes a GET request to the specified pattern
|
|
20
22
|
*/
|
|
21
|
-
get<P extends PatternsByMethod<
|
|
23
|
+
get<P extends PatternsByMethod<Routes, 'GET'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'GET', P>>): Promise<EndpointByMethodPattern<Routes, 'GET', P>['types']['response']>;
|
|
22
24
|
/**
|
|
23
25
|
* Makes a POST request to the specified pattern
|
|
24
26
|
*/
|
|
25
|
-
post<P extends PatternsByMethod<
|
|
27
|
+
post<P extends PatternsByMethod<Routes, 'POST'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'POST', P>>): Promise<EndpointByMethodPattern<Routes, 'POST', P>['types']['response']>;
|
|
26
28
|
/**
|
|
27
29
|
* Makes a PUT request to the specified pattern
|
|
28
30
|
*/
|
|
29
|
-
put<P extends PatternsByMethod<
|
|
31
|
+
put<P extends PatternsByMethod<Routes, 'PUT'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'PUT', P>>): Promise<EndpointByMethodPattern<Routes, 'PUT', P>['types']['response']>;
|
|
30
32
|
/**
|
|
31
33
|
* Makes a PATCH request to the specified pattern
|
|
32
34
|
*/
|
|
33
|
-
patch<P extends PatternsByMethod<
|
|
35
|
+
patch<P extends PatternsByMethod<Routes, 'PATCH'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'PATCH', P>>): Promise<EndpointByMethodPattern<Routes, 'PATCH', P>['types']['response']>;
|
|
34
36
|
/**
|
|
35
37
|
* Makes a DELETE request to the specified pattern
|
|
36
38
|
*/
|
|
37
|
-
delete<P extends PatternsByMethod<
|
|
39
|
+
delete<P extends PatternsByMethod<Routes, 'DELETE'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'DELETE', P>>): Promise<EndpointByMethodPattern<Routes, 'DELETE', P>['types']['response']>;
|
|
38
40
|
/**
|
|
39
41
|
* Makes a HEAD request to the specified pattern
|
|
40
42
|
*/
|
|
41
|
-
head<P extends PatternsByMethod<
|
|
43
|
+
head<P extends PatternsByMethod<Routes, 'HEAD'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'HEAD', P>>): Promise<EndpointByMethodPattern<Routes, 'HEAD', P>['types']['response']>;
|
|
42
44
|
/**
|
|
43
45
|
* Makes a request to a named endpoint
|
|
44
46
|
*/
|
|
45
|
-
request<Name extends StrKeys<
|
|
47
|
+
request<Name extends StrKeys<Routes>>(name: Name, args: RequestArgs<Routes[Name]>): Promise<Routes[Name]['types']['response']>;
|
|
46
48
|
/**
|
|
47
49
|
* Gets route information by name including URL and HTTP method
|
|
48
50
|
*/
|
|
49
|
-
getRoute<Name extends StrKeys<
|
|
51
|
+
getRoute<Name extends StrKeys<Routes>>(name: Name, args: RequestArgs<Routes[Name]>): {
|
|
50
52
|
url: string;
|
|
51
53
|
methods: Method[];
|
|
52
54
|
};
|
|
53
|
-
/**
|
|
54
|
-
* Creates a proxy-based fluent API for accessing endpoints by name
|
|
55
|
-
*/
|
|
56
|
-
private makeNamed;
|
|
57
55
|
}
|
|
58
56
|
/**
|
|
59
57
|
* Factory function to create a new Tuyau client instance
|
|
60
58
|
*/
|
|
61
|
-
declare function createTuyau<
|
|
59
|
+
declare function createTuyau<Reg extends TuyauRegistry>(config: TuyauConfiguration<Reg>): Tuyau<Reg, InferRoutes<Reg>>;
|
|
62
60
|
|
|
63
61
|
declare function parseResponse(response?: KyResponse): Promise<unknown>;
|
|
64
62
|
declare class TuyauHTTPError extends Error {
|
package/build/client/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/client/tuyau.ts
|
|
2
|
-
import ky, { HTTPError } from "ky";
|
|
3
2
|
import { serialize } from "object-to-formdata";
|
|
3
|
+
import ky, { HTTPError } from "ky";
|
|
4
4
|
import { createUrlBuilder } from "@adonisjs/http-server/client/url_builder";
|
|
5
5
|
|
|
6
6
|
// src/client/errors.ts
|
|
@@ -33,6 +33,7 @@ var TuyauHTTPError = class extends Error {
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
var TuyauNetworkError = class extends Error {
|
|
36
|
+
cause;
|
|
36
37
|
constructor(cause, request) {
|
|
37
38
|
const message = request ? `Network error: ${request.method.toUpperCase()} ${request.url}` : "Network error occurred";
|
|
38
39
|
super(message, { cause });
|
|
@@ -76,32 +77,22 @@ function segmentsToRouteName(segments) {
|
|
|
76
77
|
|
|
77
78
|
// src/client/tuyau.ts
|
|
78
79
|
var Tuyau = class {
|
|
79
|
-
/**
|
|
80
|
-
* Initializes the Tuyau client with provided configuration
|
|
81
|
-
*/
|
|
82
|
-
constructor(config) {
|
|
83
|
-
this.config = config;
|
|
84
|
-
this.api = this.makeNamed([]);
|
|
85
|
-
this.#entries = Object.entries(this.config.registry);
|
|
86
|
-
this.urlFor = this.#createUrlBuilder();
|
|
87
|
-
this.#applyPlugins();
|
|
88
|
-
this.#client = ky.create(this.#mergeKyConfiguration());
|
|
89
|
-
}
|
|
90
80
|
api;
|
|
91
81
|
urlFor;
|
|
92
82
|
#entries;
|
|
93
83
|
#client;
|
|
84
|
+
#config;
|
|
94
85
|
/**
|
|
95
86
|
* Merges the default Ky configuration with user-provided config
|
|
96
87
|
*/
|
|
97
88
|
#mergeKyConfiguration() {
|
|
98
89
|
return {
|
|
99
|
-
prefixUrl: this
|
|
100
|
-
...this
|
|
90
|
+
prefixUrl: this.#config.baseUrl,
|
|
91
|
+
...this.#config,
|
|
101
92
|
hooks: {
|
|
102
|
-
...this
|
|
93
|
+
...this.#config.hooks,
|
|
103
94
|
beforeRequest: [
|
|
104
|
-
...this
|
|
95
|
+
...this.#config.hooks?.beforeRequest || [],
|
|
105
96
|
this.#appendCsrfToken.bind(this)
|
|
106
97
|
]
|
|
107
98
|
}
|
|
@@ -111,7 +102,7 @@ var Tuyau = class {
|
|
|
111
102
|
* Applies registered plugins to the client configuration
|
|
112
103
|
*/
|
|
113
104
|
#applyPlugins() {
|
|
114
|
-
this
|
|
105
|
+
this.#config.plugins?.forEach((plugin) => plugin({ options: this.#config }));
|
|
115
106
|
}
|
|
116
107
|
/**
|
|
117
108
|
* Creates a URL builder instance for generating URLs based on the route registry
|
|
@@ -120,6 +111,17 @@ var Tuyau = class {
|
|
|
120
111
|
const rootEntries = this.#entries.map(([name, entry]) => ({ name, domain: "root", ...entry }));
|
|
121
112
|
return createUrlBuilder({ root: rootEntries }, buildSearchParams);
|
|
122
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Initializes the Tuyau client with provided configuration
|
|
116
|
+
*/
|
|
117
|
+
constructor(config) {
|
|
118
|
+
this.#config = config;
|
|
119
|
+
this.api = this.#makeNamed([]);
|
|
120
|
+
this.#entries = Object.entries(this.#config.registry.routes);
|
|
121
|
+
this.urlFor = this.#createUrlBuilder();
|
|
122
|
+
this.#applyPlugins();
|
|
123
|
+
this.#client = ky.create(this.#mergeKyConfiguration());
|
|
124
|
+
}
|
|
123
125
|
/**
|
|
124
126
|
* Automatically appends CSRF token from cookies to requests
|
|
125
127
|
*/
|
|
@@ -254,14 +256,14 @@ var Tuyau = class {
|
|
|
254
256
|
* Makes a request to a named endpoint
|
|
255
257
|
*/
|
|
256
258
|
request(name, args) {
|
|
257
|
-
const def = this
|
|
259
|
+
const def = this.#config.registry.routes[name];
|
|
258
260
|
return this.#doFetch(name, def.methods[0], args);
|
|
259
261
|
}
|
|
260
262
|
/**
|
|
261
263
|
* Gets route information by name including URL and HTTP method
|
|
262
264
|
*/
|
|
263
265
|
getRoute(name, args) {
|
|
264
|
-
const def = this
|
|
266
|
+
const def = this.#config.registry.routes[name];
|
|
265
267
|
if (!def) throw new Error(`Route ${String(name)} not found`);
|
|
266
268
|
const url = this.#buildUrl(name, def.methods[0], args);
|
|
267
269
|
return { url, methods: def.methods };
|
|
@@ -269,17 +271,17 @@ var Tuyau = class {
|
|
|
269
271
|
/**
|
|
270
272
|
* Creates a proxy-based fluent API for accessing endpoints by name
|
|
271
273
|
*/
|
|
272
|
-
makeNamed(segments) {
|
|
274
|
+
#makeNamed(segments) {
|
|
273
275
|
const routeName = segmentsToRouteName(segments);
|
|
274
|
-
const def = this
|
|
276
|
+
const def = this.#config.registry.routes[routeName];
|
|
275
277
|
if (def) {
|
|
276
278
|
const fn = (args) => this.#doFetch(routeName, def.methods[0], args);
|
|
277
279
|
return new Proxy(fn, {
|
|
278
|
-
get: (_t, prop) => this
|
|
280
|
+
get: (_t, prop) => this.#makeNamed([...segments, String(prop)]),
|
|
279
281
|
apply: (_t, _this, argArray) => fn(...argArray)
|
|
280
282
|
});
|
|
281
283
|
}
|
|
282
|
-
return new Proxy({}, { get: (_t, prop) => this
|
|
284
|
+
return new Proxy({}, { get: (_t, prop) => this.#makeNamed([...segments, String(prop)]) });
|
|
283
285
|
}
|
|
284
286
|
};
|
|
285
287
|
function createTuyau(config) {
|
|
@@ -6,97 +6,133 @@ import { ClientRouteMatchItTokens } from '@adonisjs/http-server/client/url_build
|
|
|
6
6
|
*/
|
|
7
7
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Base endpoint types structure
|
|
10
|
+
*/
|
|
11
|
+
interface EndpointTypes {
|
|
12
|
+
paramsTuple: [...any[]];
|
|
13
|
+
params: Record<string, string | number | boolean>;
|
|
14
|
+
query: Record<string, any>;
|
|
15
|
+
body: unknown;
|
|
16
|
+
response: unknown;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Schema endpoint, used in generated registry.schema.d.ts
|
|
20
|
+
* Does not include tokens (only present in runtime routes)
|
|
10
21
|
*/
|
|
11
|
-
interface
|
|
22
|
+
interface SchemaEndpoint {
|
|
12
23
|
methods: Method[];
|
|
13
24
|
pattern: string;
|
|
25
|
+
types: EndpointTypes;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Definition of an AdonisJS endpoint with types and metadata
|
|
29
|
+
* Includes tokens for runtime route building
|
|
30
|
+
*/
|
|
31
|
+
interface AdonisEndpoint extends SchemaEndpoint {
|
|
14
32
|
tokens: ClientRouteMatchItTokens[];
|
|
15
|
-
types: {
|
|
16
|
-
paramsTuple: [...any[]];
|
|
17
|
-
params: Record<string, string | number | boolean>;
|
|
18
|
-
query: Record<string, any>;
|
|
19
|
-
body: unknown;
|
|
20
|
-
response: unknown;
|
|
21
|
-
};
|
|
22
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Extract query params from a validator type if it has a 'query' property.
|
|
36
|
+
* Used in generated registry to separate query params from body for POST/PUT/PATCH/DELETE.
|
|
37
|
+
* For GET/HEAD, the validator type is used directly as query.
|
|
38
|
+
*/
|
|
39
|
+
type ExtractQuery<T> = T extends {
|
|
40
|
+
query: infer Q;
|
|
41
|
+
} ? (Q extends undefined ? {} : Q) : {};
|
|
42
|
+
/**
|
|
43
|
+
* Extract body from a validator type, excluding 'query' and 'params' properties.
|
|
44
|
+
* Used in generated registry to separate body from query/params for POST/PUT/PATCH/DELETE.
|
|
45
|
+
* Returns {} if the validator only contains query/params, otherwise returns the body fields.
|
|
46
|
+
*/
|
|
47
|
+
type ExtractBody<T> = T extends {
|
|
48
|
+
query: any;
|
|
49
|
+
} | {
|
|
50
|
+
params: any;
|
|
51
|
+
} ? Omit<T, 'query' | 'params'> extends infer B ? keyof B extends never ? {} : B : {} : T;
|
|
23
52
|
/**
|
|
24
53
|
* Registry mapping endpoint names to their definitions
|
|
25
54
|
*/
|
|
26
55
|
interface AdonisRegistry extends Record<string, AdonisEndpoint> {
|
|
27
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Should be augmented by the user to provide their API definition tree
|
|
59
|
+
* This is the pre-computed tree structure generated at build time
|
|
60
|
+
*/
|
|
61
|
+
interface UserApiDefinition {
|
|
62
|
+
}
|
|
28
63
|
type ValueOf<T> = T[keyof T];
|
|
29
|
-
type IsEmptyObj<T> = keyof T extends never ? true : false;
|
|
30
|
-
type IsEmptyTuple<T> = T extends [] ? true : false;
|
|
31
|
-
type Endpoints = ValueOf<AdonisRegistry>;
|
|
32
|
-
type EndpointByName<Name extends keyof AdonisRegistry & string> = AdonisRegistry[Name];
|
|
33
64
|
/**
|
|
34
|
-
*
|
|
65
|
+
* Split a string by a delimiter
|
|
35
66
|
*/
|
|
36
|
-
type
|
|
67
|
+
type Split<S extends string, D extends string = '.'> = S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
|
|
37
68
|
/**
|
|
38
|
-
*
|
|
69
|
+
* Convert a union type to an intersection type
|
|
39
70
|
*/
|
|
40
|
-
type
|
|
71
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
41
72
|
/**
|
|
42
|
-
*
|
|
73
|
+
* Structure of a Tuyau registry containing routes and optional tree
|
|
43
74
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} : {}) & (E['types']['query'] extends object ? HasRequiredKeys<E['types']['query']> extends true ? {
|
|
49
|
-
query: E['types']['query'];
|
|
50
|
-
} : {
|
|
51
|
-
query?: E['types']['query'];
|
|
52
|
-
} : {}) & (E['types']['body'] extends never | undefined ? {} : HasRequiredKeys<E['types']['body']> extends true ? {
|
|
53
|
-
body: E['types']['body'];
|
|
54
|
-
} : {
|
|
55
|
-
body?: E['types']['body'];
|
|
56
|
-
}) & Omit<Options, 'body' | 'params' | 'searchParams' | 'method' | 'json' | 'prefixUrl'>;
|
|
75
|
+
interface TuyauRegistry<Routes extends Record<string, AdonisEndpoint> = Record<string, AdonisEndpoint>, Tree = unknown> {
|
|
76
|
+
routes: Routes;
|
|
77
|
+
$tree?: Tree;
|
|
78
|
+
}
|
|
57
79
|
/**
|
|
58
|
-
* Extracts
|
|
80
|
+
* Extracts the $tree type from a registry
|
|
81
|
+
* Uses direct access instead of conditional type for performance
|
|
59
82
|
*/
|
|
60
|
-
type
|
|
83
|
+
type InferTree<R extends TuyauRegistry> = R['$tree'];
|
|
61
84
|
/**
|
|
62
|
-
*
|
|
85
|
+
* Extracts the routes from a registry
|
|
63
86
|
*/
|
|
64
|
-
type
|
|
87
|
+
type InferRoutes<R extends TuyauRegistry> = R['routes'];
|
|
88
|
+
type Endpoints = ValueOf<AdonisRegistry>;
|
|
89
|
+
type EndpointByName<Name extends keyof AdonisRegistry & string> = AdonisRegistry[Name];
|
|
65
90
|
/**
|
|
66
|
-
*
|
|
91
|
+
* Pre-computed base Ky options to avoid recomputing Omit on every request
|
|
67
92
|
*/
|
|
68
|
-
type
|
|
69
|
-
type CamelCaseRest<S extends string> = S extends `_${infer H}${infer T}` ? `${Uppercase<H>}${CamelCaseRest<T>}` : S extends `${infer H}${infer T}` ? `${H}${CamelCaseRest<T>}` : S;
|
|
93
|
+
type BaseRequestOptions = Omit<Options, 'body' | 'params' | 'searchParams' | 'method' | 'json' | 'prefixUrl'>;
|
|
70
94
|
/**
|
|
71
|
-
*
|
|
95
|
+
* Helper types for optional/required fields - using literal types instead of mapped types
|
|
72
96
|
*/
|
|
73
|
-
type
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
type ParamsArg<T> = keyof T extends never ? {} : {} extends T ? {
|
|
98
|
+
params?: T;
|
|
99
|
+
} : {
|
|
100
|
+
params: T;
|
|
101
|
+
};
|
|
102
|
+
type QueryArg<T> = keyof T extends never ? {} : {} extends T ? {
|
|
103
|
+
query?: T;
|
|
104
|
+
} : {
|
|
105
|
+
query: T;
|
|
106
|
+
};
|
|
107
|
+
type BodyArg<T> = keyof T extends never ? {} : {} extends T ? {
|
|
108
|
+
body?: T;
|
|
109
|
+
} : {
|
|
110
|
+
body: T;
|
|
111
|
+
};
|
|
77
112
|
/**
|
|
78
|
-
*
|
|
113
|
+
* Request args without ky options
|
|
79
114
|
*/
|
|
80
|
-
type
|
|
115
|
+
type RawRequestArgs<E extends SchemaEndpoint> = ParamsArg<E['types']['params']> & QueryArg<E['types']['query']> & BodyArg<E['types']['body']>;
|
|
81
116
|
/**
|
|
82
|
-
*
|
|
117
|
+
* Constructs the request arguments type for an endpoint
|
|
83
118
|
*/
|
|
84
|
-
type
|
|
85
|
-
[K in keyof Reg & string]: SetAtPath<CamelCaseSplit<Split<K>>, EndpointFn<Reg[K]>>;
|
|
86
|
-
}[keyof Reg & string]>;
|
|
119
|
+
type RequestArgs<E extends SchemaEndpoint> = RawRequestArgs<E> & BaseRequestOptions;
|
|
87
120
|
/**
|
|
88
|
-
*
|
|
121
|
+
* Extracts response type from an endpoint
|
|
89
122
|
*/
|
|
90
|
-
type
|
|
91
|
-
infer H extends string,
|
|
92
|
-
...infer T extends string[]
|
|
93
|
-
] ? {
|
|
94
|
-
[K in H]: T['length'] extends 0 ? V : SetAtPath<T, V>;
|
|
95
|
-
} : {};
|
|
123
|
+
type ResponseOf<E extends SchemaEndpoint> = E['types']['response'];
|
|
96
124
|
/**
|
|
97
125
|
* Function type for calling an endpoint
|
|
98
126
|
*/
|
|
99
|
-
type EndpointFn<E extends
|
|
127
|
+
type EndpointFn<E extends SchemaEndpoint> = (args: RequestArgs<E>) => Promise<E['types']['response']>;
|
|
128
|
+
/**
|
|
129
|
+
* Transforms a pre-computed ApiDefinition tree into callable endpoint functions
|
|
130
|
+
* This recursively converts each endpoint in the tree to a callable function
|
|
131
|
+
* Fully inlined for maximum performance
|
|
132
|
+
*/
|
|
133
|
+
type TransformApiDefinition<T> = {
|
|
134
|
+
[K in keyof T]: T[K] extends AdonisEndpoint ? (args: ParamsArg<T[K]['types']['params']> & QueryArg<T[K]['types']['query']> & BodyArg<T[K]['types']['body']> & BaseRequestOptions) => Promise<T[K]['types']['response']> : TransformApiDefinition<T[K]>;
|
|
135
|
+
};
|
|
100
136
|
/**
|
|
101
137
|
* Filters endpoints by HTTP method
|
|
102
138
|
*/
|
|
@@ -130,7 +166,7 @@ interface QueryParameters extends Record<string, MaybeArray<string | number | bo
|
|
|
130
166
|
/**
|
|
131
167
|
* Configuration options for creating a Tuyau client
|
|
132
168
|
*/
|
|
133
|
-
interface TuyauConfiguration<T extends
|
|
169
|
+
interface TuyauConfiguration<T extends TuyauRegistry> extends Omit<Options, 'prefixUrl' | 'body' | 'json' | 'method' | 'searchParams'> {
|
|
134
170
|
registry: T;
|
|
135
171
|
baseUrl: string;
|
|
136
172
|
plugins?: TuyauPlugin[];
|
|
@@ -143,7 +179,7 @@ interface UserRegistry {
|
|
|
143
179
|
type UserAdonisRegistry = UserRegistry extends Record<string, AdonisEndpoint> ? UserRegistry : Record<string, AdonisEndpoint>;
|
|
144
180
|
type UserEndpointByName<Name extends keyof UserAdonisRegistry> = UserAdonisRegistry[Name];
|
|
145
181
|
type FilterByMethodPathForRegistry<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends ValueOf<Reg>['pattern'] & string> = {
|
|
146
|
-
[K in keyof Reg]:
|
|
182
|
+
[K in keyof Reg]: Reg[K]['pattern'] extends P ? M extends Reg[K]['methods'][number] ? Reg[K] : never : never;
|
|
147
183
|
}[keyof Reg];
|
|
148
184
|
type EndpointByNameForRegistry<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = Reg[Name];
|
|
149
185
|
/**
|
|
@@ -195,11 +231,11 @@ type ParamsShape<E> = E extends {
|
|
|
195
231
|
paramsTuple: infer PT;
|
|
196
232
|
params: infer P;
|
|
197
233
|
};
|
|
198
|
-
} ? (
|
|
234
|
+
} ? (PT extends [] ? {
|
|
199
235
|
paramsTuple?: PT;
|
|
200
236
|
} : {
|
|
201
237
|
paramsTuple: PT;
|
|
202
|
-
}) & (
|
|
238
|
+
}) & (keyof P extends never ? {} : {
|
|
203
239
|
params: P;
|
|
204
240
|
}) : never;
|
|
205
241
|
type RegistryGroupedByMethod<R extends Record<string, AdonisEndpoint>, M extends Method = Method> = {
|
|
@@ -208,4 +244,4 @@ type RegistryGroupedByMethod<R extends Record<string, AdonisEndpoint>, M extends
|
|
|
208
244
|
};
|
|
209
245
|
};
|
|
210
246
|
|
|
211
|
-
export { type AdonisEndpoint, type AdonisRegistry, type
|
|
247
|
+
export { type AdonisEndpoint, type AdonisRegistry, type EndpointByMethodPattern, type EndpointByName, type EndpointFn, type EndpointTypes, type Endpoints, type EndpointsByMethod, type ExtractBody, type ExtractQuery, type InferRoutes, type InferTree, type MaybeArray, type Method, Path, PathWithRegistry, type PatternsByMethod, type QueryParameters, type RawRequestArgs, type RegValues, type RegistryGroupedByMethod, type RequestArgs, type ResponseOf, Route, RouteWithRegistry, type SchemaEndpoint, type Split, type StrKeys, type TransformApiDefinition, type TuyauConfiguration, type TuyauPlugin, type TuyauRegistry, type UnionToIntersection, type UserApiDefinition, type UserRegistry, type ValueOf };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuyau/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.4",
|
|
5
5
|
"description": "e2e client for AdonisJS",
|
|
6
6
|
"author": "Julien Ripouteau <julien@ripouteau.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"@adonisjs/assembler": "8.0.0-next.19",
|
|
35
35
|
"@adonisjs/core": "^7.0.0-next.10",
|
|
36
36
|
"@adonisjs/http-server": "8.0.0-next.12",
|
|
37
|
-
"@arktype/attest": "^0.53.0",
|
|
38
37
|
"@faker-js/faker": "^10.1.0",
|
|
39
38
|
"@poppinss/ts-exec": "^1.4.1",
|
|
40
39
|
"@types/node": "^24.10.0",
|