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.
@@ -1038,8 +1038,8 @@ import { cache } from 'react'
1038
1038
  * We use dynamic imports to avoid bundling them in the client
1039
1039
  */
1040
1040
  let serverOnlyModules: {
1041
- cookies?: () => import('next/headers').ReadonlyRequestCookies
1042
- headers?: () => import('next/headers').Headers
1041
+ cookies?: () => Awaited<ReturnType<typeof import('next/headers').cookies>>
1042
+ headers?: () => Awaited<ReturnType<typeof import('next/headers').headers>>
1043
1043
  after?: (fn: () => void | Promise<void>) => void
1044
1044
  updateTag?: (tag: string) => void
1045
1045
  } | null = null
@@ -1342,12 +1342,14 @@ export class BaseApiClient {
1342
1342
 
1343
1343
  // Try external auth service
1344
1344
  ${this.configuration.auth?.enabled ? `try {
1345
- const { ${tokenGetter} } = await import('${authPath}')
1346
- const session = await ${tokenGetter}()
1347
-
1348
- if (session?.accessToken) {
1349
- getAuthHeaders.Authorization = \`Bearer \${session.accessToken}\`
1350
- return getAuthHeaders
1345
+ const authModule = await import('${authPath}').catch(() => null)
1346
+ if (authModule && authModule.${tokenGetter}) {
1347
+ const session = await authModule.${tokenGetter}()
1348
+
1349
+ if (session?.accessToken) {
1350
+ getAuthHeaders.Authorization = \`Bearer \${session.accessToken}\`
1351
+ return getAuthHeaders
1352
+ }
1351
1353
  }
1352
1354
  } catch (error) {
1353
1355
  // Auth not available or error accessing
@@ -1546,7 +1548,8 @@ export class BaseApiClient {
1546
1548
  const requestPromise = this.executeRequestInternal<TData>(
1547
1549
  method, url, body, headers, fetchOptions, timeout, retries,
1548
1550
  retryDelay, retryCondition, validateResponse, skipAuth,
1549
- responseSchema, requestId, [...this.middleware, ...middleware]
1551
+ responseSchema, requestId, [...this.middleware, ...middleware],
1552
+ cacheTags, revalidate, connection, updateTag
1550
1553
  )
1551
1554
 
1552
1555
  requestCache.set(dedupeKey, requestPromise)
@@ -1562,7 +1565,8 @@ export class BaseApiClient {
1562
1565
  return this.executeRequestInternal<TData>(
1563
1566
  method, url, body, headers, fetchOptions, timeout, retries,
1564
1567
  retryDelay, retryCondition, validateResponse, skipAuth,
1565
- responseSchema, requestId, [...this.middleware, ...middleware]
1568
+ responseSchema, requestId, [...this.middleware, ...middleware],
1569
+ cacheTags, revalidate, connection, updateTag
1566
1570
  )
1567
1571
  }
1568
1572
 
@@ -1581,7 +1585,11 @@ export class BaseApiClient {
1581
1585
  skipAuth: boolean,
1582
1586
  responseSchema?: z.ZodSchema<TData>,
1583
1587
  requestId?: string,
1584
- middleware: RequestMiddleware[] = []
1588
+ middleware: RequestMiddleware[] = [],
1589
+ cacheTags: string[] = [],
1590
+ revalidate?: number | false,
1591
+ connection?: 'keep-alive' | 'close',
1592
+ updateTag?: (tag: string) => void
1585
1593
  ): Promise<ClientResponse<TData>> {
1586
1594
  const startTime = Date.now()
1587
1595
 
@@ -1668,7 +1676,7 @@ export class BaseApiClient {
1668
1676
 
1669
1677
  if (tagUpdater && typeof window === 'undefined') {
1670
1678
  // Update cache tags on server-side only
1671
- cacheTags.forEach(tag => {
1679
+ cacheTags.forEach((tag: string) => {
1672
1680
  try {
1673
1681
  tagUpdater(tag)
1674
1682
  } catch (error) {
@@ -2555,13 +2563,23 @@ var SchemaGenerator = class {
2555
2563
  const result = [];
2556
2564
  const schemaMap = /* @__PURE__ */ new Map();
2557
2565
  const dependencyGraph = /* @__PURE__ */ new Map();
2566
+ const enumSchemas = [];
2567
+ const otherSchemas = [];
2558
2568
  for (const schema of schemas) {
2569
+ const isEnum = this.isEnumSchema(schema);
2570
+ if (isEnum) {
2571
+ enumSchemas.push(schema);
2572
+ } else {
2573
+ otherSchemas.push(schema);
2574
+ }
2575
+ }
2576
+ for (const schema of [...enumSchemas, ...otherSchemas]) {
2559
2577
  if (!schemaMap.has(schema.name)) {
2560
2578
  schemaMap.set(schema.name, schema);
2561
2579
  dependencyGraph.set(schema.name, /* @__PURE__ */ new Set());
2562
2580
  }
2563
2581
  }
2564
- for (const schema of schemas) {
2582
+ for (const schema of [...enumSchemas, ...otherSchemas]) {
2565
2583
  const dependencies = this.findSchemaDependencies(schema);
2566
2584
  for (const dep of dependencies) {
2567
2585
  if (schemaMap.has(dep)) {
@@ -2591,13 +2609,25 @@ var SchemaGenerator = class {
2591
2609
  visiting.delete(schemaName);
2592
2610
  visited.add(schemaName);
2593
2611
  }, "visit");
2594
- for (const schema of schemaMap.values()) {
2612
+ for (const schema of enumSchemas) {
2613
+ if (!visited.has(schema.name)) {
2614
+ visit(schema.name);
2615
+ }
2616
+ }
2617
+ for (const schema of otherSchemas) {
2595
2618
  if (!visited.has(schema.name)) {
2596
2619
  visit(schema.name);
2597
2620
  }
2598
2621
  }
2599
2622
  return result;
2600
2623
  }
2624
+ isEnumSchema(schema) {
2625
+ if (!schema.schema || !schema.schema._def) {
2626
+ return false;
2627
+ }
2628
+ const def = schema.schema._def;
2629
+ return def.typeName === "ZodEnum";
2630
+ }
2601
2631
  findSchemaDependencies(schema) {
2602
2632
  const dependencies = [];
2603
2633
  const schemaObj = schema.schema;
@@ -2679,7 +2709,7 @@ var SchemaGenerator = class {
2679
2709
  schemaExports.push(...endpointSchemas.exports);
2680
2710
  }
2681
2711
  const validationHelpers = this.generateValidationHelpers();
2682
- const content = this.removeUnusedCode([
2712
+ const contentWithImports = [
2683
2713
  ...imports,
2684
2714
  "",
2685
2715
  "// Generated schemas from OpenAPI specification",
@@ -2689,7 +2719,13 @@ var SchemaGenerator = class {
2689
2719
  "",
2690
2720
  validationHelpers,
2691
2721
  ""
2692
- ].join("\n"));
2722
+ ].join("\n");
2723
+ let content = this.removeUnusedCode(contentWithImports);
2724
+ if (!content.includes('import { z } from "zod"') && !content.includes("import { z } from 'zod'")) {
2725
+ content = `import { z } from "zod"
2726
+
2727
+ ${content}`;
2728
+ }
2693
2729
  return {
2694
2730
  path: "schemas/index.ts",
2695
2731
  content,
@@ -3038,7 +3074,11 @@ export const errorMessages = {
3038
3074
  const lines = content.split("\n");
3039
3075
  const cleanedLines = [];
3040
3076
  const usedIdentifiers = /* @__PURE__ */ new Set();
3077
+ usedIdentifiers.add("z");
3041
3078
  for (const line of lines) {
3079
+ if (line.includes("z.")) {
3080
+ usedIdentifiers.add("z");
3081
+ }
3042
3082
  const assignments = line.match(/(\w+)\s*[:=]/g);
3043
3083
  if (assignments) {
3044
3084
  assignments.forEach((match) => {
@@ -3053,6 +3093,9 @@ export const errorMessages = {
3053
3093
  usedIdentifiers.add(identifier);
3054
3094
  });
3055
3095
  }
3096
+ if (line.includes("z.infer")) {
3097
+ usedIdentifiers.add("z");
3098
+ }
3056
3099
  }
3057
3100
  for (const line of lines) {
3058
3101
  if (line.trim() === "" || line.trim().startsWith("//")) {
@@ -3060,6 +3103,10 @@ export const errorMessages = {
3060
3103
  continue;
3061
3104
  }
3062
3105
  if (line.includes("import") && line.includes("from")) {
3106
+ if (line.includes('from "zod"') || line.includes("from 'zod'")) {
3107
+ cleanedLines.push(line);
3108
+ continue;
3109
+ }
3063
3110
  const importMatch = line.match(/import\s*\{([^}]+)\}/);
3064
3111
  if (importMatch) {
3065
3112
  const imports = importMatch[1].split(",").map((imp) => imp.trim());
@@ -3088,6 +3135,166 @@ var ActionGenerator = class {
3088
3135
  static {
3089
3136
  __name(this, "ActionGenerator");
3090
3137
  }
3138
+ get loggingEnabled() {
3139
+ return this.configuration.dev?.logging !== false;
3140
+ }
3141
+ get validationEnabled() {
3142
+ return this.configuration.dev?.validation !== false;
3143
+ }
3144
+ get commentsEnabled() {
3145
+ return this.configuration.generation?.comments !== false;
3146
+ }
3147
+ get documentationEnabled() {
3148
+ return this.configuration.generation?.documentation !== false;
3149
+ }
3150
+ commentLine(text, indent = 2) {
3151
+ if (!this.commentsEnabled) {
3152
+ return "";
3153
+ }
3154
+ return `${" ".repeat(indent)}// ${text}
3155
+ `;
3156
+ }
3157
+ buildDocBlock(endpoint, featuresDescription) {
3158
+ if (!this.documentationEnabled) {
3159
+ return "";
3160
+ }
3161
+ const summary = endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`;
3162
+ return `/**
3163
+ * ${summary}
3164
+ * @generated from ${endpoint.method} ${endpoint.path}
3165
+ * Features: ${featuresDescription}
3166
+ */
3167
+ `;
3168
+ }
3169
+ buildUtilityFunctions() {
3170
+ const parts = [];
3171
+ if (this.commentsEnabled) {
3172
+ parts.push("// Utility functions for enhanced server actions");
3173
+ }
3174
+ parts.push(`async function getClientInfo() {
3175
+ const headersList = await headers()
3176
+ const userAgent = headersList.get('user-agent') || 'unknown'
3177
+ const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown'
3178
+
3179
+ return { userAgent, ip }
3180
+ }`);
3181
+ if (this.validationEnabled) {
3182
+ parts.push(`async function validateAndSanitizeInput<T>(schema: z.ZodSchema<T>, input: unknown): Promise<T> {
3183
+ try {
3184
+ return await schema.parseAsync(input)
3185
+ } catch (error) {
3186
+ if (error instanceof z.ZodError) {
3187
+ const errorMessages = error.issues.map(issue => {
3188
+ const path = issue.path.length > 0 ? \`\${issue.path.join('.')}: \` : ''
3189
+ return \`\${path}\${issue.message}\`
3190
+ }).join(', ')
3191
+ throw new ActionError(\`Input validation failed: \${errorMessages}\`, 'VALIDATION_ERROR')
3192
+ }
3193
+ throw new ActionError('Invalid input format', 'VALIDATION_ERROR')
3194
+ }
3195
+ }`);
3196
+ }
3197
+ const actionErrorBlock = `${this.commentsEnabled ? "// Enhanced error handling with context\n" : ""}class ActionExecutionError extends ActionError {
3198
+ constructor(
3199
+ message: string,
3200
+ public readonly context: {
3201
+ endpoint: string
3202
+ method: string
3203
+ timestamp: number
3204
+ },
3205
+ public readonly originalError?: unknown
3206
+ ) {
3207
+ super(message, 'EXECUTION_ERROR')
3208
+ }
3209
+ }`;
3210
+ parts.push(actionErrorBlock);
3211
+ if (this.loggingEnabled) {
3212
+ const loggingHeader = this.commentsEnabled ? "// Logging utility for server actions\n" : "";
3213
+ parts.push(`${loggingHeader}async function logActionExecution(
3214
+ action: string,
3215
+ success: boolean,
3216
+ duration: number,
3217
+ context?: Record<string, any>
3218
+ ) {
3219
+ if (process.env.NODE_ENV === 'development') {
3220
+ console.log(\`[ACTION] \${action} - \${success ? 'SUCCESS' : 'FAILED'} (\${duration}ms)\`, context)
3221
+ }
3222
+
3223
+ ${this.commentsEnabled ? " // In production, send to your logging service\n // await analytics.track('server_action_executed', { action, success, duration, ...context })" : ""}
3224
+ }`);
3225
+ }
3226
+ return parts.join("\n\n");
3227
+ }
3228
+ buildCombinedValidationSnippet(operationName, schemaName) {
3229
+ if (this.validationEnabled) {
3230
+ return `
3231
+ ${this.commentLine("Validate and sanitize input payload", 6)} const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3232
+ const validatedBody = body
3233
+ const validatedParams = params as z.infer<typeof ${operationName}ParamsSchema>`;
3234
+ }
3235
+ return `
3236
+ ${this.commentLine("Runtime validation disabled - trusting provided payload", 6)} const { body, params } = parsedInput as {
3237
+ body: z.infer<typeof ${operationName}RequestSchema>
3238
+ params: z.infer<typeof ${operationName}ParamsSchema>
3239
+ }
3240
+ const validatedBody = body
3241
+ const validatedParams = params`;
3242
+ }
3243
+ buildBodyValidationSnippet(schemaName) {
3244
+ if (schemaName === "z.void()") {
3245
+ return "";
3246
+ }
3247
+ if (this.validationEnabled) {
3248
+ return `
3249
+ ${this.commentLine("Validate and sanitize request body", 6)} const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3250
+ }
3251
+ return `
3252
+ ${this.commentLine("Runtime validation disabled - trusting request body", 6)} const validatedBody = parsedInput as z.infer<typeof ${schemaName}>`;
3253
+ }
3254
+ buildParamsValidationSnippet(operationName, schemaName) {
3255
+ if (schemaName === "z.void()") {
3256
+ return "";
3257
+ }
3258
+ if (this.validationEnabled) {
3259
+ return `
3260
+ ${this.commentLine("Validate and sanitize parameters", 6)} const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as z.infer<typeof ${operationName}ParamsSchema>`;
3261
+ }
3262
+ return `
3263
+ ${this.commentLine("Runtime validation disabled - trusting parameters", 6)} const validatedParams = parsedInput as z.infer<typeof ${operationName}ParamsSchema>`;
3264
+ }
3265
+ buildSuccessLoggingBlock(actionName, endpoint, indent, isStreaming = false) {
3266
+ if (!this.loggingEnabled) {
3267
+ return "";
3268
+ }
3269
+ if (isStreaming) {
3270
+ return `
3271
+ ${this.commentLine("Background tasks (Next.js 15 feature)", indent)}${" ".repeat(indent)}after(async () => {
3272
+ ${this.commentLine("Perform background tasks after response is sent", indent + 2)}${" ".repeat(indent + 2)}await logActionExecution('${actionName}', true, Date.now() - startTime, {
3273
+ ${" ".repeat(indent + 4)}method: '${endpoint.method}',
3274
+ ${" ".repeat(indent + 4)}path: '${endpoint.path}'
3275
+ ${" ".repeat(indent + 2)}})
3276
+ ${" ".repeat(indent)}})`;
3277
+ }
3278
+ return `
3279
+ ${this.commentLine("Log successful execution", indent)}${" ".repeat(indent)}const duration = Date.now() - startTime
3280
+ ${" ".repeat(indent)}await logActionExecution('${actionName}', true, duration, {
3281
+ ${" ".repeat(indent + 2)}method: '${endpoint.method}',
3282
+ ${" ".repeat(indent + 2)}path: '${endpoint.path}'
3283
+ ${" ".repeat(indent)}})`;
3284
+ }
3285
+ buildErrorLoggingBlock(actionName, endpoint, indent) {
3286
+ if (!this.loggingEnabled) {
3287
+ return "";
3288
+ }
3289
+ return `
3290
+ ${" ".repeat(indent)}const duration = Date.now() - startTime
3291
+
3292
+ ${this.commentLine("Enhanced error logging", indent)}${" ".repeat(indent)}await logActionExecution('${actionName}', false, duration, {
3293
+ ${" ".repeat(indent + 2)}method: '${endpoint.method}',
3294
+ ${" ".repeat(indent + 2)}path: '${endpoint.path}',
3295
+ ${" ".repeat(indent + 2)}error: error instanceof Error ? error.message : 'Unknown error'
3296
+ ${" ".repeat(indent)}})`;
3297
+ }
3091
3298
  buildImportPath(relativePath) {
3092
3299
  const outputDirectory = this.configuration.outputDir || "generated";
3093
3300
  const cleanPath = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
@@ -3140,7 +3347,7 @@ var ActionGenerator = class {
3140
3347
  ...hasStreaming ? ["import { after } from 'next/server'"] : [],
3141
3348
  "import { headers } from 'next/headers'",
3142
3349
  `import { apiClient } from '${clientImport}'`,
3143
- `import { ${usesAuth || hasRateLimit ? "authActionClient" : "actionClientWithMeta"}, ActionError } from '${safeActionImport}'`,
3350
+ `import { actionClientWithMeta${usesAuth || hasRateLimit ? ", authActionClient" : ""}, ActionError } from '${safeActionImport}'`,
3144
3351
  ...schemaImportsString ? [`import {
3145
3352
  ${schemaImportsString}
3146
3353
  } from '${schemasImport}'`] : [],
@@ -3152,8 +3359,7 @@ var ActionGenerator = class {
3152
3359
  ] : []
3153
3360
  ].filter(Boolean).join("\n");
3154
3361
  const rateLimitSetup = hasRateLimit ? `
3155
- // Rate limiting setup
3156
- const redis = new Redis({
3362
+ ${this.commentsEnabled ? "// Rate limiting setup\n" : ""}const redis = new Redis({
3157
3363
  url: process.env.UPSTASH_REDIS_REST_URL!,
3158
3364
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
3159
3365
  })
@@ -3163,60 +3369,7 @@ const ratelimit = new Ratelimit({
3163
3369
  limiter: Ratelimit.slidingWindow(10, '1 m'),
3164
3370
  analytics: true,
3165
3371
  })` : "";
3166
- const utilityFunctions = `
3167
- // Utility functions for enhanced server actions
3168
- 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
-
3176
- 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
- // Enhanced error handling with context
3192
- class ActionExecutionError extends ActionError {
3193
- constructor(
3194
- message: string,
3195
- public readonly context: {
3196
- endpoint: string
3197
- method: string
3198
- timestamp: number
3199
- },
3200
- public readonly originalError?: unknown
3201
- ) {
3202
- super(message, 'EXECUTION_ERROR')
3203
- }
3204
- }
3205
-
3206
- // Logging utility for server actions
3207
- 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
- // In production, send to your logging service
3218
- // await analytics.track('server_action_executed', { action, success, duration, ...context })
3219
- }`;
3372
+ const utilityFunctions = this.buildUtilityFunctions();
3220
3373
  const content = `${imports}
3221
3374
  ${rateLimitSetup}
3222
3375
  ${utilityFunctions}
@@ -3233,8 +3386,8 @@ ${actions}`;
3233
3386
  }
3234
3387
  });
3235
3388
  }
3236
- const indexContent = `// Auto-generated actions index
3237
- ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier(tag)}'`).join("\n")}`;
3389
+ const indexHeader = this.commentsEnabled ? "// Auto-generated actions index\n" : "";
3390
+ const indexContent = `${indexHeader}${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier(tag)}'`).join("\n")}`;
3238
3391
  generatedFiles.push({
3239
3392
  path: "actions/index.ts",
3240
3393
  content: indexContent,
@@ -3283,54 +3436,48 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3283
3436
  body: ${operationName}RequestSchema,
3284
3437
  params: ${operationName}ParamsSchema
3285
3438
  })`;
3286
- parameterProcessing = `
3287
- // Validate and sanitize input
3288
- const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3289
- const validatedBody = body
3290
- const validatedParams = params as any`;
3439
+ parameterProcessing = this.buildCombinedValidationSnippet(operationName, schemaName);
3291
3440
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, true, true);
3292
3441
  } else if (hasRequestBody) {
3293
3442
  schemaName = `${operationName}RequestSchema`;
3294
- parameterProcessing = `
3295
- // Validate and sanitize request body
3296
- const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3443
+ parameterProcessing = this.buildBodyValidationSnippet(schemaName);
3297
3444
  requestOptionsParams = `body: validatedBody`;
3298
3445
  } else if (hasAnyParams) {
3299
3446
  schemaName = `${operationName}ParamsSchema`;
3300
- parameterProcessing = `
3301
- // Validate and sanitize parameters
3302
- const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as any`;
3447
+ parameterProcessing = this.buildParamsValidationSnippet(operationName, schemaName);
3303
3448
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, false, true);
3304
3449
  } else {
3305
3450
  schemaName = "z.void()";
3306
3451
  parameterProcessing = "";
3307
3452
  requestOptionsParams = "";
3308
3453
  }
3454
+ const parsedInputType = hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void";
3455
+ const ctxType = requiresAuth || hasRateLimit ? "{ user?: any; ratelimit?: { remaining: number } }" : "any";
3456
+ const ctxDeclaration = requiresAuth || hasRateLimit ? `ctx: ${ctxType}` : "ctx?: any";
3457
+ const actionArgsType = `{ parsedInput: ${parsedInputType}; ${ctxDeclaration} }`;
3309
3458
  const clientName = requiresAuth || hasRateLimit ? "authActionClient" : "actionClientWithMeta";
3310
3459
  const rateLimitCode = hasRateLimit ? `
3311
- // Rate limiting
3312
- const { userAgent, ip } = await getClientInfo()
3313
- const identifier = \`\${ip}-\${userAgent}\`
3314
- const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3315
-
3316
- if (!success) {
3317
- throw new ActionError(
3318
- \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3319
- 'RATE_LIMIT_EXCEEDED'
3320
- )
3321
- }` : "";
3460
+ ${this.commentLine("Rate limiting", 6)} const { userAgent, ip } = await getClientInfo()
3461
+ const identifier = \`\${ip}-\${userAgent}\`
3462
+ const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3463
+
3464
+ if (!success) {
3465
+ throw new ActionError(
3466
+ \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3467
+ 'RATE_LIMIT_EXCEEDED'
3468
+ )
3469
+ }` : "";
3322
3470
  const revalidationCode = revalidationTags.length > 0 ? revalidationTags.map(
3323
- (tag) => ` updateTag('${tag}')
3324
- console.log('Updated tag: ${tag}')`
3471
+ (tag) => ` updateTag('${tag}')${this.loggingEnabled ? `
3472
+ console.log('Updated tag: ${tag}')` : ""}`
3325
3473
  ).join("\n") : "";
3326
3474
  const kebabActionName = actionName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
3327
3475
  if (isQuery) {
3328
- return `/**
3329
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3330
- * @generated from ${endpoint.method} ${endpoint.path}
3331
- * Features: React cache, input validation, error handling
3332
- */
3333
- export const ${actionName} = cache(
3476
+ const docBlock = this.buildDocBlock(
3477
+ endpoint,
3478
+ "React cache, input validation, error handling"
3479
+ );
3480
+ return `${docBlock}export const ${actionName} = cache(
3334
3481
  ${clientName}
3335
3482
  .metadata({
3336
3483
  name: "${kebabActionName}",
@@ -3338,7 +3485,7 @@ export const ${actionName} = cache(
3338
3485
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3339
3486
  })
3340
3487
  .schema(${schemaName})
3341
- .action(async ({ parsedInput, ctx }) => {
3488
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3342
3489
  const startTime = Date.now()
3343
3490
 
3344
3491
  try {${rateLimitCode}${parameterProcessing}
@@ -3353,23 +3500,11 @@ export const ${actionName} = cache(
3353
3500
  }
3354
3501
  })
3355
3502
 
3356
- // Log successful execution
3357
- const duration = Date.now() - startTime
3358
- await logActionExecution('${actionName}', true, duration, {
3359
- method: '${endpoint.method}',
3360
- path: '${endpoint.path}'
3361
- })
3503
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 8)}
3362
3504
 
3363
3505
  return response.data
3364
3506
  } catch (error) {
3365
- const duration = Date.now() - startTime
3366
-
3367
- // Enhanced error logging
3368
- await logActionExecution('${actionName}', false, duration, {
3369
- method: '${endpoint.method}',
3370
- path: '${endpoint.path}',
3371
- error: error instanceof Error ? error.message : 'Unknown error'
3372
- })
3507
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 8)}
3373
3508
 
3374
3509
  // Throw enhanced error with context
3375
3510
  throw new ActionExecutionError(
@@ -3386,50 +3521,37 @@ export const ${actionName} = cache(
3386
3521
  )`;
3387
3522
  }
3388
3523
  const redirectCode = `
3389
- // Handle potential redirects based on response
3390
- if (response.status === 201 && response.headers.get('location')) {
3524
+ ${this.commentLine("Handle potential redirects based on response", 8)} if (response.status === 201 && response.headers.get('location')) {
3391
3525
  const location = response.headers.get('location')!
3392
3526
  redirect(location)
3393
3527
  }`;
3394
3528
  const streamingCode = `
3395
- // Handle streaming responses
3396
- if (response.headers.get('content-type')?.includes('text/stream')) {
3397
- // Process streaming response
3398
- return response.data
3529
+ ${this.commentLine("Handle streaming responses", 8)} if (response.headers.get('content-type')?.includes('text/stream')) {
3530
+ ${this.commentLine("Process streaming response", 10)} return response.data
3399
3531
  }`;
3400
3532
  const fileUploadCode = hasFileUpload ? uploadStrategy === "external" && uploadProvider === "vercel-blob" ? `
3401
- // Handle file uploads with Vercel Blob
3402
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3533
+ ${this.commentLine("Handle file uploads with Vercel Blob", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3403
3534
  const file = (validatedBody as any).file as File
3404
3535
  const blob = await put(file.name, file, { access: 'public' })
3405
3536
  Object.assign(validatedBody, { fileUrl: blob.url })
3406
3537
  }` : uploadStrategy === "external" && uploadProvider === "uploadthing" ? `
3407
- // Handle file uploads with UploadThing
3408
- // Note: UploadThing requires a separate route handler setup
3409
- // See: https://docs.uploadthing.com/getting-started/appdir
3410
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3411
- // UploadThing file handling should be done via their route handler
3412
- // This is a placeholder - implement according to UploadThing docs
3413
- }` : `
3414
- // Handle file uploads with standard FormData
3415
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3416
- // Standard file upload handling
3417
- const file = (validatedBody as any).file as File
3418
- // Process file with compression and validation if enabled
3419
- }` : "";
3420
- return `/**
3421
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3422
- * @generated from ${endpoint.method} ${endpoint.path}
3423
- * Features: Input validation, revalidation, error handling
3424
- */
3425
- export const ${actionName} = ${clientName}
3538
+ ${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) {
3539
+ ${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)} }` : `
3540
+ ${this.commentLine("Handle file uploads with standard FormData", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3541
+ ${this.commentLine("Standard file upload handling", 10)} const file = (validatedBody as any).file as File
3542
+ ${this.commentLine("Process file with compression and validation if enabled", 10)} }` : "";
3543
+ const mutationDocBlock = this.buildDocBlock(
3544
+ endpoint,
3545
+ "Input validation, revalidation, error handling"
3546
+ );
3547
+ return `${mutationDocBlock}export const ${actionName} = ${clientName}
3426
3548
  .metadata({
3427
3549
  name: "${kebabActionName}",
3428
3550
  requiresAuth: ${requiresAuth}${hasRateLimit ? `,
3429
3551
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3430
3552
  })
3431
3553
  .schema(${schemaName})
3432
- .action(async ({ parsedInput, ctx }) => {
3554
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3433
3555
  const startTime = Date.now()
3434
3556
 
3435
3557
  try {${rateLimitCode}${parameterProcessing}${fileUploadCode}
@@ -3444,33 +3566,13 @@ export const ${actionName} = ${clientName}
3444
3566
  }
3445
3567
  })${streamingCode}${redirectCode}
3446
3568
 
3447
- // Revalidate cache after successful mutation
3448
- ${revalidationCode}
3449
-
3450
- // Background tasks (Next.js 15 feature)
3451
- ${isStreaming ? `after(async () => {
3452
- // Perform background tasks after response is sent
3453
- await logActionExecution('${actionName}', true, Date.now() - startTime, {
3454
- method: '${endpoint.method}',
3455
- path: '${endpoint.path}'
3456
- })
3457
- })` : `// Log successful execution
3458
- const duration = Date.now() - startTime
3459
- await logActionExecution('${actionName}', true, duration, {
3460
- method: '${endpoint.method}',
3461
- path: '${endpoint.path}'
3462
- })`}
3569
+ ${revalidationCode ? `${this.commentLine("Revalidate cache after successful mutation", 6)}${revalidationCode}
3570
+ ` : ""}
3571
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 6, isStreaming)}
3463
3572
 
3464
3573
  return response.data
3465
3574
  } catch (error) {
3466
- const duration = Date.now() - startTime
3467
-
3468
- // Enhanced error logging
3469
- await logActionExecution('${actionName}', false, duration, {
3470
- method: '${endpoint.method}',
3471
- path: '${endpoint.path}',
3472
- error: error instanceof Error ? error.message : 'Unknown error'
3473
- })
3575
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 6)}
3474
3576
 
3475
3577
  // Throw enhanced error with context
3476
3578
  throw new ActionExecutionError(
@@ -3629,7 +3731,6 @@ var HookGenerator = class {
3629
3731
  if (hasInfiniteQueries) reactQueryImports.push("useInfiniteQuery");
3630
3732
  const reactImports = [];
3631
3733
  if (hasMutations) reactImports.push("useOptimistic", "useTransition");
3632
- if (hasQueries) reactImports.push("useCallback");
3633
3734
  const imports = [
3634
3735
  "'use client'",
3635
3736
  "",
@@ -3741,19 +3842,19 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3741
3842
  * @returns useQuery result with data of type ${returnType}
3742
3843
  */
3743
3844
  export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join(", ")}, ` : ""}options?: ${optionsType}) {
3744
- const [searchParams, setSearchParams] = useQueryStates(searchParamsParser)
3845
+ const [searchParams] = useQueryStates(searchParamsParser)
3745
3846
  const { initialData, ...restOptions } = options ?? {}
3746
3847
 
3747
3848
  return useQuery({
3748
3849
  queryKey: [...${queryKey}, searchParams],
3749
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3850
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3750
3851
  try {
3751
3852
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams }")})
3752
3853
  return result
3753
3854
  } catch (error) {
3754
3855
  handleActionError(error)
3755
3856
  }
3756
- }, [searchParams]),
3857
+ },
3757
3858
  staleTime: ${staleTime},
3758
3859
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3759
3860
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3763,14 +3864,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3763
3864
  refetchInterval: options?.refetchInterval, // Optional polling interval
3764
3865
  initialDataUpdatedAt: initialData ? Date.now() : undefined,
3765
3866
  // React Query v5: placeholderData replaces keepPreviousData
3766
- placeholderData: (previousData) => previousData,
3767
- retry: (failureCount, error) => {
3867
+ placeholderData: (previousData: ${returnType} | undefined) => previousData,
3868
+ retry: (failureCount: number, error: Error) => {
3768
3869
  // Don't retry on 4xx errors (client errors)
3769
3870
  if (error instanceof Error && error.message.includes('4')) return false
3770
3871
  // Retry up to 3 times for network/server errors
3771
3872
  return failureCount < 3
3772
3873
  },
3773
- initialData: initialData as any,
3874
+ initialData: initialData as ${returnType} | undefined,
3774
3875
  ...restOptions
3775
3876
  })
3776
3877
  }
@@ -3785,15 +3886,15 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3785
3886
 
3786
3887
  return useInfiniteQuery({
3787
3888
  queryKey: [...${queryKey}, 'infinite', searchParams],
3788
- queryFn: useCallback(async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3889
+ queryFn: async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3789
3890
  try {
3790
3891
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams, page: pageParam, limit: searchParams.limit }")})
3791
3892
  return result
3792
3893
  } catch (error) {
3793
3894
  handleActionError(error)
3794
3895
  }
3795
- }, [searchParams]),
3796
- getNextPageParam: (lastPage: any, allPages) => {
3896
+ },
3897
+ getNextPageParam: (lastPage: ${returnType}, allPages: ${returnType}[]) => {
3797
3898
  if (lastPage?.hasMore || (Array.isArray(lastPage) && lastPage.length === searchParams.limit)) {
3798
3899
  return allPages.length + 1
3799
3900
  }
@@ -3806,9 +3907,9 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3806
3907
  refetchOnReconnect: true,
3807
3908
  refetchOnMount: 'always',
3808
3909
  initialDataUpdatedAt: initialData ? Date.now() : undefined,
3809
- placeholderData: (previousData) => previousData,
3910
+ placeholderData: (previousData: ${returnType} | undefined) => previousData,
3810
3911
  retry: 3,
3811
- initialData: initialData as any,
3912
+ initialData: initialData as ${returnType} | undefined,
3812
3913
  ...restOptions
3813
3914
  })
3814
3915
  }
@@ -3822,12 +3923,12 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3822
3923
 
3823
3924
  return useSuspenseQuery({
3824
3925
  queryKey: ${queryKey},
3825
- queryFn: useCallback(async () => {
3926
+ queryFn: async () => {
3826
3927
  const result = await ${actionName}(${actionCallParams})
3827
3928
  return result
3828
- }, []),
3929
+ },
3829
3930
  staleTime: ${staleTime},
3830
- initialData: initialData as any,
3931
+ initialData: initialData as ${returnType} | undefined,
3831
3932
  ...restOptions
3832
3933
  })
3833
3934
  }`;
@@ -3842,14 +3943,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3842
3943
 
3843
3944
  return useQuery({
3844
3945
  queryKey: ${queryKey},
3845
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3946
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3846
3947
  try {
3847
3948
  const result = await ${actionName}(${actionCallParams})
3848
3949
  return result
3849
3950
  } catch (error) {
3850
3951
  handleActionError(error)
3851
3952
  }
3852
- }, []),
3953
+ },
3853
3954
  staleTime: ${staleTime},
3854
3955
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3855
3956
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3859,14 +3960,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3859
3960
  refetchInterval: options?.refetchInterval, // Optional polling interval
3860
3961
  initialDataUpdatedAt: initialData ? Date.now() : undefined,
3861
3962
  // React Query v5: placeholderData replaces keepPreviousData
3862
- placeholderData: (previousData) => previousData,
3863
- retry: (failureCount, error) => {
3963
+ placeholderData: (previousData: ${returnType} | undefined) => previousData,
3964
+ retry: (failureCount: number, error: Error) => {
3864
3965
  // Don't retry on 4xx errors (client errors)
3865
3966
  if (error instanceof Error && error.message.includes('4')) return false
3866
3967
  // Retry up to 3 times for network/server errors
3867
3968
  return failureCount < 3
3868
3969
  },
3869
- initialData: initialData as any,
3970
+ initialData: initialData as ${returnType} | undefined,
3870
3971
  ...restOptions
3871
3972
  })
3872
3973
  }
@@ -3880,12 +3981,12 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3880
3981
 
3881
3982
  return useSuspenseQuery({
3882
3983
  queryKey: ${queryKey},
3883
- queryFn: useCallback(async () => {
3984
+ queryFn: async () => {
3884
3985
  const result = await ${actionName}(${actionCallParams})
3885
3986
  return result
3886
- }, []),
3987
+ },
3887
3988
  staleTime: ${staleTime},
3888
- initialData: initialData as any,
3989
+ initialData: initialData as ${returnType} | undefined,
3889
3990
  ...restOptions
3890
3991
  })
3891
3992
  }`;
@@ -3916,8 +4017,6 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3916
4017
  const relatedQueries = this.findRelatedQueries(endpoint, context);
3917
4018
  const invalidationCode = this.buildInvalidationCode(endpoint, relatedQueries, invalidationTags);
3918
4019
  const cancelQueriesCode = this.buildCancelQueriesCode(endpoint, relatedQueries);
3919
- const snapshotCode = this.buildSnapshotCode(endpoint, relatedQueries);
3920
- const rollbackCode = this.buildRollbackCode(endpoint, relatedQueries);
3921
4020
  return `/**
3922
4021
  * Optimized mutation hook for ${endpoint.method} ${endpoint.path}
3923
4022
  * Features: Optimistic updates, smart invalidation, error handling
@@ -3938,24 +4037,22 @@ export function ${hookName}(options?: {
3938
4037
  mutationFn: async (variables: ${inputType}): Promise<${outputType}> => {
3939
4038
  try {
3940
4039
  const result = await ${actionName}(${variablesParam === "undefined" ? "" : "variables"})
3941
- return result.data || ({} as any)
4040
+ return (result ?? ({} as ${outputType}))
3942
4041
  } catch (error) {
3943
4042
  handleActionError(error)
3944
4043
  }
3945
4044
  },
3946
4045
 
3947
- onMutate: async (variables) => {
4046
+ onMutate: async (variables: ${inputType}) => {
3948
4047
  ${cancelQueriesCode}
3949
-
3950
- ${snapshotCode}
3951
-
4048
+
3952
4049
  // Optimistic update (if provided)
3953
4050
  if (options?.optimisticUpdate) {
3954
4051
  const optimisticValue = options.optimisticUpdate(variables)
3955
4052
  setOptimisticData(optimisticValue)
3956
4053
  }
3957
4054
 
3958
- return { ${this.getSnapshotReturnNames(relatedQueries)} }
4055
+ return {}
3959
4056
  },
3960
4057
 
3961
4058
  onSuccess: (data, variables) => {
@@ -3964,17 +4061,11 @@ ${snapshotCode}
3964
4061
  toast.success('${this.getSuccessMessage(endpoint)}')
3965
4062
  }
3966
4063
 
3967
- // Invalidate and refetch related queries
3968
- ${invalidationCode}
3969
-
3970
4064
  // Custom success handler
3971
4065
  options?.onSuccess?.(data, variables)
3972
4066
  },
3973
4067
 
3974
- onError: (error, variables, context) => {
3975
- // Rollback optimistic update
3976
- ${rollbackCode}
3977
-
4068
+ onError: (error: Error, variables: ${inputType}) => {
3978
4069
  // Show error toast
3979
4070
  if (options?.showToast !== false) {
3980
4071
  toast.error(error.message || '${this.getErrorMessage(endpoint)}')
@@ -3984,7 +4075,7 @@ ${rollbackCode}
3984
4075
  options?.onError?.(error as Error, variables)
3985
4076
  },
3986
4077
 
3987
- onSettled: () => {
4078
+ onSettled: async () => {
3988
4079
  // Always refetch after error or success
3989
4080
  ${invalidationCode}
3990
4081
  }
@@ -4010,103 +4101,81 @@ ${invalidationCode}
4010
4101
  return [];
4011
4102
  }
4012
4103
  const relatedQueries = [];
4013
- const mutationPath = endpoint.path;
4104
+ const seen = /* @__PURE__ */ new Set();
4014
4105
  const mutationTag = endpoint.tags[0];
4015
- const pathSegments = mutationPath.split("/").filter(Boolean);
4106
+ const mutationSegments = this.getResourceSegments(endpoint.path);
4107
+ const mutationResource = mutationSegments[0];
4016
4108
  context.schema.endpoints.forEach((queryEndpoint) => {
4017
4109
  if (queryEndpoint.method !== "GET") return;
4018
- const queryPath = queryEndpoint.path;
4110
+ const queryId = queryEndpoint.operationId || queryEndpoint.id;
4111
+ if (seen.has(queryId)) return;
4019
4112
  const queryTag = queryEndpoint.tags[0];
4020
- if (queryTag === mutationTag) {
4113
+ const querySegments = this.getResourceSegments(queryEndpoint.path);
4114
+ const queryResource = querySegments[0];
4115
+ if (mutationTag && queryTag === mutationTag) {
4021
4116
  relatedQueries.push(queryEndpoint);
4117
+ seen.add(queryId);
4022
4118
  return;
4023
4119
  }
4024
- const querySegments = queryPath.split("/").filter(Boolean);
4025
- if (pathSegments.length >= 2 && querySegments.length >= 2) {
4026
- const baseMatch = pathSegments.slice(0, 2).join("/") === querySegments.slice(0, 2).join("/");
4027
- if (baseMatch) {
4028
- relatedQueries.push(queryEndpoint);
4029
- }
4120
+ if (mutationResource && queryResource && mutationResource === queryResource) {
4121
+ relatedQueries.push(queryEndpoint);
4122
+ seen.add(queryId);
4030
4123
  }
4031
4124
  });
4032
4125
  return relatedQueries;
4033
4126
  }
4127
+ getResourceSegments(path3) {
4128
+ return path3.split("/").filter(Boolean).filter((segment) => segment.toLowerCase() !== "api" && !/^v\d+/i.test(segment));
4129
+ }
4034
4130
  /**
4035
4131
  * Build invalidation code for related queries
4036
4132
  */
4037
4133
  buildInvalidationCode(endpoint, relatedQueries, invalidationTags) {
4038
- const invalidations = [];
4134
+ const invalidations = /* @__PURE__ */ new Set();
4039
4135
  relatedQueries.forEach((queryEndpoint) => {
4040
- const queryKey = this.generateQueryKey(queryEndpoint);
4041
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
4136
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4137
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4042
4138
  });
4043
4139
  invalidationTags.forEach((tag) => {
4044
- if (!invalidations.some((inv) => inv.includes(`'${tag}'`))) {
4045
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4046
- }
4140
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4047
4141
  });
4048
- if (invalidations.length === 0) {
4142
+ if (invalidations.size === 0) {
4049
4143
  const inferredKey = this.inferQueryKeyFromPath(endpoint);
4050
4144
  if (inferredKey) {
4051
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4145
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4052
4146
  }
4053
4147
  }
4054
- return invalidations.length > 0 ? invalidations.join("\n") : " // No specific cache invalidation needed";
4148
+ if (invalidations.size === 0) {
4149
+ return " // No specific cache invalidation needed";
4150
+ }
4151
+ if (invalidations.size === 1) {
4152
+ const [statement] = Array.from(invalidations);
4153
+ return ` await ${statement}`;
4154
+ }
4155
+ return ` await Promise.all([
4156
+ ${Array.from(invalidations).join(",\n ")}
4157
+ ])`;
4055
4158
  }
4056
4159
  /**
4057
4160
  * Build cancel queries code
4161
+ * Uses query key patterns to cancel all related queries regardless of parameters
4058
4162
  */
4059
4163
  buildCancelQueriesCode(endpoint, relatedQueries) {
4060
- const cancels = [];
4164
+ const cancels = /* @__PURE__ */ new Set();
4061
4165
  relatedQueries.forEach((queryEndpoint) => {
4062
- const queryKey = this.generateQueryKey(queryEndpoint);
4063
- cancels.push(` await queryClient.cancelQueries({ queryKey: ${queryKey} })`);
4064
- });
4065
- return cancels.length > 0 ? cancels.join("\n") : " // No queries to cancel";
4066
- }
4067
- /**
4068
- * Build snapshot code for rollback
4069
- */
4070
- buildSnapshotCode(endpoint, relatedQueries) {
4071
- if (relatedQueries.length === 0) {
4072
- return " // No queries to snapshot";
4073
- }
4074
- const snapshots = [];
4075
- relatedQueries.forEach((queryEndpoint, index) => {
4076
- const queryKey = this.generateQueryKey(queryEndpoint);
4077
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4078
- snapshots.push(` const ${varName} = queryClient.getQueryData(${queryKey})`);
4166
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4167
+ cancels.add(`queryClient.cancelQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4079
4168
  });
4080
- return snapshots.join("\n");
4081
- }
4082
- /**
4083
- * Build rollback code
4084
- */
4085
- buildRollbackCode(endpoint, relatedQueries) {
4086
- if (relatedQueries.length === 0) {
4087
- return " // No queries to rollback";
4169
+ if (cancels.size === 0) {
4170
+ return " // No queries to cancel";
4088
4171
  }
4089
- const rollbacks = [];
4090
- relatedQueries.forEach((queryEndpoint) => {
4091
- const queryKey = this.generateQueryKey(queryEndpoint);
4092
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4093
- rollbacks.push(` if (context?.${varName}) {
4094
- queryClient.setQueryData(${queryKey}, context.${varName})
4095
- }`);
4096
- });
4097
- return rollbacks.join("\n");
4098
- }
4099
- /**
4100
- * Get snapshot return names for context
4101
- */
4102
- getSnapshotReturnNames(relatedQueries) {
4103
- if (relatedQueries.length === 0) {
4104
- return "previousData: null";
4172
+ if (cancels.size === 1) {
4173
+ const [statement] = Array.from(cancels);
4174
+ return ` await ${statement}`;
4105
4175
  }
4106
- return relatedQueries.map((queryEndpoint) => {
4107
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4108
- return `${varName}`;
4109
- }).join(", ");
4176
+ return ` await Promise.all([
4177
+ ${Array.from(cancels).join(",\n ")}
4178
+ ])`;
4110
4179
  }
4111
4180
  /**
4112
4181
  * Infer query key from mutation path
@@ -4264,8 +4333,8 @@ export function useBridgeQuery<TData = unknown, TError = Error>(
4264
4333
  refetchOnWindowFocus: true,
4265
4334
  refetchOnReconnect: true,
4266
4335
  refetchOnMount: 'always',
4267
- placeholderData: (previousData) => previousData,
4268
- retry: (failureCount, error) => {
4336
+ placeholderData: (previousData: TData | undefined) => previousData,
4337
+ retry: (failureCount: number, error: Error) => {
4269
4338
  if (error instanceof Error && error.message.includes('4')) return false
4270
4339
  return failureCount < 3
4271
4340
  },
@@ -4290,13 +4359,13 @@ export function useBridgeInfiniteQuery<TData = unknown, TError = Error>(
4290
4359
  refetchOnWindowFocus: true,
4291
4360
  refetchOnReconnect: true,
4292
4361
  refetchOnMount: 'always',
4293
- placeholderData: (previousData) => previousData,
4294
- retry: (failureCount, error) => {
4362
+ placeholderData: (previousData: TData | undefined) => previousData,
4363
+ retry: (failureCount: number, error: Error) => {
4295
4364
  if (error instanceof Error && error.message.includes('4')) return false
4296
4365
  return failureCount < 3
4297
4366
  },
4298
4367
  ...options,
4299
- } as any)
4368
+ })
4300
4369
  }
4301
4370
 
4302
4371
  /**
@@ -4313,7 +4382,7 @@ export function useBridgeSuspenseQuery<TData = unknown, TError = Error>(
4313
4382
  queryFn: queryFn as QueryFunction<TData, QueryKey>,
4314
4383
  staleTime: 5 * 60 * 1000,
4315
4384
  gcTime: 10 * 60 * 1000,
4316
- retry: (failureCount, error) => {
4385
+ retry: (failureCount: number, error: Error) => {
4317
4386
  if (error instanceof Error && error.message.includes('4')) return false
4318
4387
  return failureCount < 3
4319
4388
  },
@@ -7528,9 +7597,6 @@ var UploadGenerator = class {
7528
7597
  const compressionFormats = uploads?.compression?.formats || ["gzip", "webp"];
7529
7598
  const content = `'use client'
7530
7599
 
7531
- import { useMutation, useQueryClient } from '@tanstack/react-query'
7532
- import { toast } from 'sonner'
7533
-
7534
7600
  /**
7535
7601
  * Upload configuration
7536
7602
  */
@@ -7710,7 +7776,7 @@ export function createUploadFormData(
7710
7776
  "FileValidationResult",
7711
7777
  "UploadProgress"
7712
7778
  ],
7713
- imports: ["@tanstack/react-query", "sonner"],
7779
+ imports: [],
7714
7780
  dependencies: []
7715
7781
  }
7716
7782
  };
@@ -7816,16 +7882,18 @@ async function uploadToS3(
7816
7882
  xhr.send(formData)
7817
7883
  })
7818
7884
  }
7819
-
7885
+ ` : "";
7886
+ const needsBackendHelper = progressEnabled && useXHR || presignedEnabled && fallbackEnabled;
7887
+ const backendUploadHelper = needsBackendHelper ? `
7820
7888
  /**
7821
7889
  * Upload via backend API with progress tracking
7822
- * Uses generated server action for type-safe upload
7890
+ * Uses XMLHttpRequest for progress tracking support
7823
7891
  */
7824
7892
  async function uploadViaBackendApi(
7825
7893
  formData: FormData,
7826
7894
  onProgress?: (progress: { loaded: number; total: number; percentage: number }) => void
7827
7895
  ): Promise<z.infer<typeof ${operationName}ResponseSchema>> {
7828
- ${progressEnabled && useXHR ? `return new Promise((resolve, reject) => {
7896
+ return new Promise((resolve, reject) => {
7829
7897
  const xhr = new XMLHttpRequest()
7830
7898
 
7831
7899
  if (onProgress) {
@@ -7874,13 +7942,9 @@ async function uploadViaBackendApi(
7874
7942
  : '${this.configuration.api?.baseUrl || "http://localhost:8000"}'
7875
7943
  xhr.open('POST', \`\${baseUrl}${endpoint.path}\`)
7876
7944
  xhr.send(formData)
7877
- })` : `
7878
- // Use generated server action for type-safe upload (no progress tracking)
7879
- // Note: Server actions don't support progress tracking, use XHR for progress
7880
- const response = await ${actionName}(formData as any)
7881
- return response
7882
- `}
7883
- }` : "";
7945
+ })
7946
+ }
7947
+ ` : "";
7884
7948
  const uploadLogic = presignedEnabled ? `
7885
7949
  let fileKey: string | null = null
7886
7950
  let directUploadAttempted = false
@@ -7995,6 +8059,7 @@ async function uploadViaBackendApi(
7995
8059
  `;
7996
8060
  const content = `${imports}
7997
8061
  ${presignedHelpers}
8062
+ ${backendUploadHelper || ""}
7998
8063
 
7999
8064
  /**
8000
8065
  * Upload hook for ${endpoint.method} ${endpoint.path}
@@ -8031,7 +8096,7 @@ export function ${hookName}Upload(options?: {
8031
8096
  ${uploadLogic}
8032
8097
  },
8033
8098
 
8034
- onSuccess: (data) => {
8099
+ onSuccess: (data: z.infer<typeof ${operationName}ResponseSchema>) => {
8035
8100
  // Don't show toast here, let the page handle it
8036
8101
  options?.onSuccess?.(data)
8037
8102
 
@@ -8245,7 +8310,9 @@ let toast: {
8245
8310
  } | null = null
8246
8311
 
8247
8312
  // Lazy load toast to avoid bundling issues
8248
- async function getToast() {
8313
+ async function getToast(): Promise<{
8314
+ error: (message: string, options?: { duration?: number; description?: string }) => void
8315
+ } | null> {
8249
8316
  if (toast !== null) return toast
8250
8317
 
8251
8318
  try {
@@ -8339,24 +8406,26 @@ export async function handleAuthError(error: unknown, redirectTo?: string): Prom
8339
8406
  const toastInstance = await getToast()
8340
8407
 
8341
8408
  // Show appropriate error message based on error type
8342
- if (isInactive) {
8343
- toastInstance.error('Your account has been deactivated. Please contact support for assistance.', {
8344
- duration: 5000,
8345
- description: 'You will be redirected to the login page.',
8346
- })
8347
- } else if (apiError.status === 401) {
8348
- toastInstance.error('Your session has expired. Please sign in again.', {
8349
- duration: 4000,
8350
- })
8351
- } else if (apiError.status === 403) {
8352
- toastInstance.error('Access denied. Your account may have been suspended.', {
8353
- duration: 5000,
8354
- description: 'You will be redirected to the login page.',
8355
- })
8356
- } else {
8357
- toastInstance.error('Authentication failed. Please sign in again.', {
8358
- duration: 4000,
8359
- })
8409
+ if (toastInstance) {
8410
+ if (isInactive) {
8411
+ toastInstance.error('Your account has been deactivated. Please contact support for assistance.', {
8412
+ duration: 5000,
8413
+ description: 'You will be redirected to the login page.',
8414
+ })
8415
+ } else if (apiError.status === 401) {
8416
+ toastInstance.error('Your session has expired. Please sign in again.', {
8417
+ duration: 4000,
8418
+ })
8419
+ } else if (apiError.status === 403) {
8420
+ toastInstance.error('Access denied. Your account may have been suspended.', {
8421
+ duration: 5000,
8422
+ description: 'You will be redirected to the login page.',
8423
+ })
8424
+ } else {
8425
+ toastInstance.error('Authentication failed. Please sign in again.', {
8426
+ duration: 4000,
8427
+ })
8428
+ }
8360
8429
  }
8361
8430
 
8362
8431
  if (typeof window !== 'undefined') {
@@ -8367,7 +8436,7 @@ export async function handleAuthError(error: unknown, redirectTo?: string): Prom
8367
8436
  const { signOut } = await import('${authPath}')
8368
8437
  signOut({
8369
8438
  redirect: false,
8370
- }).catch((signOutError) => {
8439
+ }).catch((signOutError: unknown) => {
8371
8440
  console.error('[Auth Error Handler] Error during sign out (non-blocking):', signOutError)
8372
8441
  })
8373
8442
  } catch (importError) {
@@ -9356,7 +9425,8 @@ var bridgeConfigSchema = zod.z.object({
9356
9425
  logging: zod.z.boolean().default(true),
9357
9426
  validation: zod.z.boolean().default(true),
9358
9427
  mocking: zod.z.boolean().default(false),
9359
- watch: zod.z.boolean().default(false)
9428
+ watch: zod.z.boolean().default(false),
9429
+ hotReload: zod.z.boolean().default(false)
9360
9430
  }).optional(),
9361
9431
  plugins: zod.z.array(
9362
9432
  zod.z.object({
@@ -9369,6 +9439,7 @@ var bridgeConfigSchema = zod.z.object({
9369
9439
  typescript: zod.z.boolean().default(true),
9370
9440
  strict: zod.z.boolean().default(true),
9371
9441
  comments: zod.z.boolean().default(true),
9442
+ documentation: zod.z.boolean().default(true),
9372
9443
  examples: zod.z.boolean().default(false),
9373
9444
  tests: zod.z.boolean().default(false)
9374
9445
  }).optional()
@@ -9474,12 +9545,14 @@ ${errorMessages}`,
9474
9545
  logging: true,
9475
9546
  validation: true,
9476
9547
  mocking: false,
9477
- watch: false
9548
+ watch: false,
9549
+ hotReload: false
9478
9550
  },
9479
9551
  generation: {
9480
9552
  typescript: true,
9481
9553
  strict: true,
9482
9554
  comments: true,
9555
+ documentation: true,
9483
9556
  examples: false,
9484
9557
  tests: false
9485
9558
  }
@@ -9546,5 +9619,5 @@ exports.VersionChecker = VersionChecker;
9546
9619
  exports.__name = __name;
9547
9620
  exports.checkAndNotifyUpdates = checkAndNotifyUpdates;
9548
9621
  exports.createBridgeVersionChecker = createBridgeVersionChecker;
9549
- //# sourceMappingURL=chunk-RGLCJDOY.cjs.map
9550
- //# sourceMappingURL=chunk-RGLCJDOY.cjs.map
9622
+ //# sourceMappingURL=chunk-OOY4PCDK.cjs.map
9623
+ //# sourceMappingURL=chunk-OOY4PCDK.cjs.map