@tuyau/core 1.0.0-beta.0 → 1.0.0-beta.2

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,164 +1,16 @@
1
- import * as _adonisjs_assembler_routes_scanner from '@adonisjs/assembler/routes_scanner';
2
- import * as _poppinss_cliui_types from '@poppinss/cliui/types';
3
- import * as _poppinss_cliui from '@poppinss/cliui';
4
- import * as _poppinss_colors_types from '@poppinss/colors/types';
5
- import { DevServerOptions } from './types/common.ts';
6
-
7
- /**
8
- * Exposes the API to start the development server in HMR, watch or static mode
9
- *
10
- * In HMR mode, the DevServer will exec the "bin/server.ts" file and let hot-hook
11
- * manage the changes using hot module reloading.
12
- *
13
- * In watch mode, the DevServer will start an internal watcher and restarts the server
14
- * after every file change. The files must be part of the TypeScript project (via tsconfig.json),
15
- * or registered as metaFiles.
16
- *
17
- * In static mode, the server runs without file watching or hot reloading.
18
- *
19
- * @example
20
- * const devServer = new DevServer(cwd, { hmr: true, hooks: [] })
21
- * await devServer.start(ts)
22
- */
23
- declare class DevServer {
24
- #private;
25
- /**
26
- * CLI UI instance for displaying colorful messages and progress information
27
- */
28
- ui: {
29
- colors: _poppinss_colors_types.Colors;
30
- logger: _poppinss_cliui.Logger;
31
- table: (tableOptions?: Partial<_poppinss_cliui_types.TableOptions>) => _poppinss_cliui.Table;
32
- tasks: (tasksOptions?: Partial<_poppinss_cliui_types.TaskManagerOptions>) => _poppinss_cliui.TaskManager;
33
- icons: {
34
- tick: string;
35
- cross: string;
36
- bullet: string;
37
- nodejs: string;
38
- pointer: string;
39
- info: string;
40
- warning: string;
41
- squareSmallFilled: string;
42
- };
43
- sticker: () => _poppinss_cliui.Instructions;
44
- instructions: () => _poppinss_cliui.Instructions;
45
- switchMode(modeToUse: "raw" | "silent" | "normal"): void;
46
- useRenderer(rendererToUse: _poppinss_cliui_types.RendererContract): void;
47
- useColors(colorsToUse: _poppinss_colors_types.Colors): void;
48
- };
49
- /**
50
- * The mode in which the DevServer is running
51
- *
52
- * Returns the current operating mode of the development server:
53
- * - 'hmr': Hot Module Reloading enabled
54
- * - 'watch': File system watching with full restarts
55
- * - 'static': No file watching or hot reloading
56
- */
57
- get mode(): "hmr" | "watch" | "static";
58
- /**
59
- * Script file to start the development server
60
- */
61
- scriptFile: string;
62
- /**
63
- * The current working directory URL
64
- */
65
- cwd: URL;
66
- /**
67
- * File path computed from the cwd
68
- */
69
- cwdPath: string;
70
- /**
71
- * Development server configuration options including hooks and environment variables
72
- */
73
- options: DevServerOptions;
74
- /**
75
- * Create a new DevServer instance
76
- *
77
- * @param cwd - The current working directory URL
78
- * @param options - Development server configuration options
79
- */
80
- constructor(cwd: URL, options: DevServerOptions);
81
- /**
82
- * Adds listener to get notified when dev server is closed
83
- *
84
- * Registers a callback function that will be invoked when the development
85
- * server's child process exits. The callback receives the exit code.
86
- *
87
- * @param callback - Function to call when dev server closes
88
- * @returns This DevServer instance for method chaining
89
- *
90
- * @example
91
- * devServer.onClose((exitCode) => {
92
- * console.log(`Server closed with exit code: ${exitCode}`)
93
- * })
94
- */
95
- onClose(callback: (exitCode: number) => any): this;
96
- /**
97
- * Adds listener to get notified when dev server encounters an error
98
- *
99
- * Registers a callback function that will be invoked when the development
100
- * server's child process encounters an error or fails to start.
101
- *
102
- * @param callback - Function to call when dev server encounters an error
103
- * @returns This DevServer instance for method chaining
104
- *
105
- * @example
106
- * devServer.onError((error) => {
107
- * console.error('Dev server error:', error.message)
108
- * })
109
- */
110
- onError(callback: (error: any) => any): this;
111
- /**
112
- * Closes watchers and terminates the running child process
113
- *
114
- * Cleans up keyboard shortcuts, stops file system watchers, and kills
115
- * the HTTP server child process. This should be called when shutting down
116
- * the development server.
117
- *
118
- * @example
119
- * await devServer.close()
120
- */
121
- close(): Promise<void>;
122
- /**
123
- * Starts the development server in static or HMR mode
124
- *
125
- * Initializes the server and starts the HTTP server. The mode is determined
126
- * by the `hmr` option in DevServerOptions. In HMR mode, hot-hook is configured
127
- * to enable hot module reloading.
128
- *
129
- * @param ts - TypeScript module reference
130
- *
131
- * @example
132
- * const devServer = new DevServer(cwd, { hmr: true, hooks: [] })
133
- * await devServer.start(ts)
134
- */
135
- start(): Promise<void>;
136
- /**
137
- * Starts the development server in watch mode and restarts on file changes
138
- *
139
- * Initializes the server, starts the HTTP server, and sets up a file system
140
- * watcher that monitors for changes. When files are added, modified, or deleted,
141
- * the server automatically restarts. The watcher respects TypeScript project
142
- * configuration and metaFiles settings.
143
- *
144
- * @param ts - TypeScript module reference
145
- * @param options - Watch options including polling mode
146
- *
147
- * @example
148
- * const devServer = new DevServer(cwd, { hooks: [] })
149
- * await devServer.startAndWatch(ts, { poll: false })
150
- */
151
- startAndWatch(options?: {
152
- poll: boolean;
153
- }): Promise<void>;
154
- }
155
-
156
1
  interface GenerateRegistryConfig {
157
2
  /**
158
3
  * Path to write the generated registry file
159
4
  * @default ./.adonisjs/client/registry.ts
160
5
  */
161
6
  output?: string;
7
+ /**
8
+ * Whether to split runtime values and TypeScript types into separate files
9
+ * When true, generates registry.ts (runtime) and registry.schema.d.ts (types)
10
+ * When false, generates a single file with both runtime and types
11
+ * @default true
12
+ */
13
+ splitTypesFromRuntime?: boolean;
162
14
  /**
163
15
  * Routes filtering configuration
164
16
  */
@@ -176,7 +28,7 @@ interface GenerateRegistryConfig {
176
28
  };
177
29
  }
178
30
  declare function generateRegistry(options?: GenerateRegistryConfig): {
179
- run(devServer: DevServer, routesScanner: _adonisjs_assembler_routes_scanner.RoutesScanner): Promise<void>;
31
+ run(devServer: any, routesScanner: any): Promise<void>;
180
32
  };
181
33
 
182
34
  export { generateRegistry };
@@ -1,6 +1,5 @@
1
1
  // src/backend/generate_registry.ts
2
2
  import { dirname } from "path";
3
- import string from "@adonisjs/core/helpers/string";
4
3
  import { writeFile, mkdir } from "fs/promises";
5
4
  async function writeOutputFile(filePath, content) {
6
5
  const dir = dirname(filePath);
@@ -32,11 +31,37 @@ function generateRouteParams(route) {
32
31
  const paramsTuple = dynamicParams.map(() => "string").join(", ");
33
32
  return { paramsType, paramsTuple };
34
33
  }
34
+ function generateRuntimeRegistryEntry(route) {
35
+ const routeName = route.name;
36
+ return ` '${routeName}': {
37
+ methods: ${JSON.stringify(route.methods)},
38
+ pattern: '${route.pattern}',
39
+ tokens: ${JSON.stringify(route.tokens)},
40
+ types: placeholder as Registry['${routeName}']['types'],
41
+ }`;
42
+ }
43
+ function generateTypesRegistryEntry(route) {
44
+ const requestType = route.request?.type || "{}";
45
+ const responseType = route.response?.type || "unknown";
46
+ const { paramsType, paramsTuple } = generateRouteParams(route);
47
+ const routeName = route.name;
48
+ return ` '${routeName}': {
49
+ methods: ${JSON.stringify(route.methods)}
50
+ pattern: '${route.pattern}'
51
+ types: {
52
+ body: ${requestType}
53
+ paramsTuple: [${paramsTuple}]
54
+ params: ${paramsType ? `{ ${paramsType} }` : "{}"}
55
+ query: {}
56
+ response: ${responseType}
57
+ }
58
+ }`;
59
+ }
35
60
  function generateRegistryEntry(route) {
36
61
  const requestType = route.request?.type || "{}";
37
62
  const responseType = route.response?.type || "unknown";
38
63
  const { paramsType, paramsTuple } = generateRouteParams(route);
39
- const routeName = route.name.split(".").map((segment) => string.camelCase(segment)).join(".");
64
+ const routeName = route.name;
40
65
  return ` '${routeName}': {
41
66
  methods: ${JSON.stringify(route.methods)},
42
67
  pattern: '${route.pattern}',
@@ -50,6 +75,35 @@ function generateRegistryEntry(route) {
50
75
  },
51
76
  }`;
52
77
  }
78
+ function generateRuntimeContent(routes) {
79
+ const registryEntries = routes.map(generateRuntimeRegistryEntry).join(",\n");
80
+ return `/* eslint-disable prettier/prettier */
81
+ import { type AdonisEndpoint } from '@tuyau/core/types'
82
+ import type { Registry } from './registry.schema'
83
+ const placeholder: any = {}
84
+
85
+ export const registry = {
86
+ ${registryEntries}
87
+ } as const satisfies Record<string, AdonisEndpoint>
88
+ `;
89
+ }
90
+ function generateTypesContent(routes) {
91
+ const registryEntries = routes.map(generateTypesRegistryEntry).join("\n");
92
+ return `/* eslint-disable prettier/prettier */
93
+ /// <reference path="../../adonisrc.ts" />
94
+
95
+ import type { AdonisEndpoint } from '@tuyau/core/types'
96
+ import type { Infer } from '@vinejs/vine/types'
97
+
98
+ export interface Registry {
99
+ ${registryEntries}
100
+ }
101
+
102
+ declare module '@tuyau/core/types' {
103
+ export interface UserRegistry extends Registry {}
104
+ }
105
+ `;
106
+ }
53
107
  function generateRegistryContent(routes) {
54
108
  const registryEntries = routes.map(generateRegistryEntry).join(",\n");
55
109
  return `/* eslint-disable prettier/prettier */
@@ -70,6 +124,7 @@ declare module '@tuyau/core/types' {
70
124
  function generateRegistry(options) {
71
125
  const config = {
72
126
  output: "./.adonisjs/client/registry.ts",
127
+ splitTypesFromRuntime: true,
73
128
  ...options
74
129
  };
75
130
  return {
@@ -77,9 +132,21 @@ function generateRegistry(options) {
77
132
  const startTime = process.hrtime();
78
133
  const scannedRoutes = routesScanner.getScannedRoutes();
79
134
  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 });
135
+ if (config.splitTypesFromRuntime) {
136
+ const runtimeContent = generateRuntimeContent(filteredRoutes);
137
+ const typesContent = generateTypesContent(filteredRoutes);
138
+ const basePath = config.output.replace(/\.(ts|js)$/, "");
139
+ const runtimePath = `${basePath}.ts`;
140
+ const typesPath = `${basePath}.schema.d.ts`;
141
+ await writeOutputFile(runtimePath, runtimeContent);
142
+ await writeOutputFile(typesPath, typesContent);
143
+ devServer.ui.logger.info(`created ${runtimePath}`, { startTime });
144
+ devServer.ui.logger.info(`created ${typesPath}`, { startTime });
145
+ } else {
146
+ const registryContent = generateRegistryContent(filteredRoutes);
147
+ await writeOutputFile(config.output, registryContent);
148
+ devServer.ui.logger.info(`created ${config.output}`, { startTime });
149
+ }
83
150
  }
84
151
  };
85
152
  }
@@ -70,6 +70,9 @@ function isObject(value) {
70
70
  }
71
71
  var isServer = typeof FileList === "undefined";
72
72
  var isReactNative = typeof navigator !== "undefined" && navigator.product === "ReactNative";
73
+ function segmentsToRouteName(segments) {
74
+ return segments.map((segment) => segment.replace(/[A-Z]/g, (g) => `-${g.toLowerCase()}`)).join(".");
75
+ }
73
76
 
74
77
  // src/client/tuyau.ts
75
78
  var Tuyau = class {
@@ -267,10 +270,10 @@ var Tuyau = class {
267
270
  * Creates a proxy-based fluent API for accessing endpoints by name
268
271
  */
269
272
  makeNamed(segments) {
270
- const dot = segments.join(".");
271
- const def = this.config.registry[dot];
273
+ const routeName = segmentsToRouteName(segments);
274
+ const def = this.config.registry[routeName];
272
275
  if (def) {
273
- const fn = (args) => this.#doFetch(dot, def.methods[0], args);
276
+ const fn = (args) => this.#doFetch(routeName, def.methods[0], args);
274
277
  return new Proxy(fn, {
275
278
  get: (_t, prop) => this.makeNamed([...segments, String(prop)]),
276
279
  apply: (_t, _this, argArray) => fn(...argArray)
@@ -23,7 +23,8 @@ interface AdonisEndpoint {
23
23
  /**
24
24
  * Registry mapping endpoint names to their definitions
25
25
  */
26
- type AdonisRegistry = Record<string, AdonisEndpoint>;
26
+ interface AdonisRegistry extends Record<string, AdonisEndpoint> {
27
+ }
27
28
  type ValueOf<T> = T[keyof T];
28
29
  type IsEmptyObj<T> = keyof T extends never ? true : false;
29
30
  type IsEmptyTuple<T> = T extends [] ? true : false;
@@ -61,6 +62,18 @@ type ResponseOf<E extends AdonisEndpoint> = E['types']['response'];
61
62
  * Splits a dot-separated string into an array of strings
62
63
  */
63
64
  type Split<S extends string> = S extends `${infer H}.${infer T}` ? [H, ...Split<T>] : [S];
65
+ /**
66
+ * Converts a string to camelCase
67
+ */
68
+ type CamelCase<S extends string> = S extends `${infer H}${infer T}` ? `${Lowercase<H>}${CamelCaseRest<T>}` : S;
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;
70
+ /**
71
+ * Applies camelCase to each segment in a split array
72
+ */
73
+ type CamelCaseSplit<T extends string[]> = T extends [
74
+ infer H extends string,
75
+ ...infer Rest extends string[]
76
+ ] ? [CamelCase<H>, ...CamelCaseSplit<Rest>] : [];
64
77
  /**
65
78
  * Converts a union type to an intersection type
66
79
  */
@@ -69,7 +82,7 @@ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
69
82
  * Builds a nested object type for the fluent API based on endpoint names
70
83
  */
71
84
  type BuildNamed<Reg extends Record<string, AdonisEndpoint>> = UnionToIntersection<{
72
- [K in keyof Reg & string]: SetAtPath<Split<K>, EndpointFn<Reg[K]>>;
85
+ [K in keyof Reg & string]: SetAtPath<CamelCaseSplit<Split<K>>, EndpointFn<Reg[K]>>;
73
86
  }[keyof Reg & string]>;
74
87
  /**
75
88
  * Sets a value at a specific path in a nested object type
@@ -103,22 +116,25 @@ type EndpointByMethodPattern<R extends Record<string, AdonisEndpoint>, M extends
103
116
  /**
104
117
  * Plugin function type for extending Tuyau functionality
105
118
  */
106
- type TuyauPlugin = (params: {
107
- options: TuyauConfiguration<any>;
108
- }) => void;
119
+ interface TuyauPlugin {
120
+ (params: {
121
+ options: TuyauConfiguration<any>;
122
+ }): void;
123
+ }
109
124
  type MaybeArray<T> = T | T[];
110
125
  /**
111
126
  * Type for URL query parameters
112
127
  */
113
- type QueryParameters = Record<string, MaybeArray<string | number | boolean | null | undefined>>;
128
+ interface QueryParameters extends Record<string, MaybeArray<string | number | boolean | null | undefined>> {
129
+ }
114
130
  /**
115
131
  * Configuration options for creating a Tuyau client
116
132
  */
117
- type TuyauConfiguration<T extends Record<string, AdonisEndpoint>> = Omit<Options, 'prefixUrl' | 'body' | 'json' | 'method' | 'searchParams'> & {
133
+ interface TuyauConfiguration<T extends Record<string, AdonisEndpoint>> extends Omit<Options, 'prefixUrl' | 'body' | 'json' | 'method' | 'searchParams'> {
118
134
  registry: T;
119
135
  baseUrl: string;
120
136
  plugins?: TuyauPlugin[];
121
- };
137
+ }
122
138
  /**
123
139
  * Should be augmented by the user to provide their endpoint registry
124
140
  */
@@ -192,4 +208,4 @@ type RegistryGroupedByMethod<R extends Record<string, AdonisEndpoint>, M extends
192
208
  };
193
209
  };
194
210
 
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 };
211
+ export { type AdonisEndpoint, type AdonisRegistry, type BuildNamed, type CamelCase, type CamelCaseSplit, 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 };
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.0",
4
+ "version": "1.0.0-beta.2",
5
5
  "description": "e2e client for AdonisJS",
6
6
  "author": "Julien Ripouteau <julien@ripouteau.com>",
7
7
  "license": "MIT",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@adonisjs/assembler": "8.0.0-next.19",
27
- "@adonisjs/core": "7.0.0-next.8"
27
+ "@adonisjs/core": "^7.0.0-next.10"
28
28
  },
29
29
  "dependencies": {
30
30
  "ky": "^1.14.0",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@adonisjs/assembler": "8.0.0-next.19",
35
- "@adonisjs/core": "7.0.0-next.8",
35
+ "@adonisjs/core": "^7.0.0-next.10",
36
36
  "@adonisjs/http-server": "8.0.0-next.12",
37
37
  "@arktype/attest": "^0.53.0",
38
38
  "@faker-js/faker": "^10.1.0",