@tuyau/core 0.4.2 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ interface GenerateRegistryConfig {
2
+ /**
3
+ * Path to write the generated registry file
4
+ * @default ./.adonisjs/client/registry.ts
5
+ */
6
+ output?: string;
7
+ /**
8
+ * Routes filtering configuration
9
+ */
10
+ routes?: {
11
+ /**
12
+ * Only include routes matching these patterns (route names)
13
+ * Can be strings, regex patterns, or functions
14
+ */
15
+ only?: Array<string | RegExp | ((routeName: string) => boolean)>;
16
+ /**
17
+ * Exclude routes matching these patterns (route names)
18
+ * Can be strings, regex patterns, or functions
19
+ */
20
+ except?: Array<string | RegExp | ((routeName: string) => boolean)>;
21
+ };
22
+ }
23
+ declare function generateRegistry(options?: GenerateRegistryConfig): {
24
+ run(devServer: any, routesScanner: any): Promise<void>;
25
+ };
26
+
27
+ export { generateRegistry };
@@ -0,0 +1,88 @@
1
+ // src/backend/generate_registry.ts
2
+ import { dirname } from "path";
3
+ import string from "@adonisjs/core/helpers/string";
4
+ import { writeFile, mkdir } from "fs/promises";
5
+ async function writeOutputFile(filePath, content) {
6
+ const dir = dirname(filePath);
7
+ await mkdir(dir, { recursive: true });
8
+ await writeFile(filePath, content);
9
+ }
10
+ function matchesPattern(routeName, pattern) {
11
+ if (typeof pattern === "string") return routeName.includes(pattern);
12
+ if (pattern instanceof RegExp) return pattern.test(routeName);
13
+ if (typeof pattern === "function") return pattern(routeName);
14
+ return false;
15
+ }
16
+ function filterRoutes(routes, filters) {
17
+ if (!filters) return routes;
18
+ const { only, except } = filters;
19
+ if (only && except)
20
+ throw new Error('Cannot use both "only" and "except" filters at the same time');
21
+ if (only) {
22
+ return routes.filter((route) => only.some((pattern) => matchesPattern(route.name, pattern)));
23
+ }
24
+ if (except) {
25
+ return routes.filter((route) => !except.some((pattern) => matchesPattern(route.name, pattern)));
26
+ }
27
+ return routes;
28
+ }
29
+ function generateRouteParams(route) {
30
+ const dynamicParams = route.tokens.filter((token) => token.type === 1);
31
+ const paramsType = dynamicParams.map((token) => `${token.val}: string`).join("; ");
32
+ const paramsTuple = dynamicParams.map(() => "string").join(", ");
33
+ return { paramsType, paramsTuple };
34
+ }
35
+ function generateRegistryEntry(route) {
36
+ const requestType = route.request?.type || "{}";
37
+ const responseType = route.response?.type || "unknown";
38
+ const { paramsType, paramsTuple } = generateRouteParams(route);
39
+ const routeName = route.name.split(".").map((segment) => string.camelCase(segment)).join(".");
40
+ return ` '${routeName}': {
41
+ methods: ${JSON.stringify(route.methods)},
42
+ pattern: '${route.pattern}',
43
+ tokens: ${JSON.stringify(route.tokens)},
44
+ types: placeholder as {
45
+ body: ${requestType}
46
+ paramsTuple: [${paramsTuple}]
47
+ params: ${paramsType ? `{ ${paramsType} }` : "{}"}
48
+ query: {}
49
+ response: ${responseType}
50
+ },
51
+ }`;
52
+ }
53
+ function generateRegistryContent(routes) {
54
+ const registryEntries = routes.map(generateRegistryEntry).join(",\n");
55
+ return `/* eslint-disable prettier/prettier */
56
+ import type { AdonisEndpoint } from '@tuyau/core/types'
57
+ import type { Infer } from '@vinejs/vine/types'
58
+
59
+ const placeholder: any = {}
60
+ export const registry = {
61
+ ${registryEntries}
62
+ } as const satisfies Record<string, AdonisEndpoint>
63
+
64
+ declare module '@tuyau/core/types' {
65
+ type Registry = typeof registry
66
+ export interface UserRegistry extends Registry {}
67
+ }
68
+ `;
69
+ }
70
+ function generateRegistry(options) {
71
+ const config = {
72
+ output: "./.adonisjs/client/registry.ts",
73
+ ...options
74
+ };
75
+ return {
76
+ async run(devServer, routesScanner) {
77
+ const startTime = process.hrtime();
78
+ const scannedRoutes = routesScanner.getScannedRoutes();
79
+ const filteredRoutes = filterRoutes(scannedRoutes, config.routes);
80
+ const registryContent = generateRegistryContent(filteredRoutes);
81
+ await writeOutputFile(config.output, registryContent);
82
+ devServer.ui.logger.info(`created ${config.output}`, { startTime });
83
+ }
84
+ };
85
+ }
86
+ export {
87
+ generateRegistry
88
+ };
@@ -0,0 +1,82 @@
1
+ import { UrlFor } from '@adonisjs/http-server/client/url_builder';
2
+ import { AdonisEndpoint, TuyauConfiguration, BuildNamed, RegistryGroupedByMethod, PatternsByMethod, RequestArgs, EndpointByMethodPattern, StrKeys, Method } from './types/index.js';
3
+ import { KyResponse, KyRequest, HTTPError } from 'ky';
4
+
5
+ /**
6
+ * Main client class for making HTTP requests to AdonisJS endpoints
7
+ * Provides both fluent API and direct method calling capabilities
8
+ */
9
+ declare class Tuyau<R extends Record<string, AdonisEndpoint>> {
10
+ #private;
11
+ private config;
12
+ readonly api: BuildNamed<R>;
13
+ readonly urlFor: UrlFor<RegistryGroupedByMethod<R>>;
14
+ /**
15
+ * Initializes the Tuyau client with provided configuration
16
+ */
17
+ constructor(config: TuyauConfiguration<R>);
18
+ /**
19
+ * Makes a GET request to the specified pattern
20
+ */
21
+ get<P extends PatternsByMethod<R, 'GET'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<R, 'GET', P>>): Promise<EndpointByMethodPattern<R, 'GET', P>['types']['response']>;
22
+ /**
23
+ * Makes a POST request to the specified pattern
24
+ */
25
+ post<P extends PatternsByMethod<R, 'POST'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<R, 'POST', P>>): Promise<EndpointByMethodPattern<R, 'POST', P>['types']['response']>;
26
+ /**
27
+ * Makes a PUT request to the specified pattern
28
+ */
29
+ put<P extends PatternsByMethod<R, 'PUT'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<R, 'PUT', P>>): Promise<EndpointByMethodPattern<R, 'PUT', P>['types']['response']>;
30
+ /**
31
+ * Makes a PATCH request to the specified pattern
32
+ */
33
+ patch<P extends PatternsByMethod<R, 'PATCH'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<R, 'PATCH', P>>): Promise<EndpointByMethodPattern<R, 'PATCH', P>['types']['response']>;
34
+ /**
35
+ * Makes a DELETE request to the specified pattern
36
+ */
37
+ delete<P extends PatternsByMethod<R, 'DELETE'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<R, 'DELETE', P>>): Promise<EndpointByMethodPattern<R, 'DELETE', P>['types']['response']>;
38
+ /**
39
+ * Makes a HEAD request to the specified pattern
40
+ */
41
+ head<P extends PatternsByMethod<R, 'HEAD'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<R, 'HEAD', P>>): Promise<EndpointByMethodPattern<R, 'HEAD', P>['types']['response']>;
42
+ /**
43
+ * Makes a request to a named endpoint
44
+ */
45
+ request<Name extends StrKeys<R>>(name: Name, args: RequestArgs<R[Name]>): Promise<R[Name]['types']['response']>;
46
+ /**
47
+ * Gets route information by name including URL and HTTP method
48
+ */
49
+ getRoute<Name extends StrKeys<R>>(name: Name, args: RequestArgs<R[Name]>): {
50
+ url: string;
51
+ methods: Method[];
52
+ };
53
+ /**
54
+ * Creates a proxy-based fluent API for accessing endpoints by name
55
+ */
56
+ private makeNamed;
57
+ }
58
+ /**
59
+ * Factory function to create a new Tuyau client instance
60
+ */
61
+ declare function createTuyau<R extends Record<string, AdonisEndpoint>>(config: TuyauConfiguration<R>): Tuyau<R>;
62
+
63
+ declare function parseResponse(response?: KyResponse): Promise<unknown>;
64
+ declare class TuyauHTTPError extends Error {
65
+ status: number | undefined;
66
+ rawResponse: KyResponse | undefined;
67
+ rawRequest: KyRequest | undefined;
68
+ response: any;
69
+ constructor(kyError: HTTPError, response: any);
70
+ }
71
+ /**
72
+ * Network error that occurs when the server is unreachable or the client is offline
73
+ */
74
+ declare class TuyauNetworkError extends Error {
75
+ cause: Error;
76
+ constructor(cause: Error, request?: {
77
+ url: string;
78
+ method: string;
79
+ });
80
+ }
81
+
82
+ export { Tuyau, TuyauHTTPError, TuyauNetworkError, createTuyau, parseResponse };
@@ -0,0 +1,291 @@
1
+ // src/client/tuyau.ts
2
+ import ky, { HTTPError } from "ky";
3
+ import { serialize } from "object-to-formdata";
4
+ import { createUrlBuilder } from "@adonisjs/http-server/client/url_builder";
5
+
6
+ // src/client/errors.ts
7
+ async function parseResponse(response) {
8
+ if (!response) return;
9
+ const responseType = response.headers.get("Content-Type")?.split(";")[0];
10
+ if (responseType === "application/json") {
11
+ return await response.json();
12
+ } else if (responseType === "application/octet-stream") {
13
+ return await response.arrayBuffer();
14
+ }
15
+ return await response.text();
16
+ }
17
+ var TuyauHTTPError = class extends Error {
18
+ status;
19
+ rawResponse;
20
+ rawRequest;
21
+ response;
22
+ constructor(kyError, response) {
23
+ const status = kyError.response?.status;
24
+ const reason = status ? `status code ${status}` : "an unknown error";
25
+ const url = kyError.request?.url.replace(kyError.options.prefixUrl || "", "");
26
+ const message = kyError.response ? `Request failed with ${reason}: ${kyError.request?.method.toUpperCase()} /${url}` : `Request failed with ${reason}`;
27
+ super(message, { cause: kyError });
28
+ this.rawResponse = kyError.response;
29
+ this.response = response;
30
+ this.status = kyError.response?.status;
31
+ this.rawRequest = kyError.request;
32
+ this.name = "TuyauHTTPError";
33
+ }
34
+ };
35
+ var TuyauNetworkError = class extends Error {
36
+ constructor(cause, request) {
37
+ const message = request ? `Network error: ${request.method.toUpperCase()} ${request.url}` : "Network error occurred";
38
+ super(message, { cause });
39
+ this.cause = cause;
40
+ this.name = "TuyauNetworkError";
41
+ }
42
+ };
43
+
44
+ // src/client/utils.ts
45
+ function buildSearchParams(query) {
46
+ if (!query) return;
47
+ let stringified = "";
48
+ const append = (key, value, isArray = false) => {
49
+ if (value === void 0 || value === null) return;
50
+ const encodedKey = encodeURIComponent(key);
51
+ const encodedValue = encodeURIComponent(value);
52
+ const keyValuePair = `${encodedKey}${isArray ? "[]" : ""}=${encodedValue}`;
53
+ stringified += (stringified ? "&" : "?") + keyValuePair;
54
+ };
55
+ for (const [key, value] of Object.entries(query)) {
56
+ if (!value) continue;
57
+ if (Array.isArray(value)) {
58
+ for (const v of value) append(key, v, true);
59
+ } else {
60
+ append(key, `${value}`);
61
+ }
62
+ }
63
+ return stringified;
64
+ }
65
+ function removeSlash(value) {
66
+ return value.replace(/^\//, "");
67
+ }
68
+ function isObject(value) {
69
+ return typeof value === "object" && !Array.isArray(value) && value !== null;
70
+ }
71
+ var isServer = typeof FileList === "undefined";
72
+ var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
73
+
74
+ // src/client/tuyau.ts
75
+ var Tuyau = class {
76
+ /**
77
+ * Initializes the Tuyau client with provided configuration
78
+ */
79
+ constructor(config) {
80
+ this.config = config;
81
+ this.api = this.makeNamed([]);
82
+ this.#entries = Object.entries(this.config.registry);
83
+ this.urlFor = this.#createUrlBuilder();
84
+ this.#applyPlugins();
85
+ this.#client = ky.create(this.#mergeKyConfiguration());
86
+ }
87
+ api;
88
+ urlFor;
89
+ #entries;
90
+ #client;
91
+ /**
92
+ * Merges the default Ky configuration with user-provided config
93
+ */
94
+ #mergeKyConfiguration() {
95
+ return {
96
+ prefixUrl: this.config.baseUrl,
97
+ ...this.config,
98
+ hooks: {
99
+ ...this.config.hooks,
100
+ beforeRequest: [
101
+ ...this.config.hooks?.beforeRequest || [],
102
+ this.#appendCsrfToken.bind(this)
103
+ ]
104
+ }
105
+ };
106
+ }
107
+ /**
108
+ * Applies registered plugins to the client configuration
109
+ */
110
+ #applyPlugins() {
111
+ this.config.plugins?.forEach((plugin) => plugin({ options: this.config }));
112
+ }
113
+ /**
114
+ * Creates a URL builder instance for generating URLs based on the route registry
115
+ */
116
+ #createUrlBuilder() {
117
+ const rootEntries = this.#entries.map(([name, entry]) => ({ name, domain: "root", ...entry }));
118
+ return createUrlBuilder({ root: rootEntries }, buildSearchParams);
119
+ }
120
+ /**
121
+ * Automatically appends CSRF token from cookies to requests
122
+ */
123
+ #appendCsrfToken(request) {
124
+ const xCsrfToken = globalThis.document?.cookie.split("; ").find((row) => row.startsWith("XSRF-TOKEN="));
125
+ if (!xCsrfToken) return;
126
+ request.headers.set("X-XSRF-TOKEN", decodeURIComponent(xCsrfToken.split("=")[1]));
127
+ }
128
+ /**
129
+ * Checks if a value represents a file for upload
130
+ */
131
+ #isFile(v) {
132
+ if (isReactNative && isObject(v) && v.uri) return true;
133
+ if (isServer) return v instanceof Blob;
134
+ return v instanceof FileList || v instanceof File;
135
+ }
136
+ /**
137
+ * Checks if an object contains any file uploads
138
+ */
139
+ #hasFile(obj) {
140
+ if (!obj) return false;
141
+ return Object.values(obj).some((val) => {
142
+ if (Array.isArray(val)) return val.some(this.#isFile);
143
+ return this.#isFile(val);
144
+ });
145
+ }
146
+ #getLowercaseMethod(method) {
147
+ return method.toLowerCase();
148
+ }
149
+ #buildUrl(name, method, args) {
150
+ const lowercaseMethod = this.#getLowercaseMethod(method);
151
+ const usedMethod = lowercaseMethod === "head" || lowercaseMethod === "options" ? "get" : lowercaseMethod;
152
+ return this.urlFor[usedMethod](name, args?.params || {}).url;
153
+ }
154
+ /**
155
+ * Performs the actual HTTP request with proper body formatting
156
+ */
157
+ async #doFetch(name, method, args) {
158
+ const url = this.#buildUrl(name, method, args);
159
+ let key = "json";
160
+ let body = args.body;
161
+ if (!(body instanceof FormData) && this.#hasFile(body)) {
162
+ body = serialize(body, { indices: true });
163
+ key = "body";
164
+ } else if (body instanceof FormData) {
165
+ key = "body";
166
+ }
167
+ const isGetOrHead = ["GET", "HEAD"].includes(method);
168
+ const { body: _, ...restArgs } = args;
169
+ const requestOptions = {
170
+ searchParams: buildSearchParams(args?.query || {}),
171
+ [key]: !isGetOrHead ? body : void 0,
172
+ ...restArgs
173
+ };
174
+ try {
175
+ const res = await this.#client[this.#getLowercaseMethod(method)](
176
+ removeSlash(url),
177
+ requestOptions
178
+ );
179
+ let data;
180
+ const responseType = res.headers.get("Content-Type")?.split(";")[0];
181
+ if (responseType === "application/json") {
182
+ data = await res.json();
183
+ } else if (responseType === "application/octet-stream") {
184
+ data = await res.arrayBuffer();
185
+ } else {
186
+ data = await res.text();
187
+ }
188
+ return data;
189
+ } catch (originalError) {
190
+ if (originalError instanceof HTTPError) {
191
+ const parsedResponse = await parseResponse(originalError.response);
192
+ throw new TuyauHTTPError(originalError, parsedResponse);
193
+ }
194
+ throw new TuyauNetworkError(originalError, { url, method });
195
+ }
196
+ }
197
+ /**
198
+ * Finds an endpoint definition by HTTP method and pattern
199
+ */
200
+ #byMethodPath(method, pattern) {
201
+ const [name, endpoint] = this.#entries.find(
202
+ ([, e]) => e.methods[0] === method && e.pattern === pattern
203
+ ) || [null, null];
204
+ if (!name || !endpoint) throw new Error(`No ${method} ${pattern}`);
205
+ return { name, endpoint };
206
+ }
207
+ /**
208
+ * Makes a request to a specific endpoint using method and pattern
209
+ */
210
+ #request(method, pattern, args) {
211
+ const { name, endpoint } = this.#byMethodPath(method, pattern);
212
+ return this.#doFetch(name, endpoint.methods[0], args);
213
+ }
214
+ /**
215
+ * Makes a GET request to the specified pattern
216
+ */
217
+ get(pattern, args) {
218
+ return this.#request("GET", pattern, args);
219
+ }
220
+ /**
221
+ * Makes a POST request to the specified pattern
222
+ */
223
+ post(pattern, args) {
224
+ return this.#request("POST", pattern, args);
225
+ }
226
+ /**
227
+ * Makes a PUT request to the specified pattern
228
+ */
229
+ put(pattern, args) {
230
+ return this.#request("PUT", pattern, args);
231
+ }
232
+ /**
233
+ * Makes a PATCH request to the specified pattern
234
+ */
235
+ patch(pattern, args) {
236
+ return this.#request("PATCH", pattern, args);
237
+ }
238
+ /**
239
+ * Makes a DELETE request to the specified pattern
240
+ */
241
+ delete(pattern, args) {
242
+ return this.#request("DELETE", pattern, args);
243
+ }
244
+ /**
245
+ * Makes a HEAD request to the specified pattern
246
+ */
247
+ head(pattern, args) {
248
+ return this.#request("HEAD", pattern, args);
249
+ }
250
+ /**
251
+ * Makes a request to a named endpoint
252
+ */
253
+ request(name, args) {
254
+ const def = this.config.registry[name];
255
+ return this.#doFetch(name, def.methods[0], args);
256
+ }
257
+ /**
258
+ * Gets route information by name including URL and HTTP method
259
+ */
260
+ getRoute(name, args) {
261
+ const def = this.config.registry[name];
262
+ if (!def) throw new Error(`Route ${String(name)} not found`);
263
+ const url = this.#buildUrl(name, def.methods[0], args);
264
+ return { url, methods: def.methods };
265
+ }
266
+ /**
267
+ * Creates a proxy-based fluent API for accessing endpoints by name
268
+ */
269
+ makeNamed(segments) {
270
+ const dot = segments.join(".");
271
+ const def = this.config.registry[dot];
272
+ if (def) {
273
+ const fn = (args) => this.#doFetch(dot, def.methods[0], args);
274
+ return new Proxy(fn, {
275
+ get: (_t, prop) => this.makeNamed([...segments, String(prop)]),
276
+ apply: (_t, _this, argArray) => fn(...argArray)
277
+ });
278
+ }
279
+ return new Proxy({}, { get: (_t, prop) => this.makeNamed([...segments, String(prop)]) });
280
+ }
281
+ };
282
+ function createTuyau(config) {
283
+ return new Tuyau(config);
284
+ }
285
+ export {
286
+ Tuyau,
287
+ TuyauHTTPError,
288
+ TuyauNetworkError,
289
+ createTuyau,
290
+ parseResponse
291
+ };
@@ -0,0 +1,195 @@
1
+ import { Options } from 'ky';
2
+ import { ClientRouteMatchItTokens } from '@adonisjs/http-server/client/url_builder';
3
+
4
+ /**
5
+ * Supported HTTP methods for API endpoints
6
+ */
7
+ type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
8
+ /**
9
+ * Definition of an AdonisJS endpoint with types and metadata
10
+ */
11
+ interface AdonisEndpoint {
12
+ methods: Method[];
13
+ pattern: string;
14
+ 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
+ }
23
+ /**
24
+ * Registry mapping endpoint names to their definitions
25
+ */
26
+ type AdonisRegistry = Record<string, AdonisEndpoint>;
27
+ type ValueOf<T> = T[keyof T];
28
+ type IsEmptyObj<T> = keyof T extends never ? true : false;
29
+ type IsEmptyTuple<T> = T extends [] ? true : false;
30
+ type Endpoints = ValueOf<AdonisRegistry>;
31
+ type EndpointByName<Name extends keyof AdonisRegistry & string> = AdonisRegistry[Name];
32
+ /**
33
+ * Checks if a type has any keys
34
+ */
35
+ type HasKeys<T> = keyof T extends never ? false : true;
36
+ /**
37
+ * Checks if a type has required keys (non-optional properties)
38
+ */
39
+ type HasRequiredKeys<T> = T extends Record<string, any> ? keyof T extends never ? false : {} extends Pick<T, keyof T> ? false : true : false;
40
+ /**
41
+ * Constructs the request arguments type for an endpoint
42
+ */
43
+ type RequestArgs<E extends AdonisEndpoint> = (HasKeys<E['types']['params']> extends true ? HasRequiredKeys<E['types']['params']> extends true ? {
44
+ params: E['types']['params'];
45
+ } : {
46
+ params?: E['types']['params'];
47
+ } : {}) & (E['types']['query'] extends object ? HasRequiredKeys<E['types']['query']> extends true ? {
48
+ query: E['types']['query'];
49
+ } : {
50
+ query?: E['types']['query'];
51
+ } : {}) & (E['types']['body'] extends never | undefined ? {} : HasRequiredKeys<E['types']['body']> extends true ? {
52
+ body: E['types']['body'];
53
+ } : {
54
+ body?: E['types']['body'];
55
+ }) & Omit<Options, 'body' | 'params' | 'searchParams' | 'method' | 'json' | 'prefixUrl'>;
56
+ /**
57
+ * Extracts response type from an endpoint
58
+ */
59
+ type ResponseOf<E extends AdonisEndpoint> = E['types']['response'];
60
+ /**
61
+ * Splits a dot-separated string into an array of strings
62
+ */
63
+ type Split<S extends string> = S extends `${infer H}.${infer T}` ? [H, ...Split<T>] : [S];
64
+ /**
65
+ * Converts a union type to an intersection type
66
+ */
67
+ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
68
+ /**
69
+ * Builds a nested object type for the fluent API based on endpoint names
70
+ */
71
+ type BuildNamed<Reg extends Record<string, AdonisEndpoint>> = UnionToIntersection<{
72
+ [K in keyof Reg & string]: SetAtPath<Split<K>, EndpointFn<Reg[K]>>;
73
+ }[keyof Reg & string]>;
74
+ /**
75
+ * Sets a value at a specific path in a nested object type
76
+ */
77
+ type SetAtPath<Path extends string[], V> = Path extends [
78
+ infer H extends string,
79
+ ...infer T extends string[]
80
+ ] ? {
81
+ [K in H]: T['length'] extends 0 ? V : SetAtPath<T, V>;
82
+ } : {};
83
+ /**
84
+ * Function type for calling an endpoint
85
+ */
86
+ type EndpointFn<E extends AdonisEndpoint> = (args: RequestArgs<E>) => Promise<E['types']['response']>;
87
+ /**
88
+ * Filters endpoints by HTTP method
89
+ */
90
+ type EndpointsByMethod<Reg extends Record<string, AdonisEndpoint>, M extends Method> = {
91
+ [K in keyof Reg]: M extends Reg[K]['methods'][number] ? Reg[K] : never;
92
+ }[keyof Reg];
93
+ type StrKeys<R> = Extract<keyof R, string>;
94
+ type RegValues<R> = R[StrKeys<R>];
95
+ /**
96
+ * Gets URL patterns for endpoints matching a specific HTTP method
97
+ */
98
+ type PatternsByMethod<Reg extends Record<string, AdonisEndpoint>, M extends Method> = EndpointsByMethod<Reg, M>['pattern'];
99
+ /**
100
+ * Finds an endpoint by HTTP method and URL pattern
101
+ */
102
+ type EndpointByMethodPattern<R extends Record<string, AdonisEndpoint>, M extends Method, P extends PatternsByMethod<R, M>> = FilterByMethodPathForRegistry<R, M, P>;
103
+ /**
104
+ * Plugin function type for extending Tuyau functionality
105
+ */
106
+ type TuyauPlugin = (params: {
107
+ options: TuyauConfiguration<any>;
108
+ }) => void;
109
+ type MaybeArray<T> = T | T[];
110
+ /**
111
+ * Type for URL query parameters
112
+ */
113
+ type QueryParameters = Record<string, MaybeArray<string | number | boolean | null | undefined>>;
114
+ /**
115
+ * Configuration options for creating a Tuyau client
116
+ */
117
+ type TuyauConfiguration<T extends Record<string, AdonisEndpoint>> = Omit<Options, 'prefixUrl' | 'body' | 'json' | 'method' | 'searchParams'> & {
118
+ registry: T;
119
+ baseUrl: string;
120
+ plugins?: TuyauPlugin[];
121
+ };
122
+ /**
123
+ * Should be augmented by the user to provide their endpoint registry
124
+ */
125
+ interface UserRegistry {
126
+ }
127
+ type UserAdonisRegistry = UserRegistry extends Record<string, AdonisEndpoint> ? UserRegistry : Record<string, AdonisEndpoint>;
128
+ type UserEndpointByName<Name extends keyof UserAdonisRegistry> = UserAdonisRegistry[Name];
129
+ type FilterByMethodPathForRegistry<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends ValueOf<Reg>['pattern'] & string> = {
130
+ [K in keyof Reg]: [Reg[K]['pattern'], M] extends [P, Reg[K]['methods'][number]] ? Reg[K] : never;
131
+ }[keyof Reg];
132
+ type EndpointByNameForRegistry<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = Reg[Name];
133
+ /**
134
+ * Internal type utilities for working with endpoints by HTTP method and path pattern
135
+ * Accepts a registry as generic parameter
136
+ */
137
+ declare namespace PathWithRegistry {
138
+ type Request<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends PatternsByMethod<Reg, M>> = RequestArgs<FilterByMethodPathForRegistry<Reg, M, P>>;
139
+ type Response<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends PatternsByMethod<Reg, M>> = ResponseOf<FilterByMethodPathForRegistry<Reg, M, P>>;
140
+ type Params<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends PatternsByMethod<Reg, M>> = FilterByMethodPathForRegistry<Reg, M, P>['types']['params'];
141
+ type Body<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends PatternsByMethod<Reg, M>> = FilterByMethodPathForRegistry<Reg, M, P>['types']['body'];
142
+ type Query<Reg extends Record<string, AdonisEndpoint>, M extends Method, P extends PatternsByMethod<Reg, M>> = FilterByMethodPathForRegistry<Reg, M, P>['types']['query'];
143
+ }
144
+ /**
145
+ * Internal type utilities for working with endpoints by route name
146
+ * Accepts a registry as generic parameter
147
+ */
148
+ declare namespace RouteWithRegistry {
149
+ type Request<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = RequestArgs<EndpointByNameForRegistry<Reg, Name>>;
150
+ type Response<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = ResponseOf<EndpointByNameForRegistry<Reg, Name>>;
151
+ type Params<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = EndpointByNameForRegistry<Reg, Name>['types']['params'];
152
+ type Body<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = EndpointByNameForRegistry<Reg, Name>['types']['body'];
153
+ type Query<Reg extends Record<string, AdonisEndpoint>, Name extends keyof Reg> = EndpointByNameForRegistry<Reg, Name>['types']['query'];
154
+ }
155
+ /**
156
+ * Type utilities for working with endpoints by HTTP method and path pattern
157
+ * Uses the user-augmented registry
158
+ */
159
+ declare namespace Path {
160
+ type Request<M extends Method, P extends PatternsByMethod<UserAdonisRegistry, M>> = RequestArgs<FilterByMethodPathForRegistry<UserAdonisRegistry, M, P>>;
161
+ type Response<M extends Method, P extends PatternsByMethod<UserAdonisRegistry, M>> = ResponseOf<FilterByMethodPathForRegistry<UserAdonisRegistry, M, P>>;
162
+ type Params<M extends Method, P extends PatternsByMethod<UserAdonisRegistry, M>> = FilterByMethodPathForRegistry<UserAdonisRegistry, M, P>['types']['params'];
163
+ type Body<M extends Method, P extends PatternsByMethod<UserAdonisRegistry, M>> = FilterByMethodPathForRegistry<UserAdonisRegistry, M, P>['types']['body'];
164
+ type Query<M extends Method, P extends PatternsByMethod<UserAdonisRegistry, M>> = FilterByMethodPathForRegistry<UserAdonisRegistry, M, P>['types']['query'];
165
+ }
166
+ /**
167
+ * Type utilities for working with endpoints by route name
168
+ * Uses the user-augmented registry
169
+ */
170
+ declare namespace Route {
171
+ type Request<Name extends keyof UserAdonisRegistry> = RequestArgs<UserEndpointByName<Name>>;
172
+ type Response<Name extends keyof UserAdonisRegistry> = ResponseOf<UserEndpointByName<Name>>;
173
+ type Params<Name extends keyof UserAdonisRegistry> = UserEndpointByName<Name>['types']['params'];
174
+ type Body<Name extends keyof UserAdonisRegistry> = UserEndpointByName<Name>['types']['body'];
175
+ type Query<Name extends keyof UserAdonisRegistry> = UserEndpointByName<Name>['types']['query'];
176
+ }
177
+ type ParamsShape<E> = E extends {
178
+ types: {
179
+ paramsTuple: infer PT;
180
+ params: infer P;
181
+ };
182
+ } ? (IsEmptyTuple<PT> extends true ? {
183
+ paramsTuple?: PT;
184
+ } : {
185
+ paramsTuple: PT;
186
+ }) & (IsEmptyObj<P> extends true ? {} : {
187
+ params: P;
188
+ }) : never;
189
+ type RegistryGroupedByMethod<R extends Record<string, AdonisEndpoint>, M extends Method = Method> = {
190
+ [K in M | 'ALL']: {
191
+ [Route in keyof R as K extends 'ALL' ? Route : K extends R[Route]['methods'][number] ? Route : never]: ParamsShape<R[Route]>;
192
+ };
193
+ };
194
+
195
+ export { type AdonisEndpoint, type AdonisRegistry, type BuildNamed, type EndpointByMethodPattern, type EndpointByName, type Endpoints, type EndpointsByMethod, type HasKeys, type HasRequiredKeys, type MaybeArray, type Method, Path, PathWithRegistry, type PatternsByMethod, type QueryParameters, type RegValues, type RegistryGroupedByMethod, type RequestArgs, type ResponseOf, Route, RouteWithRegistry, type SetAtPath, type Split, type StrKeys, type TuyauConfiguration, type TuyauPlugin, type UnionToIntersection, type UserRegistry, type ValueOf };
@@ -1 +1 @@
1
- {"commands":[{"commandName":"tuyau:generate","description":"Tuyau generator command","help":"","namespace":"tuyau","aliases":[],"flags":[{"name":"verbose","flagName":"verbose","required":false,"type":"boolean","description":"Verbose logs","default":false,"alias":"v"}],"args":[],"options":{"startApp":true},"filePath":"generate.js"}],"version":1}
1
+ {"commands":[],"version":1}