@tuyau/core 1.0.0-beta.8 → 1.0.0-next.17
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 +9 -0
- package/build/backend/generate_registry.d.ts +185 -2
- package/build/backend/generate_registry.js +53 -27
- package/build/client/index.js +10 -3
- package/build/client/types/index.d.ts +33 -7
- package/package.json +32 -11
package/README.md
CHANGED
|
@@ -1,7 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extending the HTTP response interface to include status and response
|
|
3
|
+
* in the return type.
|
|
4
|
+
*
|
|
5
|
+
* This is ONLY type information and the properties will not be available
|
|
6
|
+
* at runtime. This is needed to infer the correct response type
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare module '@adonisjs/core/http' {
|
|
10
|
+
interface HttpResponse {
|
|
11
|
+
continue(): {
|
|
12
|
+
__status: 100;
|
|
13
|
+
};
|
|
14
|
+
switchingProtocols(etag?: boolean): {
|
|
15
|
+
__status: 101;
|
|
16
|
+
};
|
|
17
|
+
ok<T>(body: T, etag?: boolean): {
|
|
18
|
+
__response: T;
|
|
19
|
+
__status: 200;
|
|
20
|
+
};
|
|
21
|
+
created<T>(body?: T, etag?: boolean): {
|
|
22
|
+
__response: T;
|
|
23
|
+
__status: 201;
|
|
24
|
+
};
|
|
25
|
+
accepted<T>(body: T, etag?: boolean): {
|
|
26
|
+
__response: T;
|
|
27
|
+
__status: 202;
|
|
28
|
+
};
|
|
29
|
+
nonAuthoritativeInformation<T>(body?: T, etag?: boolean): {
|
|
30
|
+
__response: T;
|
|
31
|
+
__status: 203;
|
|
32
|
+
};
|
|
33
|
+
noContent<T>(body?: T, etag?: boolean): {
|
|
34
|
+
__response: T;
|
|
35
|
+
__status: 204;
|
|
36
|
+
};
|
|
37
|
+
resetContent<T>(body?: T, etag?: boolean): {
|
|
38
|
+
__response: T;
|
|
39
|
+
__status: 205;
|
|
40
|
+
};
|
|
41
|
+
partialContent<T>(body: T, etag?: boolean): {
|
|
42
|
+
__response: T;
|
|
43
|
+
__status: 206;
|
|
44
|
+
};
|
|
45
|
+
multipleChoices<T>(body?: T, etag?: boolean): {
|
|
46
|
+
__response: T;
|
|
47
|
+
__status: 300;
|
|
48
|
+
};
|
|
49
|
+
movedPermanently<T>(body?: T, etag?: boolean): {
|
|
50
|
+
__response: T;
|
|
51
|
+
__status: 301;
|
|
52
|
+
};
|
|
53
|
+
movedTemporarily<T>(body?: T, etag?: boolean): {
|
|
54
|
+
__response: T;
|
|
55
|
+
__status: 302;
|
|
56
|
+
};
|
|
57
|
+
seeOther<T>(body?: T, etag?: boolean): {
|
|
58
|
+
__response: T;
|
|
59
|
+
__status: 303;
|
|
60
|
+
};
|
|
61
|
+
notModified<T>(body?: T, etag?: boolean): {
|
|
62
|
+
__response: T;
|
|
63
|
+
__status: 304;
|
|
64
|
+
};
|
|
65
|
+
useProxy<T>(body?: T, etag?: boolean): {
|
|
66
|
+
__response: T;
|
|
67
|
+
__status: 305;
|
|
68
|
+
};
|
|
69
|
+
temporaryRedirect<T>(body?: T, etag?: boolean): {
|
|
70
|
+
__response: T;
|
|
71
|
+
__status: 307;
|
|
72
|
+
};
|
|
73
|
+
badRequest<T>(body?: T, etag?: boolean): {
|
|
74
|
+
__response: T;
|
|
75
|
+
__status: 400;
|
|
76
|
+
};
|
|
77
|
+
unauthorized<T>(body?: T, etag?: boolean): {
|
|
78
|
+
__response: T;
|
|
79
|
+
__status: 401;
|
|
80
|
+
};
|
|
81
|
+
paymentRequired<T>(body?: T, etag?: boolean): {
|
|
82
|
+
__response: T;
|
|
83
|
+
__status: 402;
|
|
84
|
+
};
|
|
85
|
+
forbidden<T>(body?: T, etag?: boolean): {
|
|
86
|
+
__response: T;
|
|
87
|
+
__status: 403;
|
|
88
|
+
};
|
|
89
|
+
notFound<T>(body?: T, etag?: boolean): {
|
|
90
|
+
__response: T;
|
|
91
|
+
__status: 404;
|
|
92
|
+
};
|
|
93
|
+
methodNotAllowed<T>(body?: T, etag?: boolean): {
|
|
94
|
+
__response: T;
|
|
95
|
+
__status: 405;
|
|
96
|
+
};
|
|
97
|
+
notAcceptable<T>(body?: T, etag?: boolean): {
|
|
98
|
+
__response: T;
|
|
99
|
+
__status: 406;
|
|
100
|
+
};
|
|
101
|
+
proxyAuthenticationRequired<T>(body?: T, etag?: boolean): {
|
|
102
|
+
__response: T;
|
|
103
|
+
__status: 407;
|
|
104
|
+
};
|
|
105
|
+
requestTimeout<T>(body?: T, etag?: boolean): {
|
|
106
|
+
__response: T;
|
|
107
|
+
__status: 408;
|
|
108
|
+
};
|
|
109
|
+
conflict<T>(body?: T, etag?: boolean): {
|
|
110
|
+
__response: T;
|
|
111
|
+
__status: 409;
|
|
112
|
+
};
|
|
113
|
+
gone<T>(body?: T, etag?: boolean): {
|
|
114
|
+
__response: T;
|
|
115
|
+
__status: 410;
|
|
116
|
+
};
|
|
117
|
+
lengthRequired<T>(body?: T, etag?: boolean): {
|
|
118
|
+
__response: T;
|
|
119
|
+
__status: 411;
|
|
120
|
+
};
|
|
121
|
+
preconditionFailed<T>(body?: T, etag?: boolean): {
|
|
122
|
+
__response: T;
|
|
123
|
+
__status: 412;
|
|
124
|
+
};
|
|
125
|
+
requestEntityTooLarge<T>(body?: T, etag?: boolean): {
|
|
126
|
+
__response: T;
|
|
127
|
+
__status: 413;
|
|
128
|
+
};
|
|
129
|
+
requestUriTooLong<T>(body?: T, etag?: boolean): {
|
|
130
|
+
__response: T;
|
|
131
|
+
__status: 414;
|
|
132
|
+
};
|
|
133
|
+
unsupportedMediaType<T>(body?: T, etag?: boolean): {
|
|
134
|
+
__response: T;
|
|
135
|
+
__status: 415;
|
|
136
|
+
};
|
|
137
|
+
requestedRangeNotSatisfiable<T>(body?: T, etag?: boolean): {
|
|
138
|
+
__response: T;
|
|
139
|
+
__status: 416;
|
|
140
|
+
};
|
|
141
|
+
expectationFailed<T>(body?: T, etag?: boolean): {
|
|
142
|
+
__response: T;
|
|
143
|
+
__status: 417;
|
|
144
|
+
};
|
|
145
|
+
unprocessableEntity<T>(body?: T, etag?: boolean): {
|
|
146
|
+
__response: T;
|
|
147
|
+
__status: 422;
|
|
148
|
+
};
|
|
149
|
+
tooManyRequests<T>(body?: T, etag?: boolean): {
|
|
150
|
+
__response: T;
|
|
151
|
+
__status: 429;
|
|
152
|
+
};
|
|
153
|
+
internalServerError<T>(body?: T, etag?: boolean): {
|
|
154
|
+
__response: T;
|
|
155
|
+
__status: 500;
|
|
156
|
+
};
|
|
157
|
+
notImplemented<T>(body?: T, etag?: boolean): {
|
|
158
|
+
__response: T;
|
|
159
|
+
__status: 501;
|
|
160
|
+
};
|
|
161
|
+
badGateway<T>(body?: T, etag?: boolean): {
|
|
162
|
+
__response: T;
|
|
163
|
+
__status: 502;
|
|
164
|
+
};
|
|
165
|
+
serviceUnavailable<T>(body?: T, etag?: boolean): {
|
|
166
|
+
__response: T;
|
|
167
|
+
__status: 503;
|
|
168
|
+
};
|
|
169
|
+
gatewayTimeout<T>(body?: T, etag?: boolean): {
|
|
170
|
+
__response: T;
|
|
171
|
+
__status: 504;
|
|
172
|
+
};
|
|
173
|
+
httpVersionNotSupported<T>(body?: T, etag?: boolean): {
|
|
174
|
+
__response: T;
|
|
175
|
+
__status: 505;
|
|
176
|
+
};
|
|
177
|
+
json<T>(body: T, generateEtag?: boolean): {
|
|
178
|
+
__response: T;
|
|
179
|
+
__status: 200;
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
1
184
|
interface GenerateRegistryConfig {
|
|
2
185
|
/**
|
|
3
|
-
* Path to write the generated registry
|
|
4
|
-
* @default ./.adonisjs/client/registry
|
|
186
|
+
* Path to write the generated registry directory
|
|
187
|
+
* @default ./.adonisjs/client/registry
|
|
5
188
|
*/
|
|
6
189
|
output?: string;
|
|
7
190
|
/**
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
// src/backend/types.ts
|
|
2
|
+
import "@adonisjs/core/http";
|
|
3
|
+
|
|
1
4
|
// src/backend/generate_registry.ts
|
|
2
5
|
import { dirname } from "path";
|
|
3
6
|
import { writeFile, mkdir } from "fs/promises";
|
|
7
|
+
import stringHelpers from "@adonisjs/core/helpers/string";
|
|
4
8
|
async function writeOutputFile(filePath, content) {
|
|
5
9
|
const dir = dirname(filePath);
|
|
6
10
|
await mkdir(dir, { recursive: true });
|
|
@@ -29,26 +33,34 @@ function filterRoute(route, filters) {
|
|
|
29
33
|
}
|
|
30
34
|
function generateRouteParams(route) {
|
|
31
35
|
const dynamicParams = route.tokens.filter((token) => token.type === 1);
|
|
32
|
-
const paramsType = dynamicParams.map((token) => `${token.val}:
|
|
33
|
-
const paramsTuple = dynamicParams.map(() => "
|
|
36
|
+
const paramsType = dynamicParams.map((token) => `${token.val}: ParamValue`).join("; ");
|
|
37
|
+
const paramsTuple = dynamicParams.map(() => "ParamValue").join(", ");
|
|
34
38
|
return { paramsType, paramsTuple };
|
|
35
39
|
}
|
|
36
|
-
function toCamelCase(str) {
|
|
37
|
-
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
38
|
-
}
|
|
39
40
|
function buildTreeStructure(routes) {
|
|
40
41
|
const tree = /* @__PURE__ */ new Map();
|
|
41
42
|
for (const route of routes) {
|
|
42
43
|
const segments = route.name.split(".");
|
|
43
44
|
let current = tree;
|
|
44
45
|
for (let i = 0; i < segments.length; i++) {
|
|
45
|
-
const segment =
|
|
46
|
+
const segment = stringHelpers.camelCase(segments[i]);
|
|
46
47
|
const isLast = i === segments.length - 1;
|
|
47
48
|
if (isLast) {
|
|
48
|
-
current.
|
|
49
|
+
if (current.has(segment) && current.get(segment) instanceof Map) {
|
|
50
|
+
current.get(segment).set("$self", { routeName: route.name, route });
|
|
51
|
+
} else {
|
|
52
|
+
current.set(segment, { routeName: route.name, route });
|
|
53
|
+
}
|
|
49
54
|
} else {
|
|
50
55
|
if (!current.has(segment)) {
|
|
51
56
|
current.set(segment, /* @__PURE__ */ new Map());
|
|
57
|
+
} else {
|
|
58
|
+
const existing = current.get(segment);
|
|
59
|
+
if (!(existing instanceof Map)) {
|
|
60
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
61
|
+
newMap.set("$self", existing);
|
|
62
|
+
current.set(segment, newMap);
|
|
63
|
+
}
|
|
52
64
|
}
|
|
53
65
|
current = current.get(segment);
|
|
54
66
|
}
|
|
@@ -60,10 +72,18 @@ function generateTreeInterface(tree, indent = 2) {
|
|
|
60
72
|
const spaces = " ".repeat(indent);
|
|
61
73
|
const lines = [];
|
|
62
74
|
for (const [key, value] of tree) {
|
|
75
|
+
if (key === "$self") continue;
|
|
63
76
|
if (value instanceof Map) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
const selfRoute = value.get("$self");
|
|
78
|
+
if (selfRoute) {
|
|
79
|
+
lines.push(`${spaces}${key}: typeof routes['${selfRoute.routeName}'] & {`);
|
|
80
|
+
lines.push(generateTreeInterface(value, indent + 2));
|
|
81
|
+
lines.push(`${spaces}}`);
|
|
82
|
+
} else {
|
|
83
|
+
lines.push(`${spaces}${key}: {`);
|
|
84
|
+
lines.push(generateTreeInterface(value, indent + 2));
|
|
85
|
+
lines.push(`${spaces}}`);
|
|
86
|
+
}
|
|
67
87
|
} else {
|
|
68
88
|
lines.push(`${spaces}${key}: typeof routes['${value.routeName}']`);
|
|
69
89
|
}
|
|
@@ -73,21 +93,25 @@ function generateTreeInterface(tree, indent = 2) {
|
|
|
73
93
|
function normalizeImportPaths(typeString) {
|
|
74
94
|
return typeString.replace(/import\('app\//g, "import('#app/").replace(/\.ts'\)/g, "')");
|
|
75
95
|
}
|
|
96
|
+
function sanitizeTokens(tokens) {
|
|
97
|
+
return tokens.map(({ old, type, val, end }) => ({ old, type, val, end }));
|
|
98
|
+
}
|
|
76
99
|
function generateRuntimeRegistryEntry(route) {
|
|
77
100
|
const routeName = route.name;
|
|
101
|
+
const sanitizedTokens = sanitizeTokens(route.tokens);
|
|
78
102
|
return ` '${routeName}': {
|
|
79
103
|
methods: ${JSON.stringify(route.methods)},
|
|
80
104
|
pattern: '${route.pattern}',
|
|
81
|
-
tokens: ${JSON.stringify(
|
|
105
|
+
tokens: ${JSON.stringify(sanitizedTokens)},
|
|
82
106
|
types: placeholder as Registry['${routeName}']['types'],
|
|
83
107
|
}`;
|
|
84
108
|
}
|
|
85
109
|
function wrapResponseType(responseType) {
|
|
86
110
|
if (responseType === "unknown" || responseType === "{}") return responseType;
|
|
87
111
|
if (responseType.startsWith("ReturnType<")) {
|
|
88
|
-
return `Awaited<${responseType}
|
|
112
|
+
return `ExtractResponse<Awaited<${responseType}>>`;
|
|
89
113
|
}
|
|
90
|
-
return responseType
|
|
114
|
+
return `ExtractResponse<${responseType}>`;
|
|
91
115
|
}
|
|
92
116
|
function determineBodyAndQueryTypes(options) {
|
|
93
117
|
const { methods, requestType } = options;
|
|
@@ -98,7 +122,7 @@ function determineBodyAndQueryTypes(options) {
|
|
|
98
122
|
return { bodyType: "{}", queryType: "{}" };
|
|
99
123
|
}
|
|
100
124
|
if (isGetLike) {
|
|
101
|
-
return { bodyType: "{}", queryType: requestType };
|
|
125
|
+
return { bodyType: "{}", queryType: `ExtractQueryForGet<${requestType}>` };
|
|
102
126
|
}
|
|
103
127
|
return {
|
|
104
128
|
bodyType: `ExtractBody<${requestType}>`,
|
|
@@ -130,8 +154,8 @@ function generateRuntimeContent(routes) {
|
|
|
130
154
|
const registryEntries = routes.map(generateRuntimeRegistryEntry).join(",\n");
|
|
131
155
|
return `/* eslint-disable prettier/prettier */
|
|
132
156
|
import type { AdonisEndpoint } from '@tuyau/core/types'
|
|
133
|
-
import type { Registry } from './
|
|
134
|
-
import type { ApiDefinition } from './
|
|
157
|
+
import type { Registry } from './schema.d.ts'
|
|
158
|
+
import type { ApiDefinition } from './tree.d.ts'
|
|
135
159
|
|
|
136
160
|
const placeholder: any = {}
|
|
137
161
|
|
|
@@ -158,7 +182,7 @@ function generateTreeContent(routes) {
|
|
|
158
182
|
const tree = buildTreeStructure(routes);
|
|
159
183
|
const treeInterface = generateTreeInterface(tree);
|
|
160
184
|
return `/* eslint-disable prettier/prettier */
|
|
161
|
-
import type { routes } from './
|
|
185
|
+
import type { routes } from './index.ts'
|
|
162
186
|
|
|
163
187
|
export interface ApiDefinition {
|
|
164
188
|
${treeInterface}
|
|
@@ -168,11 +192,13 @@ ${treeInterface}
|
|
|
168
192
|
function generateTypesContent(routes) {
|
|
169
193
|
const registryEntries = routes.map(generateTypesRegistryEntry).join("\n");
|
|
170
194
|
return `/* eslint-disable prettier/prettier */
|
|
171
|
-
/// <reference path="
|
|
195
|
+
/// <reference path="../manifest.d.ts" />
|
|
172
196
|
|
|
173
|
-
import type { ExtractBody, ExtractQuery } from '@tuyau/core/types'
|
|
197
|
+
import type { ExtractBody, ExtractQuery, ExtractQueryForGet, ExtractResponse } from '@tuyau/core/types'
|
|
174
198
|
import type { InferInput } from '@vinejs/vine/types'
|
|
175
199
|
|
|
200
|
+
export type ParamValue = string | number | bigint | boolean
|
|
201
|
+
|
|
176
202
|
export interface Registry {
|
|
177
203
|
${registryEntries}
|
|
178
204
|
}
|
|
@@ -180,7 +206,7 @@ ${registryEntries}
|
|
|
180
206
|
}
|
|
181
207
|
function generateRegistry(options) {
|
|
182
208
|
const config = {
|
|
183
|
-
output: "./.adonisjs/client/registry
|
|
209
|
+
output: "./.adonisjs/client/registry",
|
|
184
210
|
...options
|
|
185
211
|
};
|
|
186
212
|
return {
|
|
@@ -196,18 +222,18 @@ function generateRegistry(options) {
|
|
|
196
222
|
const runtimeContent = generateRuntimeContent(scannedRoutes);
|
|
197
223
|
const typesContent = generateTypesContent(scannedRoutes);
|
|
198
224
|
const treeContent = generateTreeContent(scannedRoutes);
|
|
199
|
-
const
|
|
200
|
-
const runtimePath = `${
|
|
201
|
-
const typesPath = `${
|
|
202
|
-
const treePath = `${
|
|
225
|
+
const registryDir = config.output.replace(/\/$/, "");
|
|
226
|
+
const runtimePath = `${registryDir}/index.ts`;
|
|
227
|
+
const typesPath = `${registryDir}/schema.d.ts`;
|
|
228
|
+
const treePath = `${registryDir}/tree.d.ts`;
|
|
203
229
|
await Promise.all([
|
|
204
230
|
writeOutputFile(runtimePath, runtimeContent),
|
|
205
231
|
writeOutputFile(typesPath, typesContent),
|
|
206
232
|
writeOutputFile(treePath, treeContent)
|
|
207
233
|
]);
|
|
208
|
-
devServer.ui.logger.info(`created ${
|
|
209
|
-
|
|
210
|
-
|
|
234
|
+
devServer.ui.logger.info(`tuyau: created api client registry (${registryDir})`, {
|
|
235
|
+
startTime
|
|
236
|
+
});
|
|
211
237
|
});
|
|
212
238
|
}
|
|
213
239
|
};
|
package/build/client/index.js
CHANGED
|
@@ -26,7 +26,7 @@ function buildSearchParams(query) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
serialize2(query);
|
|
29
|
-
return parts.length ?
|
|
29
|
+
return parts.length ? parts.join("&") : "";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// src/client/errors.ts
|
|
@@ -78,6 +78,9 @@ function isObject(value) {
|
|
|
78
78
|
var isServer = typeof FileList === "undefined";
|
|
79
79
|
var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
|
|
80
80
|
function segmentsToRouteName(segments) {
|
|
81
|
+
return segments.map((segment) => segment.replace(/[A-Z]/g, (g) => `_${g.toLowerCase()}`)).join(".");
|
|
82
|
+
}
|
|
83
|
+
function segmentsToKebabRouteName(segments) {
|
|
81
84
|
return segments.map((segment) => segment.replace(/[A-Z]/g, (g) => `-${g.toLowerCase()}`)).join(".");
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -284,8 +287,12 @@ var Tuyau = class {
|
|
|
284
287
|
* Creates a proxy-based fluent API for accessing endpoints by name
|
|
285
288
|
*/
|
|
286
289
|
#makeNamed(segments) {
|
|
287
|
-
|
|
288
|
-
|
|
290
|
+
let routeName = segmentsToRouteName(segments);
|
|
291
|
+
let def = this.#config.registry.routes[routeName];
|
|
292
|
+
if (!def) {
|
|
293
|
+
routeName = segmentsToKebabRouteName(segments);
|
|
294
|
+
def = this.#config.registry.routes[routeName];
|
|
295
|
+
}
|
|
289
296
|
if (def) {
|
|
290
297
|
const fn = (args) => this.#doFetch(routeName, def.methods[0], args);
|
|
291
298
|
return new Proxy(fn, {
|
|
@@ -34,16 +34,38 @@ interface AdonisEndpoint extends SchemaEndpoint {
|
|
|
34
34
|
/**
|
|
35
35
|
* Extract query params from a validator type if it has a 'query' property.
|
|
36
36
|
* Used in generated registry to separate query params from body for POST/PUT/PATCH/DELETE.
|
|
37
|
-
* For GET/HEAD,
|
|
37
|
+
* For GET/HEAD, use ExtractQueryForGet instead.
|
|
38
38
|
*/
|
|
39
39
|
type ExtractQuery<T> = T extends {
|
|
40
40
|
query?: infer Q;
|
|
41
41
|
} ? Q : {};
|
|
42
42
|
/**
|
|
43
|
-
* Extract
|
|
44
|
-
*
|
|
43
|
+
* Extract query params for GET/HEAD requests.
|
|
44
|
+
* Excludes headers, cookies, and params from the validator type since these are not query params.
|
|
45
45
|
*/
|
|
46
|
-
type
|
|
46
|
+
type ExtractQueryForGet<T> = Omit<T, 'headers' | 'cookies' | 'params'>;
|
|
47
|
+
/**
|
|
48
|
+
* Extract body from a validator type, excluding reserved properties.
|
|
49
|
+
* Excludes 'query', 'params', 'headers', and 'cookies' as these are handled separately by AdonisJS.
|
|
50
|
+
*/
|
|
51
|
+
type ExtractBody<T> = Omit<T, 'query' | 'params' | 'headers' | 'cookies'>;
|
|
52
|
+
/**
|
|
53
|
+
* Success status codes (2xx)
|
|
54
|
+
*/
|
|
55
|
+
type SuccessStatus = 200 | 201 | 202 | 203 | 204 | 205 | 206;
|
|
56
|
+
/**
|
|
57
|
+
* Extract the actual response type from a controller return type.
|
|
58
|
+
* - Success responses (2xx): extracts `__response`
|
|
59
|
+
* - Error responses (non-2xx with __response/__status): returns `never` to filter from unions
|
|
60
|
+
* - Plain types (no __status): returns as-is
|
|
61
|
+
*/
|
|
62
|
+
type ExtractResponse<T> = T extends {
|
|
63
|
+
__response: infer R;
|
|
64
|
+
__status: SuccessStatus;
|
|
65
|
+
} ? R : T extends {
|
|
66
|
+
__response: unknown;
|
|
67
|
+
__status: number;
|
|
68
|
+
} ? never : T;
|
|
47
69
|
/**
|
|
48
70
|
* Registry mapping endpoint names to their definitions
|
|
49
71
|
*/
|
|
@@ -114,13 +136,17 @@ type ResponseOf<E extends SchemaEndpoint> = E['types']['response'];
|
|
|
114
136
|
* Function type for calling an endpoint
|
|
115
137
|
*/
|
|
116
138
|
type EndpointFn<E extends SchemaEndpoint> = (args: RequestArgs<E>) => Promise<E['types']['response']>;
|
|
139
|
+
/**
|
|
140
|
+
* Function type for an endpoint with inlined args
|
|
141
|
+
*/
|
|
142
|
+
type EndpointFnInline<E extends AdonisEndpoint> = (args: ParamsArg<E['types']['params']> & QueryArg<E['types']['query']> & BodyArg<E['types']['body']> & BaseRequestOptions) => Promise<E['types']['response']>;
|
|
117
143
|
/**
|
|
118
144
|
* Transforms a pre-computed ApiDefinition tree into callable endpoint functions
|
|
119
145
|
* This recursively converts each endpoint in the tree to a callable function
|
|
120
|
-
*
|
|
146
|
+
* Handles intersection types where a node is both an endpoint AND has children
|
|
121
147
|
*/
|
|
122
148
|
type TransformApiDefinition<T> = {
|
|
123
|
-
[K in keyof T]: T[K] extends AdonisEndpoint ?
|
|
149
|
+
[K in keyof T]: T[K] extends AdonisEndpoint ? EndpointFnInline<T[K]> & TransformApiDefinition<Omit<T[K], keyof AdonisEndpoint>> : TransformApiDefinition<T[K]>;
|
|
124
150
|
};
|
|
125
151
|
/**
|
|
126
152
|
* Filters endpoints by HTTP method
|
|
@@ -236,4 +262,4 @@ type RegistryGroupedByMethod<R extends Record<string, AdonisEndpoint>, M extends
|
|
|
236
262
|
};
|
|
237
263
|
};
|
|
238
264
|
|
|
239
|
-
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 UserRegistry, type ValueOf };
|
|
265
|
+
export { type AdonisEndpoint, type AdonisRegistry, type BaseRequestOptions, type EndpointByMethodPattern, type EndpointByName, type EndpointFn, type EndpointTypes, type Endpoints, type EndpointsByMethod, type ExtractBody, type ExtractQuery, type ExtractQueryForGet, type ExtractResponse, 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 UserRegistry, type ValueOf };
|
package/package.json
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuyau/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.0.0-next.17",
|
|
5
|
+
"description": "E2E typesafe client for AdonisJS",
|
|
6
6
|
"author": "Julien Ripouteau <julien@ripouteau.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/Julien-R44/tuyau#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Julien-R44/tuyau.git",
|
|
12
|
+
"directory": "packages/core"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Julien-R44/tuyau/issues"
|
|
16
|
+
},
|
|
8
17
|
"keywords": [
|
|
9
18
|
"adonisjs",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
19
|
+
"typescript",
|
|
20
|
+
"typesafe",
|
|
21
|
+
"api",
|
|
22
|
+
"rpc",
|
|
23
|
+
"client",
|
|
24
|
+
"e2e"
|
|
12
25
|
],
|
|
13
26
|
"exports": {
|
|
14
27
|
"./client": "./build/client/index.js",
|
|
@@ -23,22 +36,30 @@
|
|
|
23
36
|
"node": ">=24.0.0"
|
|
24
37
|
},
|
|
25
38
|
"peerDependencies": {
|
|
26
|
-
"@adonisjs/assembler": "^8.0.0-next.
|
|
27
|
-
"@adonisjs/core": "^7.0.0-next.
|
|
39
|
+
"@adonisjs/assembler": "^8.0.0-next.27",
|
|
40
|
+
"@adonisjs/core": "^7.0.0-next.16"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@adonisjs/assembler": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"@adonisjs/core": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
28
49
|
},
|
|
29
50
|
"dependencies": {
|
|
30
|
-
"ky": "^1.14.
|
|
51
|
+
"ky": "^1.14.2",
|
|
31
52
|
"object-to-formdata": "^4.5.1"
|
|
32
53
|
},
|
|
33
54
|
"devDependencies": {
|
|
34
|
-
"@adonisjs/assembler": "^8.0.0-next.
|
|
35
|
-
"@adonisjs/core": "^7.0.0-next.
|
|
55
|
+
"@adonisjs/assembler": "^8.0.0-next.27",
|
|
56
|
+
"@adonisjs/core": "^7.0.0-next.16",
|
|
36
57
|
"@adonisjs/http-server": "^8.0.0-next.16",
|
|
37
58
|
"@faker-js/faker": "^10.1.0",
|
|
38
59
|
"@poppinss/ts-exec": "^1.4.1",
|
|
39
|
-
"@types/node": "^
|
|
60
|
+
"@types/node": "^25.0.3",
|
|
40
61
|
"@typescript/analyze-trace": "^0.10.1",
|
|
41
|
-
"@vinejs/vine": "^4.
|
|
62
|
+
"@vinejs/vine": "^4.2.0"
|
|
42
63
|
},
|
|
43
64
|
"publishConfig": {
|
|
44
65
|
"access": "public",
|