mulink 1.0.3 → 1.0.5

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.
@@ -1032,8 +1032,8 @@ import { cache } from 'react'
1032
1032
  * We use dynamic imports to avoid bundling them in the client
1033
1033
  */
1034
1034
  let serverOnlyModules: {
1035
- cookies?: () => import('next/headers').ReadonlyRequestCookies
1036
- headers?: () => import('next/headers').Headers
1035
+ cookies?: () => Awaited<ReturnType<typeof import('next/headers').cookies>>
1036
+ headers?: () => Awaited<ReturnType<typeof import('next/headers').headers>>
1037
1037
  after?: (fn: () => void | Promise<void>) => void
1038
1038
  updateTag?: (tag: string) => void
1039
1039
  } | null = null
@@ -1336,12 +1336,14 @@ export class BaseApiClient {
1336
1336
 
1337
1337
  // Try external auth service
1338
1338
  ${this.configuration.auth?.enabled ? `try {
1339
- const { ${tokenGetter} } = await import('${authPath}')
1340
- const session = await ${tokenGetter}()
1341
-
1342
- if (session?.accessToken) {
1343
- getAuthHeaders.Authorization = \`Bearer \${session.accessToken}\`
1344
- return getAuthHeaders
1339
+ const authModule = await import('${authPath}').catch(() => null)
1340
+ if (authModule && authModule.${tokenGetter}) {
1341
+ const session = await authModule.${tokenGetter}()
1342
+
1343
+ if (session?.accessToken) {
1344
+ getAuthHeaders.Authorization = \`Bearer \${session.accessToken}\`
1345
+ return getAuthHeaders
1346
+ }
1345
1347
  }
1346
1348
  } catch (error) {
1347
1349
  // Auth not available or error accessing
@@ -1540,7 +1542,8 @@ export class BaseApiClient {
1540
1542
  const requestPromise = this.executeRequestInternal<TData>(
1541
1543
  method, url, body, headers, fetchOptions, timeout, retries,
1542
1544
  retryDelay, retryCondition, validateResponse, skipAuth,
1543
- responseSchema, requestId, [...this.middleware, ...middleware]
1545
+ responseSchema, requestId, [...this.middleware, ...middleware],
1546
+ cacheTags, revalidate, connection, updateTag
1544
1547
  )
1545
1548
 
1546
1549
  requestCache.set(dedupeKey, requestPromise)
@@ -1556,7 +1559,8 @@ export class BaseApiClient {
1556
1559
  return this.executeRequestInternal<TData>(
1557
1560
  method, url, body, headers, fetchOptions, timeout, retries,
1558
1561
  retryDelay, retryCondition, validateResponse, skipAuth,
1559
- responseSchema, requestId, [...this.middleware, ...middleware]
1562
+ responseSchema, requestId, [...this.middleware, ...middleware],
1563
+ cacheTags, revalidate, connection, updateTag
1560
1564
  )
1561
1565
  }
1562
1566
 
@@ -1575,7 +1579,11 @@ export class BaseApiClient {
1575
1579
  skipAuth: boolean,
1576
1580
  responseSchema?: z.ZodSchema<TData>,
1577
1581
  requestId?: string,
1578
- middleware: RequestMiddleware[] = []
1582
+ middleware: RequestMiddleware[] = [],
1583
+ cacheTags: string[] = [],
1584
+ revalidate?: number | false,
1585
+ connection?: 'keep-alive' | 'close',
1586
+ updateTag?: (tag: string) => void
1579
1587
  ): Promise<ClientResponse<TData>> {
1580
1588
  const startTime = Date.now()
1581
1589
 
@@ -1662,7 +1670,7 @@ export class BaseApiClient {
1662
1670
 
1663
1671
  if (tagUpdater && typeof window === 'undefined') {
1664
1672
  // Update cache tags on server-side only
1665
- cacheTags.forEach(tag => {
1673
+ cacheTags.forEach((tag: string) => {
1666
1674
  try {
1667
1675
  tagUpdater(tag)
1668
1676
  } catch (error) {
@@ -2549,13 +2557,23 @@ var SchemaGenerator = class {
2549
2557
  const result = [];
2550
2558
  const schemaMap = /* @__PURE__ */ new Map();
2551
2559
  const dependencyGraph = /* @__PURE__ */ new Map();
2560
+ const enumSchemas = [];
2561
+ const otherSchemas = [];
2552
2562
  for (const schema of schemas) {
2563
+ const isEnum = this.isEnumSchema(schema);
2564
+ if (isEnum) {
2565
+ enumSchemas.push(schema);
2566
+ } else {
2567
+ otherSchemas.push(schema);
2568
+ }
2569
+ }
2570
+ for (const schema of [...enumSchemas, ...otherSchemas]) {
2553
2571
  if (!schemaMap.has(schema.name)) {
2554
2572
  schemaMap.set(schema.name, schema);
2555
2573
  dependencyGraph.set(schema.name, /* @__PURE__ */ new Set());
2556
2574
  }
2557
2575
  }
2558
- for (const schema of schemas) {
2576
+ for (const schema of [...enumSchemas, ...otherSchemas]) {
2559
2577
  const dependencies = this.findSchemaDependencies(schema);
2560
2578
  for (const dep of dependencies) {
2561
2579
  if (schemaMap.has(dep)) {
@@ -2585,13 +2603,25 @@ var SchemaGenerator = class {
2585
2603
  visiting.delete(schemaName);
2586
2604
  visited.add(schemaName);
2587
2605
  }, "visit");
2588
- for (const schema of schemaMap.values()) {
2606
+ for (const schema of enumSchemas) {
2607
+ if (!visited.has(schema.name)) {
2608
+ visit(schema.name);
2609
+ }
2610
+ }
2611
+ for (const schema of otherSchemas) {
2589
2612
  if (!visited.has(schema.name)) {
2590
2613
  visit(schema.name);
2591
2614
  }
2592
2615
  }
2593
2616
  return result;
2594
2617
  }
2618
+ isEnumSchema(schema) {
2619
+ if (!schema.schema || !schema.schema._def) {
2620
+ return false;
2621
+ }
2622
+ const def = schema.schema._def;
2623
+ return def.typeName === "ZodEnum";
2624
+ }
2595
2625
  findSchemaDependencies(schema) {
2596
2626
  const dependencies = [];
2597
2627
  const schemaObj = schema.schema;
@@ -2673,7 +2703,7 @@ var SchemaGenerator = class {
2673
2703
  schemaExports.push(...endpointSchemas.exports);
2674
2704
  }
2675
2705
  const validationHelpers = this.generateValidationHelpers();
2676
- const content = this.removeUnusedCode([
2706
+ const contentWithImports = [
2677
2707
  ...imports,
2678
2708
  "",
2679
2709
  "// Generated schemas from OpenAPI specification",
@@ -2683,7 +2713,13 @@ var SchemaGenerator = class {
2683
2713
  "",
2684
2714
  validationHelpers,
2685
2715
  ""
2686
- ].join("\n"));
2716
+ ].join("\n");
2717
+ let content = this.removeUnusedCode(contentWithImports);
2718
+ if (!content.includes('import { z } from "zod"') && !content.includes("import { z } from 'zod'")) {
2719
+ content = `import { z } from "zod"
2720
+
2721
+ ${content}`;
2722
+ }
2687
2723
  return {
2688
2724
  path: "schemas/index.ts",
2689
2725
  content,
@@ -3032,7 +3068,11 @@ export const errorMessages = {
3032
3068
  const lines = content.split("\n");
3033
3069
  const cleanedLines = [];
3034
3070
  const usedIdentifiers = /* @__PURE__ */ new Set();
3071
+ usedIdentifiers.add("z");
3035
3072
  for (const line of lines) {
3073
+ if (line.includes("z.")) {
3074
+ usedIdentifiers.add("z");
3075
+ }
3036
3076
  const assignments = line.match(/(\w+)\s*[:=]/g);
3037
3077
  if (assignments) {
3038
3078
  assignments.forEach((match) => {
@@ -3047,6 +3087,9 @@ export const errorMessages = {
3047
3087
  usedIdentifiers.add(identifier);
3048
3088
  });
3049
3089
  }
3090
+ if (line.includes("z.infer")) {
3091
+ usedIdentifiers.add("z");
3092
+ }
3050
3093
  }
3051
3094
  for (const line of lines) {
3052
3095
  if (line.trim() === "" || line.trim().startsWith("//")) {
@@ -3054,6 +3097,10 @@ export const errorMessages = {
3054
3097
  continue;
3055
3098
  }
3056
3099
  if (line.includes("import") && line.includes("from")) {
3100
+ if (line.includes('from "zod"') || line.includes("from 'zod'")) {
3101
+ cleanedLines.push(line);
3102
+ continue;
3103
+ }
3057
3104
  const importMatch = line.match(/import\s*\{([^}]+)\}/);
3058
3105
  if (importMatch) {
3059
3106
  const imports = importMatch[1].split(",").map((imp) => imp.trim());
@@ -3082,6 +3129,166 @@ var ActionGenerator = class {
3082
3129
  static {
3083
3130
  __name(this, "ActionGenerator");
3084
3131
  }
3132
+ get loggingEnabled() {
3133
+ return this.configuration.dev?.logging !== false;
3134
+ }
3135
+ get validationEnabled() {
3136
+ return this.configuration.dev?.validation !== false;
3137
+ }
3138
+ get commentsEnabled() {
3139
+ return this.configuration.generation?.comments !== false;
3140
+ }
3141
+ get documentationEnabled() {
3142
+ return this.configuration.generation?.documentation !== false;
3143
+ }
3144
+ commentLine(text, indent = 2) {
3145
+ if (!this.commentsEnabled) {
3146
+ return "";
3147
+ }
3148
+ return `${" ".repeat(indent)}// ${text}
3149
+ `;
3150
+ }
3151
+ buildDocBlock(endpoint, featuresDescription) {
3152
+ if (!this.documentationEnabled) {
3153
+ return "";
3154
+ }
3155
+ const summary = endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`;
3156
+ return `/**
3157
+ * ${summary}
3158
+ * @generated from ${endpoint.method} ${endpoint.path}
3159
+ * Features: ${featuresDescription}
3160
+ */
3161
+ `;
3162
+ }
3163
+ buildUtilityFunctions() {
3164
+ const parts = [];
3165
+ if (this.commentsEnabled) {
3166
+ parts.push("// Utility functions for enhanced server actions");
3167
+ }
3168
+ parts.push(`async function getClientInfo() {
3169
+ const headersList = await headers()
3170
+ const userAgent = headersList.get('user-agent') || 'unknown'
3171
+ const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown'
3172
+
3173
+ return { userAgent, ip }
3174
+ }`);
3175
+ if (this.validationEnabled) {
3176
+ parts.push(`async function validateAndSanitizeInput<T>(schema: z.ZodSchema<T>, input: unknown): Promise<T> {
3177
+ try {
3178
+ return await schema.parseAsync(input)
3179
+ } catch (error) {
3180
+ if (error instanceof z.ZodError) {
3181
+ const errorMessages = error.issues.map(issue => {
3182
+ const path = issue.path.length > 0 ? \`\${issue.path.join('.')}: \` : ''
3183
+ return \`\${path}\${issue.message}\`
3184
+ }).join(', ')
3185
+ throw new ActionError(\`Input validation failed: \${errorMessages}\`, 'VALIDATION_ERROR')
3186
+ }
3187
+ throw new ActionError('Invalid input format', 'VALIDATION_ERROR')
3188
+ }
3189
+ }`);
3190
+ }
3191
+ const actionErrorBlock = `${this.commentsEnabled ? "// Enhanced error handling with context\n" : ""}class ActionExecutionError extends ActionError {
3192
+ constructor(
3193
+ message: string,
3194
+ public readonly context: {
3195
+ endpoint: string
3196
+ method: string
3197
+ timestamp: number
3198
+ },
3199
+ public readonly originalError?: unknown
3200
+ ) {
3201
+ super(message, 'EXECUTION_ERROR')
3202
+ }
3203
+ }`;
3204
+ parts.push(actionErrorBlock);
3205
+ if (this.loggingEnabled) {
3206
+ const loggingHeader = this.commentsEnabled ? "// Logging utility for server actions\n" : "";
3207
+ parts.push(`${loggingHeader}async function logActionExecution(
3208
+ action: string,
3209
+ success: boolean,
3210
+ duration: number,
3211
+ context?: Record<string, any>
3212
+ ) {
3213
+ if (process.env.NODE_ENV === 'development') {
3214
+ console.log(\`[ACTION] \${action} - \${success ? 'SUCCESS' : 'FAILED'} (\${duration}ms)\`, context)
3215
+ }
3216
+
3217
+ ${this.commentsEnabled ? " // In production, send to your logging service\n // await analytics.track('server_action_executed', { action, success, duration, ...context })" : ""}
3218
+ }`);
3219
+ }
3220
+ return parts.join("\n\n");
3221
+ }
3222
+ buildCombinedValidationSnippet(operationName, schemaName) {
3223
+ if (this.validationEnabled) {
3224
+ return `
3225
+ ${this.commentLine("Validate and sanitize input payload", 6)} const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3226
+ const validatedBody = body
3227
+ const validatedParams = params as z.infer<typeof ${operationName}ParamsSchema>`;
3228
+ }
3229
+ return `
3230
+ ${this.commentLine("Runtime validation disabled - trusting provided payload", 6)} const { body, params } = parsedInput as {
3231
+ body: z.infer<typeof ${operationName}RequestSchema>
3232
+ params: z.infer<typeof ${operationName}ParamsSchema>
3233
+ }
3234
+ const validatedBody = body
3235
+ const validatedParams = params`;
3236
+ }
3237
+ buildBodyValidationSnippet(schemaName) {
3238
+ if (schemaName === "z.void()") {
3239
+ return "";
3240
+ }
3241
+ if (this.validationEnabled) {
3242
+ return `
3243
+ ${this.commentLine("Validate and sanitize request body", 6)} const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3244
+ }
3245
+ return `
3246
+ ${this.commentLine("Runtime validation disabled - trusting request body", 6)} const validatedBody = parsedInput as z.infer<typeof ${schemaName}>`;
3247
+ }
3248
+ buildParamsValidationSnippet(operationName, schemaName) {
3249
+ if (schemaName === "z.void()") {
3250
+ return "";
3251
+ }
3252
+ if (this.validationEnabled) {
3253
+ return `
3254
+ ${this.commentLine("Validate and sanitize parameters", 6)} const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as z.infer<typeof ${operationName}ParamsSchema>`;
3255
+ }
3256
+ return `
3257
+ ${this.commentLine("Runtime validation disabled - trusting parameters", 6)} const validatedParams = parsedInput as z.infer<typeof ${operationName}ParamsSchema>`;
3258
+ }
3259
+ buildSuccessLoggingBlock(actionName, endpoint, indent, isStreaming = false) {
3260
+ if (!this.loggingEnabled) {
3261
+ return "";
3262
+ }
3263
+ if (isStreaming) {
3264
+ return `
3265
+ ${this.commentLine("Background tasks (Next.js 15 feature)", indent)}${" ".repeat(indent)}after(async () => {
3266
+ ${this.commentLine("Perform background tasks after response is sent", indent + 2)}${" ".repeat(indent + 2)}await logActionExecution('${actionName}', true, Date.now() - startTime, {
3267
+ ${" ".repeat(indent + 4)}method: '${endpoint.method}',
3268
+ ${" ".repeat(indent + 4)}path: '${endpoint.path}'
3269
+ ${" ".repeat(indent + 2)}})
3270
+ ${" ".repeat(indent)}})`;
3271
+ }
3272
+ return `
3273
+ ${this.commentLine("Log successful execution", indent)}${" ".repeat(indent)}const duration = Date.now() - startTime
3274
+ ${" ".repeat(indent)}await logActionExecution('${actionName}', true, duration, {
3275
+ ${" ".repeat(indent + 2)}method: '${endpoint.method}',
3276
+ ${" ".repeat(indent + 2)}path: '${endpoint.path}'
3277
+ ${" ".repeat(indent)}})`;
3278
+ }
3279
+ buildErrorLoggingBlock(actionName, endpoint, indent) {
3280
+ if (!this.loggingEnabled) {
3281
+ return "";
3282
+ }
3283
+ return `
3284
+ ${" ".repeat(indent)}const duration = Date.now() - startTime
3285
+
3286
+ ${this.commentLine("Enhanced error logging", indent)}${" ".repeat(indent)}await logActionExecution('${actionName}', false, duration, {
3287
+ ${" ".repeat(indent + 2)}method: '${endpoint.method}',
3288
+ ${" ".repeat(indent + 2)}path: '${endpoint.path}',
3289
+ ${" ".repeat(indent + 2)}error: error instanceof Error ? error.message : 'Unknown error'
3290
+ ${" ".repeat(indent)}})`;
3291
+ }
3085
3292
  buildImportPath(relativePath) {
3086
3293
  const outputDirectory = this.configuration.outputDir || "generated";
3087
3294
  const cleanPath = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
@@ -3134,7 +3341,7 @@ var ActionGenerator = class {
3134
3341
  ...hasStreaming ? ["import { after } from 'next/server'"] : [],
3135
3342
  "import { headers } from 'next/headers'",
3136
3343
  `import { apiClient } from '${clientImport}'`,
3137
- `import { ${usesAuth || hasRateLimit ? "authActionClient" : "actionClientWithMeta"}, ActionError } from '${safeActionImport}'`,
3344
+ `import { actionClientWithMeta${usesAuth || hasRateLimit ? ", authActionClient" : ""}, ActionError } from '${safeActionImport}'`,
3138
3345
  ...schemaImportsString ? [`import {
3139
3346
  ${schemaImportsString}
3140
3347
  } from '${schemasImport}'`] : [],
@@ -3146,8 +3353,7 @@ var ActionGenerator = class {
3146
3353
  ] : []
3147
3354
  ].filter(Boolean).join("\n");
3148
3355
  const rateLimitSetup = hasRateLimit ? `
3149
- // Rate limiting setup
3150
- const redis = new Redis({
3356
+ ${this.commentsEnabled ? "// Rate limiting setup\n" : ""}const redis = new Redis({
3151
3357
  url: process.env.UPSTASH_REDIS_REST_URL!,
3152
3358
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
3153
3359
  })
@@ -3157,60 +3363,7 @@ const ratelimit = new Ratelimit({
3157
3363
  limiter: Ratelimit.slidingWindow(10, '1 m'),
3158
3364
  analytics: true,
3159
3365
  })` : "";
3160
- const utilityFunctions = `
3161
- // Utility functions for enhanced server actions
3162
- async function getClientInfo() {
3163
- const headersList = await headers()
3164
- const userAgent = headersList.get('user-agent') || 'unknown'
3165
- const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown'
3166
-
3167
- return { userAgent, ip }
3168
- }
3169
-
3170
- async function validateAndSanitizeInput<T>(schema: z.ZodSchema<T>, input: unknown): Promise<T> {
3171
- try {
3172
- return await schema.parseAsync(input)
3173
- } catch (error) {
3174
- if (error instanceof z.ZodError) {
3175
- const errorMessages = error.issues.map(issue => {
3176
- const path = issue.path.length > 0 ? \`\${issue.path.join('.')}: \` : ''
3177
- return \`\${path}\${issue.message}\`
3178
- }).join(', ')
3179
- throw new ActionError(\`Input validation failed: \${errorMessages}\`, 'VALIDATION_ERROR')
3180
- }
3181
- throw new ActionError('Invalid input format', 'VALIDATION_ERROR')
3182
- }
3183
- }
3184
-
3185
- // Enhanced error handling with context
3186
- class ActionExecutionError extends ActionError {
3187
- constructor(
3188
- message: string,
3189
- public readonly context: {
3190
- endpoint: string
3191
- method: string
3192
- timestamp: number
3193
- },
3194
- public readonly originalError?: unknown
3195
- ) {
3196
- super(message, 'EXECUTION_ERROR')
3197
- }
3198
- }
3199
-
3200
- // Logging utility for server actions
3201
- async function logActionExecution(
3202
- action: string,
3203
- success: boolean,
3204
- duration: number,
3205
- context?: Record<string, any>
3206
- ) {
3207
- if (process.env.NODE_ENV === 'development') {
3208
- console.log(\`[ACTION] \${action} - \${success ? 'SUCCESS' : 'FAILED'} (\${duration}ms)\`, context)
3209
- }
3210
-
3211
- // In production, send to your logging service
3212
- // await analytics.track('server_action_executed', { action, success, duration, ...context })
3213
- }`;
3366
+ const utilityFunctions = this.buildUtilityFunctions();
3214
3367
  const content = `${imports}
3215
3368
  ${rateLimitSetup}
3216
3369
  ${utilityFunctions}
@@ -3227,8 +3380,8 @@ ${actions}`;
3227
3380
  }
3228
3381
  });
3229
3382
  }
3230
- const indexContent = `// Auto-generated actions index
3231
- ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier(tag)}'`).join("\n")}`;
3383
+ const indexHeader = this.commentsEnabled ? "// Auto-generated actions index\n" : "";
3384
+ const indexContent = `${indexHeader}${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier(tag)}'`).join("\n")}`;
3232
3385
  generatedFiles.push({
3233
3386
  path: "actions/index.ts",
3234
3387
  content: indexContent,
@@ -3277,54 +3430,48 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3277
3430
  body: ${operationName}RequestSchema,
3278
3431
  params: ${operationName}ParamsSchema
3279
3432
  })`;
3280
- parameterProcessing = `
3281
- // Validate and sanitize input
3282
- const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3283
- const validatedBody = body
3284
- const validatedParams = params as any`;
3433
+ parameterProcessing = this.buildCombinedValidationSnippet(operationName, schemaName);
3285
3434
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, true, true);
3286
3435
  } else if (hasRequestBody) {
3287
3436
  schemaName = `${operationName}RequestSchema`;
3288
- parameterProcessing = `
3289
- // Validate and sanitize request body
3290
- const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3437
+ parameterProcessing = this.buildBodyValidationSnippet(schemaName);
3291
3438
  requestOptionsParams = `body: validatedBody`;
3292
3439
  } else if (hasAnyParams) {
3293
3440
  schemaName = `${operationName}ParamsSchema`;
3294
- parameterProcessing = `
3295
- // Validate and sanitize parameters
3296
- const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as any`;
3441
+ parameterProcessing = this.buildParamsValidationSnippet(operationName, schemaName);
3297
3442
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, false, true);
3298
3443
  } else {
3299
3444
  schemaName = "z.void()";
3300
3445
  parameterProcessing = "";
3301
3446
  requestOptionsParams = "";
3302
3447
  }
3448
+ const parsedInputType = hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void";
3449
+ const ctxType = requiresAuth || hasRateLimit ? "{ user?: any; ratelimit?: { remaining: number } }" : "any";
3450
+ const ctxDeclaration = requiresAuth || hasRateLimit ? `ctx: ${ctxType}` : "ctx?: any";
3451
+ const actionArgsType = `{ parsedInput: ${parsedInputType}; ${ctxDeclaration} }`;
3303
3452
  const clientName = requiresAuth || hasRateLimit ? "authActionClient" : "actionClientWithMeta";
3304
3453
  const rateLimitCode = hasRateLimit ? `
3305
- // Rate limiting
3306
- const { userAgent, ip } = await getClientInfo()
3307
- const identifier = \`\${ip}-\${userAgent}\`
3308
- const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3309
-
3310
- if (!success) {
3311
- throw new ActionError(
3312
- \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3313
- 'RATE_LIMIT_EXCEEDED'
3314
- )
3315
- }` : "";
3454
+ ${this.commentLine("Rate limiting", 6)} const { userAgent, ip } = await getClientInfo()
3455
+ const identifier = \`\${ip}-\${userAgent}\`
3456
+ const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3457
+
3458
+ if (!success) {
3459
+ throw new ActionError(
3460
+ \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3461
+ 'RATE_LIMIT_EXCEEDED'
3462
+ )
3463
+ }` : "";
3316
3464
  const revalidationCode = revalidationTags.length > 0 ? revalidationTags.map(
3317
- (tag) => ` updateTag('${tag}')
3318
- console.log('Updated tag: ${tag}')`
3465
+ (tag) => ` updateTag('${tag}')${this.loggingEnabled ? `
3466
+ console.log('Updated tag: ${tag}')` : ""}`
3319
3467
  ).join("\n") : "";
3320
3468
  const kebabActionName = actionName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
3321
3469
  if (isQuery) {
3322
- return `/**
3323
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3324
- * @generated from ${endpoint.method} ${endpoint.path}
3325
- * Features: React cache, input validation, error handling
3326
- */
3327
- export const ${actionName} = cache(
3470
+ const docBlock = this.buildDocBlock(
3471
+ endpoint,
3472
+ "React cache, input validation, error handling"
3473
+ );
3474
+ return `${docBlock}export const ${actionName} = cache(
3328
3475
  ${clientName}
3329
3476
  .metadata({
3330
3477
  name: "${kebabActionName}",
@@ -3332,7 +3479,7 @@ export const ${actionName} = cache(
3332
3479
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3333
3480
  })
3334
3481
  .schema(${schemaName})
3335
- .action(async ({ parsedInput, ctx }) => {
3482
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3336
3483
  const startTime = Date.now()
3337
3484
 
3338
3485
  try {${rateLimitCode}${parameterProcessing}
@@ -3347,23 +3494,11 @@ export const ${actionName} = cache(
3347
3494
  }
3348
3495
  })
3349
3496
 
3350
- // Log successful execution
3351
- const duration = Date.now() - startTime
3352
- await logActionExecution('${actionName}', true, duration, {
3353
- method: '${endpoint.method}',
3354
- path: '${endpoint.path}'
3355
- })
3497
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 8)}
3356
3498
 
3357
3499
  return response.data
3358
3500
  } catch (error) {
3359
- const duration = Date.now() - startTime
3360
-
3361
- // Enhanced error logging
3362
- await logActionExecution('${actionName}', false, duration, {
3363
- method: '${endpoint.method}',
3364
- path: '${endpoint.path}',
3365
- error: error instanceof Error ? error.message : 'Unknown error'
3366
- })
3501
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 8)}
3367
3502
 
3368
3503
  // Throw enhanced error with context
3369
3504
  throw new ActionExecutionError(
@@ -3380,50 +3515,37 @@ export const ${actionName} = cache(
3380
3515
  )`;
3381
3516
  }
3382
3517
  const redirectCode = `
3383
- // Handle potential redirects based on response
3384
- if (response.status === 201 && response.headers.get('location')) {
3518
+ ${this.commentLine("Handle potential redirects based on response", 8)} if (response.status === 201 && response.headers.get('location')) {
3385
3519
  const location = response.headers.get('location')!
3386
3520
  redirect(location)
3387
3521
  }`;
3388
3522
  const streamingCode = `
3389
- // Handle streaming responses
3390
- if (response.headers.get('content-type')?.includes('text/stream')) {
3391
- // Process streaming response
3392
- return response.data
3523
+ ${this.commentLine("Handle streaming responses", 8)} if (response.headers.get('content-type')?.includes('text/stream')) {
3524
+ ${this.commentLine("Process streaming response", 10)} return response.data
3393
3525
  }`;
3394
3526
  const fileUploadCode = hasFileUpload ? uploadStrategy === "external" && uploadProvider === "vercel-blob" ? `
3395
- // Handle file uploads with Vercel Blob
3396
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3527
+ ${this.commentLine("Handle file uploads with Vercel Blob", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3397
3528
  const file = (validatedBody as any).file as File
3398
3529
  const blob = await put(file.name, file, { access: 'public' })
3399
3530
  Object.assign(validatedBody, { fileUrl: blob.url })
3400
3531
  }` : uploadStrategy === "external" && uploadProvider === "uploadthing" ? `
3401
- // Handle file uploads with UploadThing
3402
- // Note: UploadThing requires a separate route handler setup
3403
- // See: https://docs.uploadthing.com/getting-started/appdir
3404
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3405
- // UploadThing file handling should be done via their route handler
3406
- // This is a placeholder - implement according to UploadThing docs
3407
- }` : `
3408
- // Handle file uploads with standard FormData
3409
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3410
- // Standard file upload handling
3411
- const file = (validatedBody as any).file as File
3412
- // Process file with compression and validation if enabled
3413
- }` : "";
3414
- return `/**
3415
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3416
- * @generated from ${endpoint.method} ${endpoint.path}
3417
- * Features: Input validation, revalidation, error handling
3418
- */
3419
- export const ${actionName} = ${clientName}
3532
+ ${this.commentLine("Handle file uploads with UploadThing", 8)}${this.commentLine("UploadThing requires a separate route handler setup", 8)}${this.commentLine("See: https://docs.uploadthing.com/getting-started/appdir", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3533
+ ${this.commentLine("UploadThing file handling should be done via their route handler", 10)}${this.commentLine("This is a placeholder - implement according to UploadThing docs", 10)} }` : `
3534
+ ${this.commentLine("Handle file uploads with standard FormData", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3535
+ ${this.commentLine("Standard file upload handling", 10)} const file = (validatedBody as any).file as File
3536
+ ${this.commentLine("Process file with compression and validation if enabled", 10)} }` : "";
3537
+ const mutationDocBlock = this.buildDocBlock(
3538
+ endpoint,
3539
+ "Input validation, revalidation, error handling"
3540
+ );
3541
+ return `${mutationDocBlock}export const ${actionName} = ${clientName}
3420
3542
  .metadata({
3421
3543
  name: "${kebabActionName}",
3422
3544
  requiresAuth: ${requiresAuth}${hasRateLimit ? `,
3423
3545
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3424
3546
  })
3425
3547
  .schema(${schemaName})
3426
- .action(async ({ parsedInput, ctx }) => {
3548
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3427
3549
  const startTime = Date.now()
3428
3550
 
3429
3551
  try {${rateLimitCode}${parameterProcessing}${fileUploadCode}
@@ -3438,33 +3560,13 @@ export const ${actionName} = ${clientName}
3438
3560
  }
3439
3561
  })${streamingCode}${redirectCode}
3440
3562
 
3441
- // Revalidate cache after successful mutation
3442
- ${revalidationCode}
3443
-
3444
- // Background tasks (Next.js 15 feature)
3445
- ${isStreaming ? `after(async () => {
3446
- // Perform background tasks after response is sent
3447
- await logActionExecution('${actionName}', true, Date.now() - startTime, {
3448
- method: '${endpoint.method}',
3449
- path: '${endpoint.path}'
3450
- })
3451
- })` : `// Log successful execution
3452
- const duration = Date.now() - startTime
3453
- await logActionExecution('${actionName}', true, duration, {
3454
- method: '${endpoint.method}',
3455
- path: '${endpoint.path}'
3456
- })`}
3563
+ ${revalidationCode ? `${this.commentLine("Revalidate cache after successful mutation", 6)}${revalidationCode}
3564
+ ` : ""}
3565
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 6, isStreaming)}
3457
3566
 
3458
3567
  return response.data
3459
3568
  } catch (error) {
3460
- const duration = Date.now() - startTime
3461
-
3462
- // Enhanced error logging
3463
- await logActionExecution('${actionName}', false, duration, {
3464
- method: '${endpoint.method}',
3465
- path: '${endpoint.path}',
3466
- error: error instanceof Error ? error.message : 'Unknown error'
3467
- })
3569
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 6)}
3468
3570
 
3469
3571
  // Throw enhanced error with context
3470
3572
  throw new ActionExecutionError(
@@ -3623,7 +3725,6 @@ var HookGenerator = class {
3623
3725
  if (hasInfiniteQueries) reactQueryImports.push("useInfiniteQuery");
3624
3726
  const reactImports = [];
3625
3727
  if (hasMutations) reactImports.push("useOptimistic", "useTransition");
3626
- if (hasQueries) reactImports.push("useCallback");
3627
3728
  const imports = [
3628
3729
  "'use client'",
3629
3730
  "",
@@ -3735,19 +3836,19 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3735
3836
  * @returns useQuery result with data of type ${returnType}
3736
3837
  */
3737
3838
  export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join(", ")}, ` : ""}options?: ${optionsType}) {
3738
- const [searchParams, setSearchParams] = useQueryStates(searchParamsParser)
3839
+ const [searchParams] = useQueryStates(searchParamsParser)
3739
3840
  const { initialData, ...restOptions } = options ?? {}
3740
3841
 
3741
3842
  return useQuery({
3742
3843
  queryKey: [...${queryKey}, searchParams],
3743
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3844
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3744
3845
  try {
3745
3846
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams }")})
3746
3847
  return result
3747
3848
  } catch (error) {
3748
3849
  handleActionError(error)
3749
3850
  }
3750
- }, [searchParams]),
3851
+ },
3751
3852
  staleTime: ${staleTime},
3752
3853
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3753
3854
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3757,14 +3858,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3757
3858
  refetchInterval: options?.refetchInterval, // Optional polling interval
3758
3859
  initialDataUpdatedAt: initialData ? Date.now() : undefined,
3759
3860
  // React Query v5: placeholderData replaces keepPreviousData
3760
- placeholderData: (previousData) => previousData,
3761
- retry: (failureCount, error) => {
3861
+ placeholderData: (previousData: ${returnType} | undefined) => previousData,
3862
+ retry: (failureCount: number, error: Error) => {
3762
3863
  // Don't retry on 4xx errors (client errors)
3763
3864
  if (error instanceof Error && error.message.includes('4')) return false
3764
3865
  // Retry up to 3 times for network/server errors
3765
3866
  return failureCount < 3
3766
3867
  },
3767
- initialData: initialData as any,
3868
+ initialData: initialData as ${returnType} | undefined,
3768
3869
  ...restOptions
3769
3870
  })
3770
3871
  }
@@ -3779,15 +3880,15 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3779
3880
 
3780
3881
  return useInfiniteQuery({
3781
3882
  queryKey: [...${queryKey}, 'infinite', searchParams],
3782
- queryFn: useCallback(async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3883
+ queryFn: async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3783
3884
  try {
3784
3885
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams, page: pageParam, limit: searchParams.limit }")})
3785
3886
  return result
3786
3887
  } catch (error) {
3787
3888
  handleActionError(error)
3788
3889
  }
3789
- }, [searchParams]),
3790
- getNextPageParam: (lastPage: any, allPages) => {
3890
+ },
3891
+ getNextPageParam: (lastPage: ${returnType}, allPages: ${returnType}[]) => {
3791
3892
  if (lastPage?.hasMore || (Array.isArray(lastPage) && lastPage.length === searchParams.limit)) {
3792
3893
  return allPages.length + 1
3793
3894
  }
@@ -3800,9 +3901,9 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3800
3901
  refetchOnReconnect: true,
3801
3902
  refetchOnMount: 'always',
3802
3903
  initialDataUpdatedAt: initialData ? Date.now() : undefined,
3803
- placeholderData: (previousData) => previousData,
3904
+ placeholderData: (previousData: ${returnType} | undefined) => previousData,
3804
3905
  retry: 3,
3805
- initialData: initialData as any,
3906
+ initialData: initialData as ${returnType} | undefined,
3806
3907
  ...restOptions
3807
3908
  })
3808
3909
  }
@@ -3816,12 +3917,12 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3816
3917
 
3817
3918
  return useSuspenseQuery({
3818
3919
  queryKey: ${queryKey},
3819
- queryFn: useCallback(async () => {
3920
+ queryFn: async () => {
3820
3921
  const result = await ${actionName}(${actionCallParams})
3821
3922
  return result
3822
- }, []),
3923
+ },
3823
3924
  staleTime: ${staleTime},
3824
- initialData: initialData as any,
3925
+ initialData: initialData as ${returnType} | undefined,
3825
3926
  ...restOptions
3826
3927
  })
3827
3928
  }`;
@@ -3836,14 +3937,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3836
3937
 
3837
3938
  return useQuery({
3838
3939
  queryKey: ${queryKey},
3839
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3940
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3840
3941
  try {
3841
3942
  const result = await ${actionName}(${actionCallParams})
3842
3943
  return result
3843
3944
  } catch (error) {
3844
3945
  handleActionError(error)
3845
3946
  }
3846
- }, []),
3947
+ },
3847
3948
  staleTime: ${staleTime},
3848
3949
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3849
3950
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3853,14 +3954,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3853
3954
  refetchInterval: options?.refetchInterval, // Optional polling interval
3854
3955
  initialDataUpdatedAt: initialData ? Date.now() : undefined,
3855
3956
  // React Query v5: placeholderData replaces keepPreviousData
3856
- placeholderData: (previousData) => previousData,
3857
- retry: (failureCount, error) => {
3957
+ placeholderData: (previousData: ${returnType} | undefined) => previousData,
3958
+ retry: (failureCount: number, error: Error) => {
3858
3959
  // Don't retry on 4xx errors (client errors)
3859
3960
  if (error instanceof Error && error.message.includes('4')) return false
3860
3961
  // Retry up to 3 times for network/server errors
3861
3962
  return failureCount < 3
3862
3963
  },
3863
- initialData: initialData as any,
3964
+ initialData: initialData as ${returnType} | undefined,
3864
3965
  ...restOptions
3865
3966
  })
3866
3967
  }
@@ -3874,12 +3975,12 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3874
3975
 
3875
3976
  return useSuspenseQuery({
3876
3977
  queryKey: ${queryKey},
3877
- queryFn: useCallback(async () => {
3978
+ queryFn: async () => {
3878
3979
  const result = await ${actionName}(${actionCallParams})
3879
3980
  return result
3880
- }, []),
3981
+ },
3881
3982
  staleTime: ${staleTime},
3882
- initialData: initialData as any,
3983
+ initialData: initialData as ${returnType} | undefined,
3883
3984
  ...restOptions
3884
3985
  })
3885
3986
  }`;
@@ -3910,8 +4011,6 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3910
4011
  const relatedQueries = this.findRelatedQueries(endpoint, context);
3911
4012
  const invalidationCode = this.buildInvalidationCode(endpoint, relatedQueries, invalidationTags);
3912
4013
  const cancelQueriesCode = this.buildCancelQueriesCode(endpoint, relatedQueries);
3913
- const snapshotCode = this.buildSnapshotCode(endpoint, relatedQueries);
3914
- const rollbackCode = this.buildRollbackCode(endpoint, relatedQueries);
3915
4014
  return `/**
3916
4015
  * Optimized mutation hook for ${endpoint.method} ${endpoint.path}
3917
4016
  * Features: Optimistic updates, smart invalidation, error handling
@@ -3932,24 +4031,22 @@ export function ${hookName}(options?: {
3932
4031
  mutationFn: async (variables: ${inputType}): Promise<${outputType}> => {
3933
4032
  try {
3934
4033
  const result = await ${actionName}(${variablesParam === "undefined" ? "" : "variables"})
3935
- return result.data || ({} as any)
4034
+ return (result ?? ({} as ${outputType}))
3936
4035
  } catch (error) {
3937
4036
  handleActionError(error)
3938
4037
  }
3939
4038
  },
3940
4039
 
3941
- onMutate: async (variables) => {
4040
+ onMutate: async (variables: ${inputType}) => {
3942
4041
  ${cancelQueriesCode}
3943
-
3944
- ${snapshotCode}
3945
-
4042
+
3946
4043
  // Optimistic update (if provided)
3947
4044
  if (options?.optimisticUpdate) {
3948
4045
  const optimisticValue = options.optimisticUpdate(variables)
3949
4046
  setOptimisticData(optimisticValue)
3950
4047
  }
3951
4048
 
3952
- return { ${this.getSnapshotReturnNames(relatedQueries)} }
4049
+ return {}
3953
4050
  },
3954
4051
 
3955
4052
  onSuccess: (data, variables) => {
@@ -3958,17 +4055,11 @@ ${snapshotCode}
3958
4055
  toast.success('${this.getSuccessMessage(endpoint)}')
3959
4056
  }
3960
4057
 
3961
- // Invalidate and refetch related queries
3962
- ${invalidationCode}
3963
-
3964
4058
  // Custom success handler
3965
4059
  options?.onSuccess?.(data, variables)
3966
4060
  },
3967
4061
 
3968
- onError: (error, variables, context) => {
3969
- // Rollback optimistic update
3970
- ${rollbackCode}
3971
-
4062
+ onError: (error: Error, variables: ${inputType}) => {
3972
4063
  // Show error toast
3973
4064
  if (options?.showToast !== false) {
3974
4065
  toast.error(error.message || '${this.getErrorMessage(endpoint)}')
@@ -3978,7 +4069,7 @@ ${rollbackCode}
3978
4069
  options?.onError?.(error as Error, variables)
3979
4070
  },
3980
4071
 
3981
- onSettled: () => {
4072
+ onSettled: async () => {
3982
4073
  // Always refetch after error or success
3983
4074
  ${invalidationCode}
3984
4075
  }
@@ -4004,103 +4095,81 @@ ${invalidationCode}
4004
4095
  return [];
4005
4096
  }
4006
4097
  const relatedQueries = [];
4007
- const mutationPath = endpoint.path;
4098
+ const seen = /* @__PURE__ */ new Set();
4008
4099
  const mutationTag = endpoint.tags[0];
4009
- const pathSegments = mutationPath.split("/").filter(Boolean);
4100
+ const mutationSegments = this.getResourceSegments(endpoint.path);
4101
+ const mutationResource = mutationSegments[0];
4010
4102
  context.schema.endpoints.forEach((queryEndpoint) => {
4011
4103
  if (queryEndpoint.method !== "GET") return;
4012
- const queryPath = queryEndpoint.path;
4104
+ const queryId = queryEndpoint.operationId || queryEndpoint.id;
4105
+ if (seen.has(queryId)) return;
4013
4106
  const queryTag = queryEndpoint.tags[0];
4014
- if (queryTag === mutationTag) {
4107
+ const querySegments = this.getResourceSegments(queryEndpoint.path);
4108
+ const queryResource = querySegments[0];
4109
+ if (mutationTag && queryTag === mutationTag) {
4015
4110
  relatedQueries.push(queryEndpoint);
4111
+ seen.add(queryId);
4016
4112
  return;
4017
4113
  }
4018
- const querySegments = queryPath.split("/").filter(Boolean);
4019
- if (pathSegments.length >= 2 && querySegments.length >= 2) {
4020
- const baseMatch = pathSegments.slice(0, 2).join("/") === querySegments.slice(0, 2).join("/");
4021
- if (baseMatch) {
4022
- relatedQueries.push(queryEndpoint);
4023
- }
4114
+ if (mutationResource && queryResource && mutationResource === queryResource) {
4115
+ relatedQueries.push(queryEndpoint);
4116
+ seen.add(queryId);
4024
4117
  }
4025
4118
  });
4026
4119
  return relatedQueries;
4027
4120
  }
4121
+ getResourceSegments(path3) {
4122
+ return path3.split("/").filter(Boolean).filter((segment) => segment.toLowerCase() !== "api" && !/^v\d+/i.test(segment));
4123
+ }
4028
4124
  /**
4029
4125
  * Build invalidation code for related queries
4030
4126
  */
4031
4127
  buildInvalidationCode(endpoint, relatedQueries, invalidationTags) {
4032
- const invalidations = [];
4128
+ const invalidations = /* @__PURE__ */ new Set();
4033
4129
  relatedQueries.forEach((queryEndpoint) => {
4034
- const queryKey = this.generateQueryKey(queryEndpoint);
4035
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
4130
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4131
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4036
4132
  });
4037
4133
  invalidationTags.forEach((tag) => {
4038
- if (!invalidations.some((inv) => inv.includes(`'${tag}'`))) {
4039
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4040
- }
4134
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4041
4135
  });
4042
- if (invalidations.length === 0) {
4136
+ if (invalidations.size === 0) {
4043
4137
  const inferredKey = this.inferQueryKeyFromPath(endpoint);
4044
4138
  if (inferredKey) {
4045
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4139
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4046
4140
  }
4047
4141
  }
4048
- return invalidations.length > 0 ? invalidations.join("\n") : " // No specific cache invalidation needed";
4142
+ if (invalidations.size === 0) {
4143
+ return " // No specific cache invalidation needed";
4144
+ }
4145
+ if (invalidations.size === 1) {
4146
+ const [statement] = Array.from(invalidations);
4147
+ return ` await ${statement}`;
4148
+ }
4149
+ return ` await Promise.all([
4150
+ ${Array.from(invalidations).join(",\n ")}
4151
+ ])`;
4049
4152
  }
4050
4153
  /**
4051
4154
  * Build cancel queries code
4155
+ * Uses query key patterns to cancel all related queries regardless of parameters
4052
4156
  */
4053
4157
  buildCancelQueriesCode(endpoint, relatedQueries) {
4054
- const cancels = [];
4158
+ const cancels = /* @__PURE__ */ new Set();
4055
4159
  relatedQueries.forEach((queryEndpoint) => {
4056
- const queryKey = this.generateQueryKey(queryEndpoint);
4057
- cancels.push(` await queryClient.cancelQueries({ queryKey: ${queryKey} })`);
4058
- });
4059
- return cancels.length > 0 ? cancels.join("\n") : " // No queries to cancel";
4060
- }
4061
- /**
4062
- * Build snapshot code for rollback
4063
- */
4064
- buildSnapshotCode(endpoint, relatedQueries) {
4065
- if (relatedQueries.length === 0) {
4066
- return " // No queries to snapshot";
4067
- }
4068
- const snapshots = [];
4069
- relatedQueries.forEach((queryEndpoint, index) => {
4070
- const queryKey = this.generateQueryKey(queryEndpoint);
4071
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4072
- snapshots.push(` const ${varName} = queryClient.getQueryData(${queryKey})`);
4160
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4161
+ cancels.add(`queryClient.cancelQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4073
4162
  });
4074
- return snapshots.join("\n");
4075
- }
4076
- /**
4077
- * Build rollback code
4078
- */
4079
- buildRollbackCode(endpoint, relatedQueries) {
4080
- if (relatedQueries.length === 0) {
4081
- return " // No queries to rollback";
4163
+ if (cancels.size === 0) {
4164
+ return " // No queries to cancel";
4082
4165
  }
4083
- const rollbacks = [];
4084
- relatedQueries.forEach((queryEndpoint) => {
4085
- const queryKey = this.generateQueryKey(queryEndpoint);
4086
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4087
- rollbacks.push(` if (context?.${varName}) {
4088
- queryClient.setQueryData(${queryKey}, context.${varName})
4089
- }`);
4090
- });
4091
- return rollbacks.join("\n");
4092
- }
4093
- /**
4094
- * Get snapshot return names for context
4095
- */
4096
- getSnapshotReturnNames(relatedQueries) {
4097
- if (relatedQueries.length === 0) {
4098
- return "previousData: null";
4166
+ if (cancels.size === 1) {
4167
+ const [statement] = Array.from(cancels);
4168
+ return ` await ${statement}`;
4099
4169
  }
4100
- return relatedQueries.map((queryEndpoint) => {
4101
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4102
- return `${varName}`;
4103
- }).join(", ");
4170
+ return ` await Promise.all([
4171
+ ${Array.from(cancels).join(",\n ")}
4172
+ ])`;
4104
4173
  }
4105
4174
  /**
4106
4175
  * Infer query key from mutation path
@@ -4258,8 +4327,8 @@ export function useBridgeQuery<TData = unknown, TError = Error>(
4258
4327
  refetchOnWindowFocus: true,
4259
4328
  refetchOnReconnect: true,
4260
4329
  refetchOnMount: 'always',
4261
- placeholderData: (previousData) => previousData,
4262
- retry: (failureCount, error) => {
4330
+ placeholderData: (previousData: TData | undefined) => previousData,
4331
+ retry: (failureCount: number, error: Error) => {
4263
4332
  if (error instanceof Error && error.message.includes('4')) return false
4264
4333
  return failureCount < 3
4265
4334
  },
@@ -4284,13 +4353,13 @@ export function useBridgeInfiniteQuery<TData = unknown, TError = Error>(
4284
4353
  refetchOnWindowFocus: true,
4285
4354
  refetchOnReconnect: true,
4286
4355
  refetchOnMount: 'always',
4287
- placeholderData: (previousData) => previousData,
4288
- retry: (failureCount, error) => {
4356
+ placeholderData: (previousData: TData | undefined) => previousData,
4357
+ retry: (failureCount: number, error: Error) => {
4289
4358
  if (error instanceof Error && error.message.includes('4')) return false
4290
4359
  return failureCount < 3
4291
4360
  },
4292
4361
  ...options,
4293
- } as any)
4362
+ })
4294
4363
  }
4295
4364
 
4296
4365
  /**
@@ -4307,7 +4376,7 @@ export function useBridgeSuspenseQuery<TData = unknown, TError = Error>(
4307
4376
  queryFn: queryFn as QueryFunction<TData, QueryKey>,
4308
4377
  staleTime: 5 * 60 * 1000,
4309
4378
  gcTime: 10 * 60 * 1000,
4310
- retry: (failureCount, error) => {
4379
+ retry: (failureCount: number, error: Error) => {
4311
4380
  if (error instanceof Error && error.message.includes('4')) return false
4312
4381
  return failureCount < 3
4313
4382
  },
@@ -7522,9 +7591,6 @@ var UploadGenerator = class {
7522
7591
  const compressionFormats = uploads?.compression?.formats || ["gzip", "webp"];
7523
7592
  const content = `'use client'
7524
7593
 
7525
- import { useMutation, useQueryClient } from '@tanstack/react-query'
7526
- import { toast } from 'sonner'
7527
-
7528
7594
  /**
7529
7595
  * Upload configuration
7530
7596
  */
@@ -7704,7 +7770,7 @@ export function createUploadFormData(
7704
7770
  "FileValidationResult",
7705
7771
  "UploadProgress"
7706
7772
  ],
7707
- imports: ["@tanstack/react-query", "sonner"],
7773
+ imports: [],
7708
7774
  dependencies: []
7709
7775
  }
7710
7776
  };
@@ -7810,16 +7876,18 @@ async function uploadToS3(
7810
7876
  xhr.send(formData)
7811
7877
  })
7812
7878
  }
7813
-
7879
+ ` : "";
7880
+ const needsBackendHelper = progressEnabled && useXHR || presignedEnabled && fallbackEnabled;
7881
+ const backendUploadHelper = needsBackendHelper ? `
7814
7882
  /**
7815
7883
  * Upload via backend API with progress tracking
7816
- * Uses generated server action for type-safe upload
7884
+ * Uses XMLHttpRequest for progress tracking support
7817
7885
  */
7818
7886
  async function uploadViaBackendApi(
7819
7887
  formData: FormData,
7820
7888
  onProgress?: (progress: { loaded: number; total: number; percentage: number }) => void
7821
7889
  ): Promise<z.infer<typeof ${operationName}ResponseSchema>> {
7822
- ${progressEnabled && useXHR ? `return new Promise((resolve, reject) => {
7890
+ return new Promise((resolve, reject) => {
7823
7891
  const xhr = new XMLHttpRequest()
7824
7892
 
7825
7893
  if (onProgress) {
@@ -7868,13 +7936,9 @@ async function uploadViaBackendApi(
7868
7936
  : '${this.configuration.api?.baseUrl || "http://localhost:8000"}'
7869
7937
  xhr.open('POST', \`\${baseUrl}${endpoint.path}\`)
7870
7938
  xhr.send(formData)
7871
- })` : `
7872
- // Use generated server action for type-safe upload (no progress tracking)
7873
- // Note: Server actions don't support progress tracking, use XHR for progress
7874
- const response = await ${actionName}(formData as any)
7875
- return response
7876
- `}
7877
- }` : "";
7939
+ })
7940
+ }
7941
+ ` : "";
7878
7942
  const uploadLogic = presignedEnabled ? `
7879
7943
  let fileKey: string | null = null
7880
7944
  let directUploadAttempted = false
@@ -7989,6 +8053,7 @@ async function uploadViaBackendApi(
7989
8053
  `;
7990
8054
  const content = `${imports}
7991
8055
  ${presignedHelpers}
8056
+ ${backendUploadHelper || ""}
7992
8057
 
7993
8058
  /**
7994
8059
  * Upload hook for ${endpoint.method} ${endpoint.path}
@@ -8025,7 +8090,7 @@ export function ${hookName}Upload(options?: {
8025
8090
  ${uploadLogic}
8026
8091
  },
8027
8092
 
8028
- onSuccess: (data) => {
8093
+ onSuccess: (data: z.infer<typeof ${operationName}ResponseSchema>) => {
8029
8094
  // Don't show toast here, let the page handle it
8030
8095
  options?.onSuccess?.(data)
8031
8096
 
@@ -8239,7 +8304,9 @@ let toast: {
8239
8304
  } | null = null
8240
8305
 
8241
8306
  // Lazy load toast to avoid bundling issues
8242
- async function getToast() {
8307
+ async function getToast(): Promise<{
8308
+ error: (message: string, options?: { duration?: number; description?: string }) => void
8309
+ } | null> {
8243
8310
  if (toast !== null) return toast
8244
8311
 
8245
8312
  try {
@@ -8333,24 +8400,26 @@ export async function handleAuthError(error: unknown, redirectTo?: string): Prom
8333
8400
  const toastInstance = await getToast()
8334
8401
 
8335
8402
  // Show appropriate error message based on error type
8336
- if (isInactive) {
8337
- toastInstance.error('Your account has been deactivated. Please contact support for assistance.', {
8338
- duration: 5000,
8339
- description: 'You will be redirected to the login page.',
8340
- })
8341
- } else if (apiError.status === 401) {
8342
- toastInstance.error('Your session has expired. Please sign in again.', {
8343
- duration: 4000,
8344
- })
8345
- } else if (apiError.status === 403) {
8346
- toastInstance.error('Access denied. Your account may have been suspended.', {
8347
- duration: 5000,
8348
- description: 'You will be redirected to the login page.',
8349
- })
8350
- } else {
8351
- toastInstance.error('Authentication failed. Please sign in again.', {
8352
- duration: 4000,
8353
- })
8403
+ if (toastInstance) {
8404
+ if (isInactive) {
8405
+ toastInstance.error('Your account has been deactivated. Please contact support for assistance.', {
8406
+ duration: 5000,
8407
+ description: 'You will be redirected to the login page.',
8408
+ })
8409
+ } else if (apiError.status === 401) {
8410
+ toastInstance.error('Your session has expired. Please sign in again.', {
8411
+ duration: 4000,
8412
+ })
8413
+ } else if (apiError.status === 403) {
8414
+ toastInstance.error('Access denied. Your account may have been suspended.', {
8415
+ duration: 5000,
8416
+ description: 'You will be redirected to the login page.',
8417
+ })
8418
+ } else {
8419
+ toastInstance.error('Authentication failed. Please sign in again.', {
8420
+ duration: 4000,
8421
+ })
8422
+ }
8354
8423
  }
8355
8424
 
8356
8425
  if (typeof window !== 'undefined') {
@@ -8361,7 +8430,7 @@ export async function handleAuthError(error: unknown, redirectTo?: string): Prom
8361
8430
  const { signOut } = await import('${authPath}')
8362
8431
  signOut({
8363
8432
  redirect: false,
8364
- }).catch((signOutError) => {
8433
+ }).catch((signOutError: unknown) => {
8365
8434
  console.error('[Auth Error Handler] Error during sign out (non-blocking):', signOutError)
8366
8435
  })
8367
8436
  } catch (importError) {
@@ -9350,7 +9419,8 @@ var bridgeConfigSchema = z.object({
9350
9419
  logging: z.boolean().default(true),
9351
9420
  validation: z.boolean().default(true),
9352
9421
  mocking: z.boolean().default(false),
9353
- watch: z.boolean().default(false)
9422
+ watch: z.boolean().default(false),
9423
+ hotReload: z.boolean().default(false)
9354
9424
  }).optional(),
9355
9425
  plugins: z.array(
9356
9426
  z.object({
@@ -9363,6 +9433,7 @@ var bridgeConfigSchema = z.object({
9363
9433
  typescript: z.boolean().default(true),
9364
9434
  strict: z.boolean().default(true),
9365
9435
  comments: z.boolean().default(true),
9436
+ documentation: z.boolean().default(true),
9366
9437
  examples: z.boolean().default(false),
9367
9438
  tests: z.boolean().default(false)
9368
9439
  }).optional()
@@ -9468,12 +9539,14 @@ ${errorMessages}`,
9468
9539
  logging: true,
9469
9540
  validation: true,
9470
9541
  mocking: false,
9471
- watch: false
9542
+ watch: false,
9543
+ hotReload: false
9472
9544
  },
9473
9545
  generation: {
9474
9546
  typescript: true,
9475
9547
  strict: true,
9476
9548
  comments: true,
9549
+ documentation: true,
9477
9550
  examples: false,
9478
9551
  tests: false
9479
9552
  }
@@ -9526,5 +9599,5 @@ ${errorMessages}`,
9526
9599
  };
9527
9600
 
9528
9601
  export { BridgeCore, BridgeError, BridgeLogger, ConfigurationLoader, FileSystemManager, GenerationError, LogLevel, NextJsCodeGenerator, OpenApiSchemaParser, SchemaParseError, ValidationError, VersionChecker, __name, checkAndNotifyUpdates, createBridgeVersionChecker };
9529
- //# sourceMappingURL=chunk-IVMVPQUK.js.map
9530
- //# sourceMappingURL=chunk-IVMVPQUK.js.map
9602
+ //# sourceMappingURL=chunk-7Y66DW7M.js.map
9603
+ //# sourceMappingURL=chunk-7Y66DW7M.js.map