nuxt-openapi-hyperfetch 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli/config.ts CHANGED
@@ -1,66 +1,50 @@
1
1
  import fs from 'fs-extra';
2
2
  import { join } from 'path';
3
+ import { pathToFileURL } from 'url';
3
4
  import * as p from '@clack/prompts';
4
- import type { GeneratorBackend, ConfigGenerator } from './types.js';
5
+ import type { GeneratorConfig, GeneratorType } from '../config/types.js';
5
6
 
6
7
  const { existsSync } = fs;
7
8
 
8
- /**
9
- * Configuration options for the generator
10
- */
11
- export interface GeneratorConfig {
12
- /** Path or URL to OpenAPI specification */
13
- input?: string;
14
- /** Output directory for generated files */
15
- output?: string;
16
- /** Base URL for API requests */
17
- baseUrl?: string;
18
- /** Generation mode: client or server */
19
- mode?: 'client' | 'server';
20
- /** Generate only specific tags */
21
- tags?: string[];
22
- /** Exclude specific tags */
23
- excludeTags?: string[];
24
- /** Overwrite existing files without prompting */
25
- overwrite?: boolean;
26
- /** Preview changes without writing files */
27
- dryRun?: boolean;
28
- /** Enable verbose logging */
29
- verbose?: boolean;
30
- /** Watch mode - regenerate on file changes */
31
- watch?: boolean;
32
- /** Generator types to use */
33
- generators?: ('useFetch' | 'useAsyncData' | 'nuxtServer' | 'connectors')[];
34
- /** Server route path (for nuxtServer mode) */
35
- serverRoutePath?: string;
36
- /** Enable BFF pattern (for nuxtServer mode) */
37
- enableBff?: boolean;
38
- /** Generator backend: official (Java) or heyapi (Node.js) */
39
- backend?: GeneratorBackend;
40
- /**
41
- * Generation engine to use.
42
- * - 'openapi': @openapitools/openapi-generator-cli (requires Java 11+)
43
- * - 'heyapi': @hey-api/openapi-ts (Node.js native, no Java required)
44
- * When set, the CLI will not ask which engine to use.
45
- */
46
- generator?: ConfigGenerator;
47
- /**
48
- * Generate headless UI connector composables on top of useAsyncData.
49
- * Connectors provide ready-made logic for tables, pagination, forms and delete actions.
50
- * Requires useAsyncData to also be generated.
51
- * @default false
52
- */
53
- createUseAsyncDataConnectors?: boolean;
9
+ export type ComposableGeneratorType = 'useFetch' | 'useAsyncData' | 'nuxtServer';
10
+
11
+ export interface NormalizedGenerators {
12
+ composables: ComposableGeneratorType[];
13
+ generateConnectors: boolean;
14
+ }
15
+
16
+ const COMPOSABLE_GENERATOR_ORDER: readonly ComposableGeneratorType[] = [
17
+ 'useFetch',
18
+ 'useAsyncData',
19
+ 'nuxtServer',
20
+ ] as const;
21
+
22
+ async function importConfigModule(configPath: string): Promise<unknown> {
23
+ try {
24
+ const module = await import(pathToFileURL(configPath).href);
25
+ return module.default || module;
26
+ } catch (error) {
27
+ if (!configPath.endsWith('.ts')) {
28
+ throw error;
29
+ }
30
+
31
+ const { createJiti } = await import('jiti');
32
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
33
+ const module = jiti(configPath);
34
+ return module?.default || module;
35
+ }
54
36
  }
55
37
 
56
38
  /**
57
- * Load configuration from nxh.config.js, nuxt-openapi-generator.config.js, or package.json
39
+ * Load configuration from nxh.config.{ts,js,mjs}, nuxt-openapi-hyperfetch.{ts,js,mjs}, or package.json
58
40
  */
59
41
  export async function loadConfig(cwd: string = process.cwd()): Promise<GeneratorConfig | null> {
60
42
  // Try different config file names
61
43
  const configFiles = [
44
+ 'nxh.config.ts',
62
45
  'nxh.config.js',
63
46
  'nxh.config.mjs',
47
+ 'nuxt-openapi-hyperfetch.ts',
64
48
  'nuxt-openapi-hyperfetch.js',
65
49
  'nuxt-openapi-hyperfetch.mjs',
66
50
  ];
@@ -69,11 +53,8 @@ export async function loadConfig(cwd: string = process.cwd()): Promise<Generator
69
53
  const configPath = join(cwd, configFile);
70
54
  if (existsSync(configPath)) {
71
55
  try {
72
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
73
- const config = await import(`file://${configPath}`);
74
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
75
- const exportedConfig = config.default || config;
76
- return exportedConfig as GeneratorConfig;
56
+ const config = await importConfigModule(configPath);
57
+ return config as GeneratorConfig;
77
58
  } catch (error) {
78
59
  p.log.warn(`Failed to load config from ${configFile}: ${String(error)}`);
79
60
  }
@@ -134,14 +115,37 @@ export function parseTags(tagsString?: string): string[] | undefined {
134
115
  /**
135
116
  * Parse generators string into array
136
117
  */
137
- export function parseGenerators(
138
- generatorsString?: string
139
- ): ('useFetch' | 'useAsyncData' | 'nuxtServer' | 'connectors')[] | undefined {
118
+ export function parseGenerators(generatorsString?: string): GeneratorType[] | undefined {
140
119
  if (!generatorsString) {
141
120
  return undefined;
142
121
  }
143
122
  const parts = generatorsString.split(',').map((g) => g.trim());
144
- return parts.filter((g): g is 'useFetch' | 'useAsyncData' | 'nuxtServer' | 'connectors' =>
123
+ return parts.filter((g): g is GeneratorType =>
145
124
  ['useFetch', 'useAsyncData', 'nuxtServer', 'connectors'].includes(g)
146
125
  );
147
126
  }
127
+
128
+ /**
129
+ * Normalize generator selection so connectors can be requested declaratively.
130
+ * Rules:
131
+ * - If `connectors` is selected, `useAsyncData` is added automatically.
132
+ * - If `createUseAsyncDataConnectors` is true, connectors are enabled and
133
+ * `useAsyncData` is added automatically.
134
+ */
135
+ export function normalizeGenerators(
136
+ generators?: GeneratorType[],
137
+ createUseAsyncDataConnectors?: boolean
138
+ ): NormalizedGenerators {
139
+ const requested = new Set(generators ?? []);
140
+ const generateConnectors = requested.has('connectors') || createUseAsyncDataConnectors === true;
141
+
142
+ const composables: ComposableGeneratorType[] = COMPOSABLE_GENERATOR_ORDER.filter((generator) =>
143
+ requested.has(generator)
144
+ );
145
+
146
+ if (generateConnectors && !composables.includes('useAsyncData')) {
147
+ composables.push('useAsyncData');
148
+ }
149
+
150
+ return { composables, generateConnectors };
151
+ }
@@ -0,0 +1,48 @@
1
+ import type { ConnectorsConfig, GeneratorConfig, GeneratorType } from './types.js';
2
+
3
+ export type RuntimeComposableGenerator = 'useFetch' | 'useAsyncData' | 'nuxtServer';
4
+
5
+ export function hasConnectorsConfig(connectors?: ConnectorsConfig): boolean {
6
+ if (!connectors) {
7
+ return false;
8
+ }
9
+
10
+ if (connectors.enabled === true) {
11
+ return true;
12
+ }
13
+
14
+ if (connectors.strategy !== undefined) {
15
+ return true;
16
+ }
17
+
18
+ return Object.keys(connectors.resources ?? {}).length > 0;
19
+ }
20
+
21
+ export function isConnectorsRequested(
22
+ config: Pick<GeneratorConfig, 'generators' | 'createUseAsyncDataConnectors' | 'connectors'>
23
+ ): boolean {
24
+ return (
25
+ config.createUseAsyncDataConnectors === true ||
26
+ (config.generators ?? []).includes('connectors') ||
27
+ hasConnectorsConfig(config.connectors)
28
+ );
29
+ }
30
+
31
+ export function toRuntimeComposableGenerators(
32
+ generators?: GeneratorType[]
33
+ ): RuntimeComposableGenerator[] {
34
+ return (generators ?? []).filter(
35
+ (g): g is RuntimeComposableGenerator =>
36
+ g === 'useFetch' || g === 'useAsyncData' || g === 'nuxtServer'
37
+ );
38
+ }
39
+
40
+ export function ensureUseAsyncDataForConnectors(
41
+ composables: RuntimeComposableGenerator[],
42
+ connectorsRequested: boolean
43
+ ): RuntimeComposableGenerator[] {
44
+ if (!connectorsRequested || composables.includes('useAsyncData')) {
45
+ return composables;
46
+ }
47
+ return [...composables, 'useAsyncData'];
48
+ }
@@ -0,0 +1,67 @@
1
+ import type { GeneratorBackend, ConfigGenerator } from '../cli/types.js';
2
+
3
+ export type GeneratorType = 'useFetch' | 'useAsyncData' | 'nuxtServer' | 'connectors';
4
+ export type ConnectorStrategy = 'manual' | 'hybrid';
5
+ export type ConnectorOperationName = 'getAll' | 'get' | 'create' | 'update' | 'delete';
6
+
7
+ export interface ConnectorOperationConfig {
8
+ operationId?: string;
9
+ path?: string;
10
+ }
11
+
12
+ export interface ConnectorResourceConfig {
13
+ operations?: Partial<Record<ConnectorOperationName, ConnectorOperationConfig>>;
14
+ }
15
+
16
+ export interface ConnectorsConfig {
17
+ enabled?: boolean;
18
+ strategy?: ConnectorStrategy;
19
+ resources?: Record<string, ConnectorResourceConfig>;
20
+ }
21
+
22
+ /**
23
+ * Shared configuration contract used by both CLI (nxh.config.*) and Nuxt module (nuxt.config.ts).
24
+ */
25
+ export interface GeneratorConfig {
26
+ /** Path or URL to OpenAPI specification */
27
+ input?: string;
28
+ /** Output directory for generated files */
29
+ output?: string;
30
+ /** Base URL for API requests */
31
+ baseUrl?: string;
32
+ /** Generation mode: client or server */
33
+ mode?: 'client' | 'server';
34
+ /** Generate only specific tags */
35
+ tags?: string[];
36
+ /** Exclude specific tags */
37
+ excludeTags?: string[];
38
+ /** Overwrite existing files without prompting */
39
+ overwrite?: boolean;
40
+ /** Preview changes without writing files */
41
+ dryRun?: boolean;
42
+ /** Enable verbose logging */
43
+ verbose?: boolean;
44
+ /** Watch mode - regenerate on file changes */
45
+ watch?: boolean;
46
+ /** Generator types to use */
47
+ generators?: GeneratorType[];
48
+ /** Server route path (for nuxtServer mode) */
49
+ serverRoutePath?: string;
50
+ /** Enable BFF pattern (for nuxtServer mode) */
51
+ enableBff?: boolean;
52
+ /** Generator backend: official (Java) or heyapi (Node.js) */
53
+ backend?: GeneratorBackend;
54
+ /**
55
+ * Generation engine to use.
56
+ * - 'openapi': @openapitools/openapi-generator-cli (requires Java 11+)
57
+ * - 'heyapi': @hey-api/openapi-ts (Node.js native, no Java required)
58
+ */
59
+ generator?: ConfigGenerator;
60
+ /**
61
+ * Generate headless UI connector composables on top of useAsyncData.
62
+ * Requires useAsyncData to also be generated.
63
+ */
64
+ createUseAsyncDataConnectors?: boolean;
65
+ /** Advanced connectors generation contract (manual/hybrid, custom resources, overloads). */
66
+ connectors?: ConnectorsConfig;
67
+ }
@@ -0,0 +1,303 @@
1
+ import { camelCase, pascalCase } from 'change-case';
2
+ import type {
3
+ ConnectorsConfig,
4
+ ConnectorOperationConfig,
5
+ ConnectorOperationName,
6
+ ConnectorResourceConfig,
7
+ } from '../../config/types.js';
8
+ import type {
9
+ EndpointInfo,
10
+ OpenApiSchema,
11
+ ResourceInfo,
12
+ ResourceMap,
13
+ } from '../components/schema-analyzer/types.js';
14
+ import {
15
+ mapColumnsFromSchema,
16
+ mapFieldsFromSchema,
17
+ buildZodSchema,
18
+ } from '../components/schema-analyzer/index.js';
19
+
20
+ function toConnectorName(tag: string): string {
21
+ const pascal = pascalCase(tag);
22
+ const plural = /(?:s|x|z|ch|sh)$/i.test(pascal) ? `${pascal}es` : `${pascal}s`;
23
+ return `use${plural}Connector`;
24
+ }
25
+
26
+ function cloneResource(resource: ResourceInfo): ResourceInfo {
27
+ return {
28
+ ...resource,
29
+ endpoints: [...resource.endpoints],
30
+ columns: [...resource.columns],
31
+ formFields: {
32
+ ...(resource.formFields.create ? { create: [...resource.formFields.create] } : {}),
33
+ ...(resource.formFields.update ? { update: [...resource.formFields.update] } : {}),
34
+ },
35
+ zodSchemas: { ...resource.zodSchemas },
36
+ };
37
+ }
38
+
39
+ function getRefNameFromSchema(schema?: OpenApiSchema): string | undefined {
40
+ if (!schema) {
41
+ return undefined;
42
+ }
43
+
44
+ const directRef = schema['x-ref-name'];
45
+ if (typeof directRef === 'string') {
46
+ return directRef;
47
+ }
48
+
49
+ const items = schema.items;
50
+ if (items && typeof items === 'object') {
51
+ const itemRef = items['x-ref-name'];
52
+ if (typeof itemRef === 'string') {
53
+ return itemRef;
54
+ }
55
+ }
56
+
57
+ return undefined;
58
+ }
59
+
60
+ function rebuildDerived(resource: ResourceInfo): void {
61
+ const schemaForColumns =
62
+ resource.listEndpoint?.responseSchema ?? resource.detailEndpoint?.responseSchema;
63
+ resource.columns = schemaForColumns ? mapColumnsFromSchema(schemaForColumns) : [];
64
+
65
+ resource.formFields = {
66
+ ...(resource.createEndpoint?.requestBodySchema
67
+ ? { create: mapFieldsFromSchema(resource.createEndpoint.requestBodySchema) }
68
+ : {}),
69
+ ...(resource.updateEndpoint?.requestBodySchema
70
+ ? { update: mapFieldsFromSchema(resource.updateEndpoint.requestBodySchema) }
71
+ : {}),
72
+ };
73
+
74
+ resource.zodSchemas = {
75
+ ...(resource.createEndpoint?.requestBodySchema
76
+ ? { create: buildZodSchema(resource.createEndpoint.requestBodySchema) }
77
+ : {}),
78
+ ...(resource.updateEndpoint?.requestBodySchema
79
+ ? { update: buildZodSchema(resource.updateEndpoint.requestBodySchema) }
80
+ : {}),
81
+ };
82
+
83
+ resource.itemTypeName =
84
+ getRefNameFromSchema(resource.detailEndpoint?.responseSchema) ??
85
+ getRefNameFromSchema(resource.listEndpoint?.responseSchema) ??
86
+ undefined;
87
+ }
88
+
89
+ function expectedMethodsForOperation(
90
+ operationName: ConnectorOperationName
91
+ ): Array<EndpointInfo['method']> {
92
+ switch (operationName) {
93
+ case 'getAll':
94
+ case 'get':
95
+ return ['GET'];
96
+ case 'create':
97
+ return ['POST'];
98
+ case 'update':
99
+ return ['PUT', 'PATCH'];
100
+ case 'delete':
101
+ return ['DELETE'];
102
+ }
103
+ }
104
+
105
+ function selectByIntent(
106
+ operationName: ConnectorOperationName,
107
+ candidates: EndpointInfo[]
108
+ ): EndpointInfo[] {
109
+ if (operationName === 'getAll') {
110
+ const preferred = candidates.filter((c) => c.intent === 'list');
111
+ return preferred.length > 0 ? preferred : candidates;
112
+ }
113
+ if (operationName === 'get') {
114
+ const preferred = candidates.filter((c) => c.intent === 'detail');
115
+ return preferred.length > 0 ? preferred : candidates;
116
+ }
117
+ if (operationName === 'update') {
118
+ const put = candidates.filter((c) => c.method === 'PUT');
119
+ return put.length > 0 ? put : candidates;
120
+ }
121
+ return candidates;
122
+ }
123
+
124
+ function buildEndpointIndex(resourceMap: ResourceMap): {
125
+ byOperationId: Map<string, EndpointInfo>;
126
+ byPath: Map<string, EndpointInfo[]>;
127
+ } {
128
+ const byOperationId = new Map<string, EndpointInfo>();
129
+ const byPath = new Map<string, EndpointInfo[]>();
130
+
131
+ for (const resource of resourceMap.values()) {
132
+ for (const endpoint of resource.endpoints) {
133
+ byOperationId.set(endpoint.operationId, endpoint);
134
+ const list = byPath.get(endpoint.path) ?? [];
135
+ list.push(endpoint);
136
+ byPath.set(endpoint.path, list);
137
+ }
138
+ }
139
+
140
+ return { byOperationId, byPath };
141
+ }
142
+
143
+ function resolveEndpoint(
144
+ operationName: ConnectorOperationName,
145
+ operationConfig: ConnectorOperationConfig,
146
+ endpointIndex: ReturnType<typeof buildEndpointIndex>,
147
+ resourceName: string
148
+ ): EndpointInfo {
149
+ if (operationConfig.operationId && operationConfig.path) {
150
+ throw new Error(
151
+ `[connectors] Resource "${resourceName}" operation "${operationName}" cannot define both operationId and path`
152
+ );
153
+ }
154
+
155
+ if (!operationConfig.operationId && !operationConfig.path) {
156
+ throw new Error(
157
+ `[connectors] Resource "${resourceName}" operation "${operationName}" must define operationId or path`
158
+ );
159
+ }
160
+
161
+ if (operationConfig.operationId) {
162
+ const endpoint = endpointIndex.byOperationId.get(operationConfig.operationId);
163
+ if (!endpoint) {
164
+ throw new Error(
165
+ `[connectors] Resource "${resourceName}" operation "${operationName}" references unknown operationId "${operationConfig.operationId}"`
166
+ );
167
+ }
168
+ return endpoint;
169
+ }
170
+
171
+ const candidates = endpointIndex.byPath.get(operationConfig.path!) ?? [];
172
+ if (candidates.length === 0) {
173
+ throw new Error(
174
+ `[connectors] Resource "${resourceName}" operation "${operationName}" references unknown path "${operationConfig.path}"`
175
+ );
176
+ }
177
+
178
+ const validMethods = expectedMethodsForOperation(operationName);
179
+ const byMethod = candidates.filter((c) => validMethods.includes(c.method));
180
+ if (byMethod.length === 0) {
181
+ throw new Error(
182
+ `[connectors] Resource "${resourceName}" operation "${operationName}" path "${operationConfig.path}" has no compatible method (${validMethods.join('/')})`
183
+ );
184
+ }
185
+
186
+ const prioritized = selectByIntent(operationName, byMethod);
187
+ return prioritized[0];
188
+ }
189
+
190
+ function setOperationEndpoint(
191
+ resource: ResourceInfo,
192
+ operationName: ConnectorOperationName,
193
+ endpoint: EndpointInfo
194
+ ): void {
195
+ switch (operationName) {
196
+ case 'getAll':
197
+ resource.listEndpoint = endpoint;
198
+ break;
199
+ case 'get':
200
+ resource.detailEndpoint = endpoint;
201
+ break;
202
+ case 'create':
203
+ resource.createEndpoint = endpoint;
204
+ break;
205
+ case 'update':
206
+ resource.updateEndpoint = endpoint;
207
+ break;
208
+ case 'delete':
209
+ resource.deleteEndpoint = endpoint;
210
+ break;
211
+ }
212
+
213
+ if (!resource.endpoints.some((ep) => ep.operationId === endpoint.operationId)) {
214
+ resource.endpoints.push(endpoint);
215
+ }
216
+ }
217
+
218
+ function createResourceSkeleton(resourceKey: string): ResourceInfo {
219
+ return {
220
+ name: pascalCase(resourceKey),
221
+ tag: resourceKey,
222
+ composableName: toConnectorName(resourceKey),
223
+ endpoints: [],
224
+ columns: [],
225
+ formFields: {},
226
+ zodSchemas: {},
227
+ };
228
+ }
229
+
230
+ function applyResourceOverrides(
231
+ target: ResourceInfo,
232
+ resourceName: string,
233
+ config: ConnectorResourceConfig,
234
+ endpointIndex: ReturnType<typeof buildEndpointIndex>
235
+ ): ResourceInfo {
236
+ const operations = config.operations ?? {};
237
+
238
+ for (const [operationName, operationConfig] of Object.entries(operations) as Array<
239
+ [ConnectorOperationName, ConnectorOperationConfig | undefined]
240
+ >) {
241
+ if (!operationConfig) {
242
+ continue;
243
+ }
244
+
245
+ const endpoint = resolveEndpoint(operationName, operationConfig, endpointIndex, resourceName);
246
+ setOperationEndpoint(target, operationName, endpoint);
247
+ }
248
+
249
+ rebuildDerived(target);
250
+ return target;
251
+ }
252
+
253
+ function normalizeConfig(
254
+ config?: ConnectorsConfig
255
+ ): Required<Pick<ConnectorsConfig, 'strategy'>> & ConnectorsConfig {
256
+ return {
257
+ strategy: config?.strategy ?? 'hybrid',
258
+ ...config,
259
+ };
260
+ }
261
+
262
+ export function resolveConnectorResourceMap(
263
+ baseResourceMap: ResourceMap,
264
+ connectorsConfig?: ConnectorsConfig
265
+ ): ResourceMap {
266
+ if (!connectorsConfig) {
267
+ return baseResourceMap;
268
+ }
269
+
270
+ const normalized = normalizeConfig(connectorsConfig);
271
+ const resourceConfigs = normalized.resources ?? {};
272
+ const endpointIndex = buildEndpointIndex(baseResourceMap);
273
+
274
+ if (normalized.strategy === 'manual') {
275
+ const manualMap: ResourceMap = new Map();
276
+
277
+ for (const [resourceName, resourceConfig] of Object.entries(resourceConfigs)) {
278
+ const resourceKey = camelCase(resourceName);
279
+ const baseResource = baseResourceMap.get(resourceKey);
280
+ const target = baseResource
281
+ ? cloneResource(baseResource)
282
+ : createResourceSkeleton(resourceName);
283
+
284
+ applyResourceOverrides(target, resourceName, resourceConfig, endpointIndex);
285
+ manualMap.set(resourceKey, target);
286
+ }
287
+
288
+ return manualMap;
289
+ }
290
+
291
+ const hybridMap: ResourceMap = new Map(
292
+ [...baseResourceMap.entries()].map(([key, value]) => [key, cloneResource(value)])
293
+ );
294
+
295
+ for (const [resourceName, resourceConfig] of Object.entries(resourceConfigs)) {
296
+ const resourceKey = camelCase(resourceName);
297
+ const target = hybridMap.get(resourceKey) ?? createResourceSkeleton(resourceName);
298
+ applyResourceOverrides(target, resourceName, resourceConfig, endpointIndex);
299
+ hybridMap.set(resourceKey, target);
300
+ }
301
+
302
+ return hybridMap;
303
+ }
@@ -9,6 +9,7 @@ import {
9
9
  generateConnectorIndexFile,
10
10
  } from './templates.js';
11
11
  import type { ConnectorGeneratorOptions } from './types.js';
12
+ import { resolveConnectorResourceMap } from './config-resolver.js';
12
13
  import { type Logger, createClackLogger } from '../../cli/logger.js';
13
14
 
14
15
  // Runtime files that must be copied to the user's project
@@ -56,7 +57,8 @@ export async function generateConnectors(
56
57
 
57
58
  // ── 1. Analyze spec ───────────────────────────────────────────────────────
58
59
  spinner.start('Analyzing OpenAPI spec');
59
- const resourceMap = analyzeSpec(options.inputSpec);
60
+ const baseResourceMap = analyzeSpec(options.inputSpec);
61
+ const resourceMap = resolveConnectorResourceMap(baseResourceMap, options.connectorsConfig);
60
62
  spinner.stop(`Found ${resourceMap.size} resource(s)`);
61
63
 
62
64
  if (resourceMap.size === 0) {
@@ -181,6 +181,7 @@ function buildOptionsInterface(resource: ResourceInfo): string {
181
181
  if (resource.listEndpoint && hasColumns) {
182
182
  fields.push(` columnLabels?: Record<string, string>;`);
183
183
  fields.push(` columnLabel?: (key: string) => string;`);
184
+ fields.push(` getAllRequestOptions?: Record<string, unknown>;`);
184
185
  }
185
186
  if (resource.createEndpoint && resource.zodSchemas.create) {
186
187
  const pascal = pascalCase(resource.name);
@@ -264,7 +265,9 @@ function buildFunctionBody(resource: ResourceInfo): string {
264
265
  // Options destructure
265
266
  const optionKeys: string[] = [];
266
267
  if (resource.listEndpoint && hasColumns) {
267
- optionKeys.push('columnLabels', 'columnLabel');
268
+ optionKeys.push('columnLabels', 'columnLabel', 'getAllRequestOptions');
269
+ } else if (resource.listEndpoint) {
270
+ optionKeys.push('getAllRequestOptions');
268
271
  }
269
272
  if (resource.createEndpoint && resource.zodSchemas.create) {
270
273
  optionKeys.push('createSchema');
@@ -317,7 +320,7 @@ function buildFunctionBody(resource: ResourceInfo): string {
317
320
  ` const isFactory = typeof paramsOrSource === 'function';`,
318
321
  ` const listFactory = isFactory`,
319
322
  ` ? (paramsOrSource as () => unknown)`,
320
- ` : () => ${fn}((paramsOrSource ?? {}) as ${listRequestTypeName});`,
323
+ ` : () => ${fn}((paramsOrSource ?? {}) as ${listRequestTypeName}, { paginated: true, ...(getAllRequestOptions ?? {}) });`,
321
324
  ` const getAll = useGetAllConnector(listFactory, ${opts}) as unknown as GetAllConnectorReturn<${pascal}>;`
322
325
  );
323
326
  } else {
@@ -5,6 +5,8 @@
5
5
  * and writes one `use{Resource}Connector.ts` file per resource using $fetch for mutations.
6
6
  */
7
7
 
8
+ import type { ConnectorsConfig } from '../../config/types.js';
9
+
8
10
  export interface ConnectorGeneratorOptions {
9
11
  /** Absolute or relative path to the OpenAPI YAML/JSON spec */
10
12
  inputSpec: string;
@@ -25,6 +27,8 @@ export interface ConnectorGeneratorOptions {
25
27
  * useRuntimeConfig().public.apiBaseUrl at runtime.
26
28
  */
27
29
  baseUrl?: string;
30
+ /** Advanced connectors configuration (manual/hybrid, custom resources, overloads). */
31
+ connectorsConfig?: ConnectorsConfig;
28
32
  }
29
33
 
30
34
  export interface ConnectorFileInfo {