@tuyau/core 1.0.0 → 1.2.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.
@@ -1,3 +1,5 @@
1
+ import { AllHooks } from '@adonisjs/assembler/types';
2
+
1
3
  /**
2
4
  * Extending the HTTP response interface to include status and response
3
5
  * in the return type.
@@ -180,7 +182,6 @@ declare module '@adonisjs/core/http' {
180
182
  };
181
183
  }
182
184
  }
183
-
184
185
  interface GenerateRegistryConfig {
185
186
  /**
186
187
  * Path to write the generated registry directory
@@ -202,9 +203,18 @@ interface GenerateRegistryConfig {
202
203
  */
203
204
  except?: Array<string | RegExp | ((routeName: string) => boolean)>;
204
205
  };
206
+ /**
207
+ * Custom TypeScript type string for 422 validation error responses.
208
+ * When not provided, uses the default VineJS SimpleErrorReporter format.
209
+ * Set to `false` to disable auto-adding 422 errors.
210
+ */
211
+ validationErrorType?: string | false;
205
212
  }
206
- declare function generateRegistry(options?: GenerateRegistryConfig): {
207
- run(devServer: any, routesScanner: any): Promise<void>;
208
- };
213
+
214
+ /**
215
+ * AdonisJS assembler hook that scans routes and generates
216
+ * the tuyau typed client registry files (runtime, schema, tree).
217
+ */
218
+ declare function generateRegistry(options?: GenerateRegistryConfig): AllHooks['init'][number];
209
219
 
210
220
  export { generateRegistry };
@@ -1,144 +1,198 @@
1
1
  // src/backend/types.ts
2
2
  import "@adonisjs/core/http";
3
3
 
4
- // src/backend/generate_registry.ts
4
+ // src/backend/registry_generator.ts
5
5
  import { dirname } from "path";
6
6
  import { writeFile, mkdir } from "fs/promises";
7
7
  import stringHelpers from "@adonisjs/core/helpers/string";
8
- async function writeOutputFile(filePath, content) {
9
- const dir = dirname(filePath);
10
- await mkdir(dir, { recursive: true });
11
- await writeFile(filePath, content);
12
- }
13
- function matchesPattern(routeName, pattern) {
14
- if (typeof pattern === "string") return routeName.includes(pattern);
15
- if (pattern instanceof RegExp) return pattern.test(routeName);
16
- if (typeof pattern === "function") return pattern(routeName);
17
- return false;
18
- }
19
- function filterRoute(route, filters) {
20
- if (!filters) {
8
+ var DEFAULT_VALIDATION_ERROR_TYPE = "{ errors: SimpleError[] }";
9
+ var RegistryGenerator = class {
10
+ #validationErrorType;
11
+ #routesFilter;
12
+ constructor(options) {
13
+ this.#validationErrorType = options?.validationErrorType === void 0 ? DEFAULT_VALIDATION_ERROR_TYPE : options.validationErrorType;
14
+ this.#routesFilter = options?.routes;
15
+ }
16
+ /**
17
+ * Test a route name against a string, regex, or function pattern.
18
+ */
19
+ #matchesPattern(routeName, pattern) {
20
+ if (typeof pattern === "string") return routeName.includes(pattern);
21
+ if (pattern instanceof RegExp) return pattern.test(routeName);
22
+ if (typeof pattern === "function") return pattern(routeName);
23
+ return false;
24
+ }
25
+ /**
26
+ * Check if a route passes the configured only/except filters.
27
+ * Returns true when no filters are set.
28
+ */
29
+ filterRoute(route) {
30
+ if (!this.#routesFilter) return true;
31
+ const { only, except } = this.#routesFilter;
32
+ if (only && except)
33
+ throw new Error('Cannot use both "only" and "except" filters at the same time');
34
+ if (only) return only.some((pattern) => this.#matchesPattern(route.name, pattern));
35
+ if (except) return !except.some((pattern) => this.#matchesPattern(route.name, pattern));
21
36
  return true;
22
37
  }
23
- const { only, except } = filters;
24
- if (only && except)
25
- throw new Error('Cannot use both "only" and "except" filters at the same time');
26
- if (only) {
27
- return only.some((pattern) => matchesPattern(route.name, pattern));
38
+ /**
39
+ * Convert `import('app/...')` paths to `import('#app/...')` subpath
40
+ * imports and strip `.ts` extensions.
41
+ */
42
+ #normalizeImportPaths(typeString) {
43
+ return typeString.replace(/import\('app\//g, "import('#app/").replace(/\.ts'\)/g, "')");
28
44
  }
29
- if (except) {
30
- return !except.some((pattern) => matchesPattern(route.name, pattern));
45
+ /**
46
+ * Strip server-only properties (matcher, etc.) from route tokens.
47
+ */
48
+ #sanitizeTokens(tokens) {
49
+ return tokens.map(({ old, type, val, end }) => ({ old, type, val, end }));
31
50
  }
32
- return true;
33
- }
34
- function generateRouteParams(route) {
35
- const dynamicParams = route.tokens.filter((token) => token.type === 1);
36
- const paramsType = dynamicParams.map((token) => `${token.val}: ParamValue`).join("; ");
37
- const paramsTuple = dynamicParams.map(() => "ParamValue").join(", ");
38
- return { paramsType, paramsTuple };
39
- }
40
- function buildTreeStructure(routes) {
41
- const tree = /* @__PURE__ */ new Map();
42
- for (const route of routes) {
43
- const segments = route.name.split(".");
44
- let current = tree;
45
- for (let i = 0; i < segments.length; i++) {
46
- const segment = stringHelpers.camelCase(segments[i]);
47
- const isLast = i === segments.length - 1;
48
- if (isLast) {
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
- }
54
- } else {
55
- if (!current.has(segment)) {
56
- current.set(segment, /* @__PURE__ */ new Map());
51
+ /**
52
+ * Extract dynamic params from route tokens and return
53
+ * the TS type string and tuple representation.
54
+ */
55
+ #generateRouteParams(route) {
56
+ const dynamicParams = route.tokens.filter((token) => token.type === 1 || token.type === 2);
57
+ const paramsType = dynamicParams.map((token) => {
58
+ if (token.type === 2) return "'*': ParamValue[]";
59
+ return `${token.val}: ParamValue`;
60
+ }).join("; ");
61
+ const paramsTuple = dynamicParams.map(() => "ParamValue").join(", ");
62
+ return { paramsType, paramsTuple };
63
+ }
64
+ /**
65
+ * Wrap a response type with `ExtractResponse` (and `Awaited`
66
+ * for `ReturnType<>`) to extract the `__response` property.
67
+ */
68
+ #wrapResponseType(responseType) {
69
+ if (responseType === "unknown" || responseType === "{}") return responseType;
70
+ if (responseType.startsWith("ReturnType<")) return `ExtractResponse<Awaited<${responseType}>>`;
71
+ return `ExtractResponse<${responseType}>`;
72
+ }
73
+ /**
74
+ * Wrap a response type with `ExtractErrorResponse` to
75
+ * extract non-2xx error types from the response union.
76
+ */
77
+ #wrapErrorResponseType(responseType) {
78
+ if (responseType === "unknown" || responseType === "{}") return "unknown";
79
+ if (responseType.startsWith("ReturnType<"))
80
+ return `ExtractErrorResponse<Awaited<${responseType}>>`;
81
+ return `ExtractErrorResponse<${responseType}>`;
82
+ }
83
+ /**
84
+ * Resolve body and query type strings based on the HTTP method.
85
+ * GET/HEAD routes use `ExtractQueryForGet`, others use
86
+ * `ExtractBody`/`ExtractQuery`.
87
+ */
88
+ #determineBodyAndQueryTypes(options) {
89
+ const { methods, requestType } = options;
90
+ const primaryMethod = methods[0];
91
+ const isGetLike = primaryMethod === "GET" || primaryMethod === "HEAD";
92
+ const hasValidator = requestType !== "{}";
93
+ if (!hasValidator) return { bodyType: "{}", queryType: "{}" };
94
+ if (isGetLike) return { bodyType: "{}", queryType: `ExtractQueryForGet<${requestType}>` };
95
+ return {
96
+ bodyType: `ExtractBody<${requestType}>`,
97
+ queryType: `ExtractQuery<${requestType}>`
98
+ };
99
+ }
100
+ /**
101
+ * Build a nested Map from dot-separated route names.
102
+ * Uses `$self` to handle nodes that are both a leaf and a prefix.
103
+ */
104
+ #buildTreeStructure(routes) {
105
+ const tree = /* @__PURE__ */ new Map();
106
+ for (const route of routes) {
107
+ const segments = route.name.split(".");
108
+ let current = tree;
109
+ for (let i = 0; i < segments.length; i++) {
110
+ const segment = stringHelpers.camelCase(segments[i]);
111
+ const isLast = i === segments.length - 1;
112
+ if (isLast) {
113
+ if (current.has(segment) && current.get(segment) instanceof Map) {
114
+ current.get(segment).set("$self", { routeName: route.name, route });
115
+ } else {
116
+ current.set(segment, { routeName: route.name, route });
117
+ }
57
118
  } 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);
119
+ if (!current.has(segment)) {
120
+ current.set(segment, /* @__PURE__ */ new Map());
121
+ } else {
122
+ const existing = current.get(segment);
123
+ if (!(existing instanceof Map)) {
124
+ const newMap = /* @__PURE__ */ new Map();
125
+ newMap.set("$self", existing);
126
+ current.set(segment, newMap);
127
+ }
63
128
  }
129
+ current = current.get(segment);
64
130
  }
65
- current = current.get(segment);
66
131
  }
67
132
  }
133
+ return tree;
68
134
  }
69
- return tree;
70
- }
71
- function generateTreeInterface(tree, indent = 2) {
72
- const spaces = " ".repeat(indent);
73
- const lines = [];
74
- for (const [key, value] of tree) {
75
- if (key === "$self") continue;
76
- if (value instanceof Map) {
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}}`);
135
+ /**
136
+ * Recursively emit TS interface lines from the nested tree Map.
137
+ * Nodes with `$self` produce intersection types.
138
+ */
139
+ #generateTreeInterface(tree, indent = 2) {
140
+ const spaces = " ".repeat(indent);
141
+ const lines = [];
142
+ for (const [key, value] of tree) {
143
+ if (key === "$self") continue;
144
+ if (value instanceof Map) {
145
+ const selfRoute = value.get("$self");
146
+ if (selfRoute) {
147
+ lines.push(`${spaces}${key}: typeof routes['${selfRoute.routeName}'] & {`);
148
+ lines.push(this.#generateTreeInterface(value, indent + 2));
149
+ lines.push(`${spaces}}`);
150
+ } else {
151
+ lines.push(`${spaces}${key}: {`);
152
+ lines.push(this.#generateTreeInterface(value, indent + 2));
153
+ lines.push(`${spaces}}`);
154
+ }
82
155
  } else {
83
- lines.push(`${spaces}${key}: {`);
84
- lines.push(generateTreeInterface(value, indent + 2));
85
- lines.push(`${spaces}}`);
156
+ lines.push(`${spaces}${key}: typeof routes['${value.routeName}']`);
86
157
  }
87
- } else {
88
- lines.push(`${spaces}${key}: typeof routes['${value.routeName}']`);
89
158
  }
159
+ return lines.join("\n");
90
160
  }
91
- return lines.join("\n");
92
- }
93
- function normalizeImportPaths(typeString) {
94
- return typeString.replace(/import\('app\//g, "import('#app/").replace(/\.ts'\)/g, "')");
95
- }
96
- function sanitizeTokens(tokens) {
97
- return tokens.map(({ old, type, val, end }) => ({ old, type, val, end }));
98
- }
99
- function generateRuntimeRegistryEntry(route) {
100
- const routeName = route.name;
101
- const sanitizedTokens = sanitizeTokens(route.tokens);
102
- return ` '${routeName}': {
161
+ /**
162
+ * Generate a single runtime registry entry for a route
163
+ * (methods, pattern, tokens, and a typed placeholder).
164
+ */
165
+ generateRuntimeRegistryEntry(route) {
166
+ const routeName = route.name;
167
+ const sanitizedTokens = this.#sanitizeTokens(route.tokens);
168
+ return ` '${routeName}': {
103
169
  methods: ${JSON.stringify(route.methods)},
104
170
  pattern: '${route.pattern}',
105
171
  tokens: ${JSON.stringify(sanitizedTokens)},
106
172
  types: placeholder as Registry['${routeName}']['types'],
107
173
  }`;
108
- }
109
- function wrapResponseType(responseType) {
110
- if (responseType === "unknown" || responseType === "{}") return responseType;
111
- if (responseType.startsWith("ReturnType<")) {
112
- return `ExtractResponse<Awaited<${responseType}>>`;
113
- }
114
- return `ExtractResponse<${responseType}>`;
115
- }
116
- function determineBodyAndQueryTypes(options) {
117
- const { methods, requestType } = options;
118
- const primaryMethod = methods[0];
119
- const isGetLike = primaryMethod === "GET" || primaryMethod === "HEAD";
120
- const hasValidator = requestType !== "{}";
121
- if (!hasValidator) {
122
- return { bodyType: "{}", queryType: "{}" };
123
- }
124
- if (isGetLike) {
125
- return { bodyType: "{}", queryType: `ExtractQueryForGet<${requestType}>` };
126
174
  }
127
- return {
128
- bodyType: `ExtractBody<${requestType}>`,
129
- queryType: `ExtractQuery<${requestType}>`
130
- };
131
- }
132
- function generateTypesRegistryEntry(route) {
133
- const requestType = normalizeImportPaths(route.request?.type || "{}");
134
- const responseType = wrapResponseType(route.response?.type || "unknown");
135
- const { paramsType, paramsTuple } = generateRouteParams(route);
136
- const routeName = route.name;
137
- const { bodyType, queryType } = determineBodyAndQueryTypes({
138
- methods: route.methods,
139
- requestType
140
- });
141
- return ` '${routeName}': {
175
+ /**
176
+ * Generate a single type-level registry entry for a route
177
+ * (body, query, params, response, and error response types).
178
+ */
179
+ generateTypesRegistryEntry(route) {
180
+ const requestType = this.#normalizeImportPaths(route.request?.type || "{}");
181
+ const rawResponseType = route.response?.type || "unknown";
182
+ const responseType = this.#wrapResponseType(rawResponseType);
183
+ const hasValidator = requestType !== "{}";
184
+ let errorResponseType = this.#wrapErrorResponseType(rawResponseType);
185
+ if (hasValidator && this.#validationErrorType !== false) {
186
+ const validationError = `{ status: 422; response: ${this.#validationErrorType} }`;
187
+ errorResponseType = errorResponseType === "unknown" ? validationError : `${errorResponseType} | ${validationError}`;
188
+ }
189
+ const { paramsType, paramsTuple } = this.#generateRouteParams(route);
190
+ const routeName = route.name;
191
+ const { bodyType, queryType } = this.#determineBodyAndQueryTypes({
192
+ methods: route.methods,
193
+ requestType
194
+ });
195
+ return ` '${routeName}': {
142
196
  methods: ${JSON.stringify(route.methods)}
143
197
  pattern: '${route.pattern}'
144
198
  types: {
@@ -147,12 +201,17 @@ function generateTypesRegistryEntry(route) {
147
201
  params: ${paramsType ? `{ ${paramsType} }` : "{}"}
148
202
  query: ${queryType}
149
203
  response: ${responseType}
204
+ errorResponse: ${errorResponseType}
150
205
  }
151
206
  }`;
152
- }
153
- function generateRuntimeContent(routes) {
154
- const registryEntries = routes.map(generateRuntimeRegistryEntry).join(",\n");
155
- return `/* eslint-disable prettier/prettier */
207
+ }
208
+ /**
209
+ * Generate the full runtime registry file (`index.ts`)
210
+ * with route definitions and module augmentation.
211
+ */
212
+ generateRuntimeContent(routes) {
213
+ const registryEntries = routes.map((route) => this.generateRuntimeRegistryEntry(route)).join(",\n");
214
+ return `/* eslint-disable prettier/prettier */
156
215
  import type { AdonisEndpoint } from '@tuyau/core/types'
157
216
  import type { Registry } from './schema.d.ts'
158
217
  import type { ApiDefinition } from './tree.d.ts'
@@ -177,25 +236,28 @@ declare module '@tuyau/core/types' {
177
236
  }
178
237
  }
179
238
  `;
180
- }
181
- function generateTreeContent(routes) {
182
- const tree = buildTreeStructure(routes);
183
- const treeInterface = generateTreeInterface(tree);
184
- return `/* eslint-disable prettier/prettier */
185
- import type { routes } from './index.ts'
186
-
187
- export interface ApiDefinition {
188
- ${treeInterface}
189
- }
190
- `;
191
- }
192
- function generateTypesContent(routes) {
193
- const registryEntries = routes.map(generateTypesRegistryEntry).join("\n");
194
- return `/* eslint-disable prettier/prettier */
239
+ }
240
+ /**
241
+ * Generate the types-only registry file (`schema.d.ts`)
242
+ * with request/response type definitions for each route.
243
+ */
244
+ generateTypesContent(routes) {
245
+ const registryEntries = routes.map((route) => this.generateTypesRegistryEntry(route)).join("\n");
246
+ const useDefaultValidationType = this.#validationErrorType === DEFAULT_VALIDATION_ERROR_TYPE;
247
+ const coreImports = [
248
+ "ExtractBody",
249
+ "ExtractErrorResponse",
250
+ "ExtractQuery",
251
+ "ExtractQueryForGet",
252
+ "ExtractResponse"
253
+ ];
254
+ const vineImports = ["InferInput"];
255
+ if (useDefaultValidationType) vineImports.push("SimpleError");
256
+ return `/* eslint-disable prettier/prettier */
195
257
  /// <reference path="../manifest.d.ts" />
196
258
 
197
- import type { ExtractBody, ExtractQuery, ExtractQueryForGet, ExtractResponse } from '@tuyau/core/types'
198
- import type { InferInput } from '@vinejs/vine/types'
259
+ import type { ${coreImports.join(", ")} } from '@tuyau/core/types'
260
+ import type { ${vineImports.join(", ")} } from '@vinejs/vine/types'
199
261
 
200
262
  export type ParamValue = string | number | bigint | boolean
201
263
 
@@ -203,35 +265,71 @@ export interface Registry {
203
265
  ${registryEntries}
204
266
  }
205
267
  `;
268
+ }
269
+ /**
270
+ * Generate the tree types file (`tree.d.ts`) with a nested
271
+ * interface mirroring the dot-separated route name hierarchy.
272
+ */
273
+ generateTreeContent(routes) {
274
+ const tree = this.#buildTreeStructure(routes);
275
+ const treeInterface = this.#generateTreeInterface(tree);
276
+ return `/* eslint-disable prettier/prettier */
277
+ import type { routes } from './index.ts'
278
+
279
+ export interface ApiDefinition {
280
+ ${treeInterface}
206
281
  }
282
+ `;
283
+ }
284
+ /**
285
+ * Write a file to disk, creating parent directories if needed.
286
+ */
287
+ async #writeFile(filePath, content) {
288
+ const dir = dirname(filePath);
289
+ await mkdir(dir, { recursive: true });
290
+ await writeFile(filePath, content);
291
+ }
292
+ /**
293
+ * Generate all three registry files at once.
294
+ * Returns the content strings without writing to disk.
295
+ */
296
+ generate(routes) {
297
+ return {
298
+ runtime: this.generateRuntimeContent(routes),
299
+ types: this.generateTypesContent(routes),
300
+ tree: this.generateTreeContent(routes)
301
+ };
302
+ }
303
+ /**
304
+ * Generate and write all registry files (index.ts, schema.d.ts, tree.d.ts)
305
+ * to the given output directory.
306
+ */
307
+ async writeOutput(options) {
308
+ const result = this.generate(options.routes);
309
+ const dir = options.outputDir.replace(/\/$/, "");
310
+ await Promise.all([
311
+ this.#writeFile(`${dir}/index.ts`, result.runtime),
312
+ this.#writeFile(`${dir}/schema.d.ts`, result.types),
313
+ this.#writeFile(`${dir}/tree.d.ts`, result.tree)
314
+ ]);
315
+ return result;
316
+ }
317
+ };
318
+
319
+ // src/backend/generate_registry.ts
207
320
  function generateRegistry(options) {
208
- const config = {
209
- output: "./.adonisjs/client/registry",
210
- ...options
211
- };
321
+ const generator = new RegistryGenerator(options);
322
+ const outputDir = options?.output ?? "./.adonisjs/client/registry";
212
323
  return {
213
324
  async run(_, hooks) {
214
325
  hooks.add("routesScanning", (_2, routesScanner) => {
215
- routesScanner.filter((route) => {
216
- return filterRoute(route, config.routes);
217
- });
326
+ routesScanner.filter((route) => generator.filterRoute(route));
218
327
  });
219
328
  hooks.add("routesScanned", async (devServer, routesScanner) => {
220
329
  const startTime = process.hrtime();
221
- const scannedRoutes = routesScanner.getScannedRoutes();
222
- const runtimeContent = generateRuntimeContent(scannedRoutes);
223
- const typesContent = generateTypesContent(scannedRoutes);
224
- const treeContent = generateTreeContent(scannedRoutes);
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`;
229
- await Promise.all([
230
- writeOutputFile(runtimePath, runtimeContent),
231
- writeOutputFile(typesPath, typesContent),
232
- writeOutputFile(treePath, treeContent)
233
- ]);
234
- devServer.ui.logger.info(`tuyau: created api client registry (${registryDir})`, {
330
+ const routes = routesScanner.getScannedRoutes();
331
+ await generator.writeOutput({ outputDir, routes });
332
+ devServer.ui.logger.info(`tuyau: created api client registry (${outputDir})`, {
235
333
  startTime
236
334
  });
237
335
  });
@@ -1,6 +1,7 @@
1
1
  import { UrlFor } from '@adonisjs/http-server/client/url_builder';
2
- import { TuyauRegistry, TuyauConfiguration, AdonisEndpoint, InferRoutes, TransformApiDefinition, InferTree, RegistryGroupedByMethod, PatternsByMethod, RequestArgs, EndpointByMethodPattern, StrKeys, Method } from './types/index.js';
3
- import { KyResponse, KyRequest, HTTPError } from 'ky';
2
+ import { T as TuyauRegistry, a as TuyauConfiguration, A as AdonisEndpoint, I as InferRoutes, b as TransformApiDefinition, c as InferTree, R as RegistryGroupedByMethod, P as PatternsByMethod, d as RequestArgs, E as EndpointByMethodPattern, e as TuyauPromise, f as ErrorResponseOf, S as StrKeys, M as Method, C as CurrentRouteOptions } from '../index-SVztBh2W.js';
3
+ export { g as TuyauError, h as TuyauHTTPError, i as TuyauNetworkError } from '../index-SVztBh2W.js';
4
+ import 'ky';
4
5
 
5
6
  /**
6
7
  * Main client class for making HTTP requests to AdonisJS endpoints
@@ -20,31 +21,31 @@ declare class Tuyau<Reg extends TuyauRegistry, Routes extends Record<string, Ado
20
21
  /**
21
22
  * Makes a GET request to the specified pattern
22
23
  */
23
- get<P extends PatternsByMethod<Routes, 'GET'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'GET', P>>): Promise<EndpointByMethodPattern<Routes, 'GET', P>['types']['response']>;
24
+ get<P extends PatternsByMethod<Routes, 'GET'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'GET', P>>): TuyauPromise<EndpointByMethodPattern<Routes, 'GET', P>['types']['response'], ErrorResponseOf<EndpointByMethodPattern<Routes, 'GET', P>>>;
24
25
  /**
25
26
  * Makes a POST request to the specified pattern
26
27
  */
27
- post<P extends PatternsByMethod<Routes, 'POST'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'POST', P>>): Promise<EndpointByMethodPattern<Routes, 'POST', P>['types']['response']>;
28
+ post<P extends PatternsByMethod<Routes, 'POST'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'POST', P>>): TuyauPromise<EndpointByMethodPattern<Routes, 'POST', P>['types']['response'], ErrorResponseOf<EndpointByMethodPattern<Routes, 'POST', P>>>;
28
29
  /**
29
30
  * Makes a PUT request to the specified pattern
30
31
  */
31
- put<P extends PatternsByMethod<Routes, 'PUT'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'PUT', P>>): Promise<EndpointByMethodPattern<Routes, 'PUT', P>['types']['response']>;
32
+ put<P extends PatternsByMethod<Routes, 'PUT'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'PUT', P>>): TuyauPromise<EndpointByMethodPattern<Routes, 'PUT', P>['types']['response'], ErrorResponseOf<EndpointByMethodPattern<Routes, 'PUT', P>>>;
32
33
  /**
33
34
  * Makes a PATCH request to the specified pattern
34
35
  */
35
- patch<P extends PatternsByMethod<Routes, 'PATCH'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'PATCH', P>>): Promise<EndpointByMethodPattern<Routes, 'PATCH', P>['types']['response']>;
36
+ patch<P extends PatternsByMethod<Routes, 'PATCH'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'PATCH', P>>): TuyauPromise<EndpointByMethodPattern<Routes, 'PATCH', P>['types']['response'], ErrorResponseOf<EndpointByMethodPattern<Routes, 'PATCH', P>>>;
36
37
  /**
37
38
  * Makes a DELETE request to the specified pattern
38
39
  */
39
- delete<P extends PatternsByMethod<Routes, 'DELETE'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'DELETE', P>>): Promise<EndpointByMethodPattern<Routes, 'DELETE', P>['types']['response']>;
40
+ delete<P extends PatternsByMethod<Routes, 'DELETE'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'DELETE', P>>): TuyauPromise<EndpointByMethodPattern<Routes, 'DELETE', P>['types']['response'], ErrorResponseOf<EndpointByMethodPattern<Routes, 'DELETE', P>>>;
40
41
  /**
41
42
  * Makes a HEAD request to the specified pattern
42
43
  */
43
- head<P extends PatternsByMethod<Routes, 'HEAD'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'HEAD', P>>): Promise<EndpointByMethodPattern<Routes, 'HEAD', P>['types']['response']>;
44
+ head<P extends PatternsByMethod<Routes, 'HEAD'>>(pattern: P, args: RequestArgs<EndpointByMethodPattern<Routes, 'HEAD', P>>): TuyauPromise<EndpointByMethodPattern<Routes, 'HEAD', P>['types']['response'], ErrorResponseOf<EndpointByMethodPattern<Routes, 'HEAD', P>>>;
44
45
  /**
45
46
  * Makes a request to a named endpoint
46
47
  */
47
- request<Name extends StrKeys<Routes>>(name: Name, args: RequestArgs<Routes[Name]>): Promise<Routes[Name]['types']['response']>;
48
+ request<Name extends StrKeys<Routes>>(name: Name, args: RequestArgs<Routes[Name]>): TuyauPromise<Routes[Name]['types']['response'], ErrorResponseOf<Routes[Name]>>;
48
49
  /**
49
50
  * Gets route information by name including URL and HTTP method
50
51
  */
@@ -52,29 +53,26 @@ declare class Tuyau<Reg extends TuyauRegistry, Routes extends Record<string, Ado
52
53
  url: string;
53
54
  methods: Method[];
54
55
  };
56
+ /**
57
+ * Checks if a route name exists in the registry.
58
+ */
59
+ has(routeName: StrKeys<Routes>): boolean;
60
+ /**
61
+ * Determines the current route based on `window.location`.
62
+ *
63
+ * - **No arguments** — returns the current route name, or `undefined`
64
+ * if no route matches (or running server-side).
65
+ * - **With a route name** — returns `true` if the current URL matches
66
+ * that route. Supports `*` wildcards.
67
+ * - **With options** — additionally checks that the current URL params
68
+ * and/or query match the provided values.
69
+ */
70
+ current(): StrKeys<Routes> | undefined;
71
+ current(routeName: StrKeys<Routes> | (string & {}), options?: CurrentRouteOptions): boolean;
55
72
  }
56
73
  /**
57
74
  * Factory function to create a new Tuyau client instance
58
75
  */
59
76
  declare function createTuyau<Reg extends TuyauRegistry>(config: TuyauConfiguration<Reg>): Tuyau<Reg, InferRoutes<Reg>>;
60
77
 
61
- declare function parseResponse(response?: KyResponse): Promise<unknown>;
62
- declare class TuyauHTTPError extends Error {
63
- status: number | undefined;
64
- rawResponse: KyResponse | undefined;
65
- rawRequest: KyRequest | undefined;
66
- response: any;
67
- constructor(kyError: HTTPError, response: any);
68
- }
69
- /**
70
- * Network error that occurs when the server is unreachable or the client is offline
71
- */
72
- declare class TuyauNetworkError extends Error {
73
- cause: Error;
74
- constructor(cause: Error, request?: {
75
- url: string;
76
- method: string;
77
- });
78
- }
79
-
80
- export { Tuyau, TuyauHTTPError, TuyauNetworkError, createTuyau, parseResponse };
78
+ export { Tuyau, TuyauPromise, createTuyau };