mulink 1.0.4 → 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.
@@ -1550,7 +1550,6 @@ export class BaseApiClient {
1550
1550
  retryDelay, retryCondition, validateResponse, skipAuth,
1551
1551
  responseSchema, requestId, [...this.middleware, ...middleware],
1552
1552
  cacheTags, revalidate, connection, updateTag
1553
- cacheTags, revalidate, connection, updateTag
1554
1553
  )
1555
1554
 
1556
1555
  requestCache.set(dedupeKey, requestPromise)
@@ -1568,7 +1567,6 @@ export class BaseApiClient {
1568
1567
  retryDelay, retryCondition, validateResponse, skipAuth,
1569
1568
  responseSchema, requestId, [...this.middleware, ...middleware],
1570
1569
  cacheTags, revalidate, connection, updateTag
1571
- cacheTags, revalidate, connection, updateTag
1572
1570
  )
1573
1571
  }
1574
1572
 
@@ -2724,7 +2722,9 @@ var SchemaGenerator = class {
2724
2722
  ].join("\n");
2725
2723
  let content = this.removeUnusedCode(contentWithImports);
2726
2724
  if (!content.includes('import { z } from "zod"') && !content.includes("import { z } from 'zod'")) {
2727
- content = 'import { z } from "zod"\n\n' + content;
2725
+ content = `import { z } from "zod"
2726
+
2727
+ ${content}`;
2728
2728
  }
2729
2729
  return {
2730
2730
  path: "schemas/index.ts",
@@ -3135,6 +3135,166 @@ var ActionGenerator = class {
3135
3135
  static {
3136
3136
  __name(this, "ActionGenerator");
3137
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
+ }
3138
3298
  buildImportPath(relativePath) {
3139
3299
  const outputDirectory = this.configuration.outputDir || "generated";
3140
3300
  const cleanPath = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
@@ -3199,8 +3359,7 @@ var ActionGenerator = class {
3199
3359
  ] : []
3200
3360
  ].filter(Boolean).join("\n");
3201
3361
  const rateLimitSetup = hasRateLimit ? `
3202
- // Rate limiting setup
3203
- const redis = new Redis({
3362
+ ${this.commentsEnabled ? "// Rate limiting setup\n" : ""}const redis = new Redis({
3204
3363
  url: process.env.UPSTASH_REDIS_REST_URL!,
3205
3364
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
3206
3365
  })
@@ -3210,60 +3369,7 @@ const ratelimit = new Ratelimit({
3210
3369
  limiter: Ratelimit.slidingWindow(10, '1 m'),
3211
3370
  analytics: true,
3212
3371
  })` : "";
3213
- const utilityFunctions = `
3214
- // Utility functions for enhanced server actions
3215
- async function getClientInfo() {
3216
- const headersList = await headers()
3217
- const userAgent = headersList.get('user-agent') || 'unknown'
3218
- const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown'
3219
-
3220
- return { userAgent, ip }
3221
- }
3222
-
3223
- async function validateAndSanitizeInput<T>(schema: z.ZodSchema<T>, input: unknown): Promise<T> {
3224
- try {
3225
- return await schema.parseAsync(input)
3226
- } catch (error) {
3227
- if (error instanceof z.ZodError) {
3228
- const errorMessages = error.issues.map(issue => {
3229
- const path = issue.path.length > 0 ? \`\${issue.path.join('.')}: \` : ''
3230
- return \`\${path}\${issue.message}\`
3231
- }).join(', ')
3232
- throw new ActionError(\`Input validation failed: \${errorMessages}\`, 'VALIDATION_ERROR')
3233
- }
3234
- throw new ActionError('Invalid input format', 'VALIDATION_ERROR')
3235
- }
3236
- }
3237
-
3238
- // Enhanced error handling with context
3239
- class ActionExecutionError extends ActionError {
3240
- constructor(
3241
- message: string,
3242
- public readonly context: {
3243
- endpoint: string
3244
- method: string
3245
- timestamp: number
3246
- },
3247
- public readonly originalError?: unknown
3248
- ) {
3249
- super(message, 'EXECUTION_ERROR')
3250
- }
3251
- }
3252
-
3253
- // Logging utility for server actions
3254
- async function logActionExecution(
3255
- action: string,
3256
- success: boolean,
3257
- duration: number,
3258
- context?: Record<string, any>
3259
- ) {
3260
- if (process.env.NODE_ENV === 'development') {
3261
- console.log(\`[ACTION] \${action} - \${success ? 'SUCCESS' : 'FAILED'} (\${duration}ms)\`, context)
3262
- }
3263
-
3264
- // In production, send to your logging service
3265
- // await analytics.track('server_action_executed', { action, success, duration, ...context })
3266
- }`;
3372
+ const utilityFunctions = this.buildUtilityFunctions();
3267
3373
  const content = `${imports}
3268
3374
  ${rateLimitSetup}
3269
3375
  ${utilityFunctions}
@@ -3280,8 +3386,8 @@ ${actions}`;
3280
3386
  }
3281
3387
  });
3282
3388
  }
3283
- const indexContent = `// Auto-generated actions index
3284
- ${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")}`;
3285
3391
  generatedFiles.push({
3286
3392
  path: "actions/index.ts",
3287
3393
  content: indexContent,
@@ -3330,54 +3436,48 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3330
3436
  body: ${operationName}RequestSchema,
3331
3437
  params: ${operationName}ParamsSchema
3332
3438
  })`;
3333
- parameterProcessing = `
3334
- // Validate and sanitize input
3335
- const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3336
- const validatedBody = body
3337
- const validatedParams = params as z.infer<typeof ${operationName}ParamsSchema>`;
3439
+ parameterProcessing = this.buildCombinedValidationSnippet(operationName, schemaName);
3338
3440
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, true, true);
3339
3441
  } else if (hasRequestBody) {
3340
3442
  schemaName = `${operationName}RequestSchema`;
3341
- parameterProcessing = `
3342
- // Validate and sanitize request body
3343
- const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3443
+ parameterProcessing = this.buildBodyValidationSnippet(schemaName);
3344
3444
  requestOptionsParams = `body: validatedBody`;
3345
3445
  } else if (hasAnyParams) {
3346
3446
  schemaName = `${operationName}ParamsSchema`;
3347
- parameterProcessing = `
3348
- // Validate and sanitize parameters
3349
- const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as z.infer<typeof ${operationName}ParamsSchema>`;
3447
+ parameterProcessing = this.buildParamsValidationSnippet(operationName, schemaName);
3350
3448
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, false, true);
3351
3449
  } else {
3352
3450
  schemaName = "z.void()";
3353
3451
  parameterProcessing = "";
3354
3452
  requestOptionsParams = "";
3355
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} }`;
3356
3458
  const clientName = requiresAuth || hasRateLimit ? "authActionClient" : "actionClientWithMeta";
3357
3459
  const rateLimitCode = hasRateLimit ? `
3358
- // Rate limiting
3359
- const { userAgent, ip } = await getClientInfo()
3360
- const identifier = \`\${ip}-\${userAgent}\`
3361
- const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3362
-
3363
- if (!success) {
3364
- throw new ActionError(
3365
- \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3366
- 'RATE_LIMIT_EXCEEDED'
3367
- )
3368
- }` : "";
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
+ }` : "";
3369
3470
  const revalidationCode = revalidationTags.length > 0 ? revalidationTags.map(
3370
- (tag) => ` updateTag('${tag}')
3371
- console.log('Updated tag: ${tag}')`
3471
+ (tag) => ` updateTag('${tag}')${this.loggingEnabled ? `
3472
+ console.log('Updated tag: ${tag}')` : ""}`
3372
3473
  ).join("\n") : "";
3373
3474
  const kebabActionName = actionName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
3374
3475
  if (isQuery) {
3375
- return `/**
3376
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3377
- * @generated from ${endpoint.method} ${endpoint.path}
3378
- * Features: React cache, input validation, error handling
3379
- */
3380
- 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(
3381
3481
  ${clientName}
3382
3482
  .metadata({
3383
3483
  name: "${kebabActionName}",
@@ -3385,7 +3485,7 @@ export const ${actionName} = cache(
3385
3485
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3386
3486
  })
3387
3487
  .schema(${schemaName})
3388
- .action(async ({ parsedInput, ctx }: { parsedInput: ${hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void"}, ctx${requiresAuth || hasRateLimit ? ": { user?: any, ratelimit?: { remaining: number } }" : "?: any"}) => {
3488
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3389
3489
  const startTime = Date.now()
3390
3490
 
3391
3491
  try {${rateLimitCode}${parameterProcessing}
@@ -3400,23 +3500,11 @@ export const ${actionName} = cache(
3400
3500
  }
3401
3501
  })
3402
3502
 
3403
- // Log successful execution
3404
- const duration = Date.now() - startTime
3405
- await logActionExecution('${actionName}', true, duration, {
3406
- method: '${endpoint.method}',
3407
- path: '${endpoint.path}'
3408
- })
3503
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 8)}
3409
3504
 
3410
3505
  return response.data
3411
3506
  } catch (error) {
3412
- const duration = Date.now() - startTime
3413
-
3414
- // Enhanced error logging
3415
- await logActionExecution('${actionName}', false, duration, {
3416
- method: '${endpoint.method}',
3417
- path: '${endpoint.path}',
3418
- error: error instanceof Error ? error.message : 'Unknown error'
3419
- })
3507
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 8)}
3420
3508
 
3421
3509
  // Throw enhanced error with context
3422
3510
  throw new ActionExecutionError(
@@ -3433,50 +3521,37 @@ export const ${actionName} = cache(
3433
3521
  )`;
3434
3522
  }
3435
3523
  const redirectCode = `
3436
- // Handle potential redirects based on response
3437
- 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')) {
3438
3525
  const location = response.headers.get('location')!
3439
3526
  redirect(location)
3440
3527
  }`;
3441
3528
  const streamingCode = `
3442
- // Handle streaming responses
3443
- if (response.headers.get('content-type')?.includes('text/stream')) {
3444
- // Process streaming response
3445
- 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
3446
3531
  }`;
3447
3532
  const fileUploadCode = hasFileUpload ? uploadStrategy === "external" && uploadProvider === "vercel-blob" ? `
3448
- // Handle file uploads with Vercel Blob
3449
- 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) {
3450
3534
  const file = (validatedBody as any).file as File
3451
3535
  const blob = await put(file.name, file, { access: 'public' })
3452
3536
  Object.assign(validatedBody, { fileUrl: blob.url })
3453
3537
  }` : uploadStrategy === "external" && uploadProvider === "uploadthing" ? `
3454
- // Handle file uploads with UploadThing
3455
- // Note: UploadThing requires a separate route handler setup
3456
- // See: https://docs.uploadthing.com/getting-started/appdir
3457
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3458
- // UploadThing file handling should be done via their route handler
3459
- // This is a placeholder - implement according to UploadThing docs
3460
- }` : `
3461
- // Handle file uploads with standard FormData
3462
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3463
- // Standard file upload handling
3464
- const file = (validatedBody as any).file as File
3465
- // Process file with compression and validation if enabled
3466
- }` : "";
3467
- return `/**
3468
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3469
- * @generated from ${endpoint.method} ${endpoint.path}
3470
- * Features: Input validation, revalidation, error handling
3471
- */
3472
- 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}
3473
3548
  .metadata({
3474
3549
  name: "${kebabActionName}",
3475
3550
  requiresAuth: ${requiresAuth}${hasRateLimit ? `,
3476
3551
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3477
3552
  })
3478
3553
  .schema(${schemaName})
3479
- .action(async ({ parsedInput, ctx }: { parsedInput: ${hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void"}, ctx${requiresAuth || hasRateLimit ? ": { user?: any, ratelimit?: { remaining: number } }" : "?: any"}) => {
3554
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3480
3555
  const startTime = Date.now()
3481
3556
 
3482
3557
  try {${rateLimitCode}${parameterProcessing}${fileUploadCode}
@@ -3491,33 +3566,13 @@ export const ${actionName} = ${clientName}
3491
3566
  }
3492
3567
  })${streamingCode}${redirectCode}
3493
3568
 
3494
- // Revalidate cache after successful mutation
3495
- ${revalidationCode}
3496
-
3497
- // Background tasks (Next.js 15 feature)
3498
- ${isStreaming ? `after(async () => {
3499
- // Perform background tasks after response is sent
3500
- await logActionExecution('${actionName}', true, Date.now() - startTime, {
3501
- method: '${endpoint.method}',
3502
- path: '${endpoint.path}'
3503
- })
3504
- })` : `// Log successful execution
3505
- const duration = Date.now() - startTime
3506
- await logActionExecution('${actionName}', true, duration, {
3507
- method: '${endpoint.method}',
3508
- path: '${endpoint.path}'
3509
- })`}
3569
+ ${revalidationCode ? `${this.commentLine("Revalidate cache after successful mutation", 6)}${revalidationCode}
3570
+ ` : ""}
3571
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 6, isStreaming)}
3510
3572
 
3511
3573
  return response.data
3512
3574
  } catch (error) {
3513
- const duration = Date.now() - startTime
3514
-
3515
- // Enhanced error logging
3516
- await logActionExecution('${actionName}', false, duration, {
3517
- method: '${endpoint.method}',
3518
- path: '${endpoint.path}',
3519
- error: error instanceof Error ? error.message : 'Unknown error'
3520
- })
3575
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 6)}
3521
3576
 
3522
3577
  // Throw enhanced error with context
3523
3578
  throw new ActionExecutionError(
@@ -3676,7 +3731,6 @@ var HookGenerator = class {
3676
3731
  if (hasInfiniteQueries) reactQueryImports.push("useInfiniteQuery");
3677
3732
  const reactImports = [];
3678
3733
  if (hasMutations) reactImports.push("useOptimistic", "useTransition");
3679
- if (hasQueries) reactImports.push("useCallback");
3680
3734
  const imports = [
3681
3735
  "'use client'",
3682
3736
  "",
@@ -3788,19 +3842,19 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3788
3842
  * @returns useQuery result with data of type ${returnType}
3789
3843
  */
3790
3844
  export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join(", ")}, ` : ""}options?: ${optionsType}) {
3791
- const [searchParams, setSearchParams] = useQueryStates(searchParamsParser)
3845
+ const [searchParams] = useQueryStates(searchParamsParser)
3792
3846
  const { initialData, ...restOptions } = options ?? {}
3793
3847
 
3794
3848
  return useQuery({
3795
3849
  queryKey: [...${queryKey}, searchParams],
3796
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3850
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3797
3851
  try {
3798
3852
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams }")})
3799
3853
  return result
3800
3854
  } catch (error) {
3801
3855
  handleActionError(error)
3802
3856
  }
3803
- }, [searchParams]),
3857
+ },
3804
3858
  staleTime: ${staleTime},
3805
3859
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3806
3860
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3832,14 +3886,14 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3832
3886
 
3833
3887
  return useInfiniteQuery({
3834
3888
  queryKey: [...${queryKey}, 'infinite', searchParams],
3835
- queryFn: useCallback(async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3889
+ queryFn: async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3836
3890
  try {
3837
3891
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams, page: pageParam, limit: searchParams.limit }")})
3838
3892
  return result
3839
3893
  } catch (error) {
3840
3894
  handleActionError(error)
3841
3895
  }
3842
- }, [searchParams]),
3896
+ },
3843
3897
  getNextPageParam: (lastPage: ${returnType}, allPages: ${returnType}[]) => {
3844
3898
  if (lastPage?.hasMore || (Array.isArray(lastPage) && lastPage.length === searchParams.limit)) {
3845
3899
  return allPages.length + 1
@@ -3869,10 +3923,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3869
3923
 
3870
3924
  return useSuspenseQuery({
3871
3925
  queryKey: ${queryKey},
3872
- queryFn: useCallback(async () => {
3926
+ queryFn: async () => {
3873
3927
  const result = await ${actionName}(${actionCallParams})
3874
3928
  return result
3875
- }, []),
3929
+ },
3876
3930
  staleTime: ${staleTime},
3877
3931
  initialData: initialData as ${returnType} | undefined,
3878
3932
  ...restOptions
@@ -3889,14 +3943,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3889
3943
 
3890
3944
  return useQuery({
3891
3945
  queryKey: ${queryKey},
3892
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3946
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3893
3947
  try {
3894
3948
  const result = await ${actionName}(${actionCallParams})
3895
3949
  return result
3896
3950
  } catch (error) {
3897
3951
  handleActionError(error)
3898
3952
  }
3899
- }, []),
3953
+ },
3900
3954
  staleTime: ${staleTime},
3901
3955
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3902
3956
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3927,10 +3981,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3927
3981
 
3928
3982
  return useSuspenseQuery({
3929
3983
  queryKey: ${queryKey},
3930
- queryFn: useCallback(async () => {
3984
+ queryFn: async () => {
3931
3985
  const result = await ${actionName}(${actionCallParams})
3932
3986
  return result
3933
- }, []),
3987
+ },
3934
3988
  staleTime: ${staleTime},
3935
3989
  initialData: initialData as ${returnType} | undefined,
3936
3990
  ...restOptions
@@ -3963,8 +4017,6 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3963
4017
  const relatedQueries = this.findRelatedQueries(endpoint, context);
3964
4018
  const invalidationCode = this.buildInvalidationCode(endpoint, relatedQueries, invalidationTags);
3965
4019
  const cancelQueriesCode = this.buildCancelQueriesCode(endpoint, relatedQueries);
3966
- const snapshotCode = this.buildSnapshotCode(endpoint, relatedQueries);
3967
- const rollbackCode = this.buildRollbackCode(endpoint, relatedQueries);
3968
4020
  return `/**
3969
4021
  * Optimized mutation hook for ${endpoint.method} ${endpoint.path}
3970
4022
  * Features: Optimistic updates, smart invalidation, error handling
@@ -3985,7 +4037,7 @@ export function ${hookName}(options?: {
3985
4037
  mutationFn: async (variables: ${inputType}): Promise<${outputType}> => {
3986
4038
  try {
3987
4039
  const result = await ${actionName}(${variablesParam === "undefined" ? "" : "variables"})
3988
- return result.data || ({} as ${outputType})
4040
+ return (result ?? ({} as ${outputType}))
3989
4041
  } catch (error) {
3990
4042
  handleActionError(error)
3991
4043
  }
@@ -3993,16 +4045,14 @@ export function ${hookName}(options?: {
3993
4045
 
3994
4046
  onMutate: async (variables: ${inputType}) => {
3995
4047
  ${cancelQueriesCode}
3996
-
3997
- ${snapshotCode}
3998
-
4048
+
3999
4049
  // Optimistic update (if provided)
4000
4050
  if (options?.optimisticUpdate) {
4001
4051
  const optimisticValue = options.optimisticUpdate(variables)
4002
4052
  setOptimisticData(optimisticValue)
4003
4053
  }
4004
4054
 
4005
- return { /* Snapshot handled via query invalidation */ }
4055
+ return {}
4006
4056
  },
4007
4057
 
4008
4058
  onSuccess: (data, variables) => {
@@ -4011,17 +4061,11 @@ ${snapshotCode}
4011
4061
  toast.success('${this.getSuccessMessage(endpoint)}')
4012
4062
  }
4013
4063
 
4014
- // Invalidate and refetch related queries
4015
- ${invalidationCode}
4016
-
4017
4064
  // Custom success handler
4018
4065
  options?.onSuccess?.(data, variables)
4019
4066
  },
4020
4067
 
4021
- onError: (error: Error, variables: ${inputType}, context: any) => {
4022
- // Rollback optimistic update
4023
- ${rollbackCode}
4024
-
4068
+ onError: (error: Error, variables: ${inputType}) => {
4025
4069
  // Show error toast
4026
4070
  if (options?.showToast !== false) {
4027
4071
  toast.error(error.message || '${this.getErrorMessage(endpoint)}')
@@ -4031,7 +4075,7 @@ ${rollbackCode}
4031
4075
  options?.onError?.(error as Error, variables)
4032
4076
  },
4033
4077
 
4034
- onSettled: () => {
4078
+ onSettled: async () => {
4035
4079
  // Always refetch after error or success
4036
4080
  ${invalidationCode}
4037
4081
  }
@@ -4057,100 +4101,81 @@ ${invalidationCode}
4057
4101
  return [];
4058
4102
  }
4059
4103
  const relatedQueries = [];
4060
- const mutationPath = endpoint.path;
4104
+ const seen = /* @__PURE__ */ new Set();
4061
4105
  const mutationTag = endpoint.tags[0];
4062
- const pathSegments = mutationPath.split("/").filter(Boolean);
4106
+ const mutationSegments = this.getResourceSegments(endpoint.path);
4107
+ const mutationResource = mutationSegments[0];
4063
4108
  context.schema.endpoints.forEach((queryEndpoint) => {
4064
4109
  if (queryEndpoint.method !== "GET") return;
4065
- const queryPath = queryEndpoint.path;
4110
+ const queryId = queryEndpoint.operationId || queryEndpoint.id;
4111
+ if (seen.has(queryId)) return;
4066
4112
  const queryTag = queryEndpoint.tags[0];
4067
- if (queryTag === mutationTag) {
4113
+ const querySegments = this.getResourceSegments(queryEndpoint.path);
4114
+ const queryResource = querySegments[0];
4115
+ if (mutationTag && queryTag === mutationTag) {
4068
4116
  relatedQueries.push(queryEndpoint);
4117
+ seen.add(queryId);
4069
4118
  return;
4070
4119
  }
4071
- const querySegments = queryPath.split("/").filter(Boolean);
4072
- if (pathSegments.length >= 2 && querySegments.length >= 2) {
4073
- const baseMatch = pathSegments.slice(0, 2).join("/") === querySegments.slice(0, 2).join("/");
4074
- if (baseMatch) {
4075
- relatedQueries.push(queryEndpoint);
4076
- }
4120
+ if (mutationResource && queryResource && mutationResource === queryResource) {
4121
+ relatedQueries.push(queryEndpoint);
4122
+ seen.add(queryId);
4077
4123
  }
4078
4124
  });
4079
4125
  return relatedQueries;
4080
4126
  }
4127
+ getResourceSegments(path3) {
4128
+ return path3.split("/").filter(Boolean).filter((segment) => segment.toLowerCase() !== "api" && !/^v\d+/i.test(segment));
4129
+ }
4081
4130
  /**
4082
4131
  * Build invalidation code for related queries
4083
4132
  */
4084
4133
  buildInvalidationCode(endpoint, relatedQueries, invalidationTags) {
4085
- const invalidations = [];
4134
+ const invalidations = /* @__PURE__ */ new Set();
4086
4135
  relatedQueries.forEach((queryEndpoint) => {
4087
- const queryKey = this.generateQueryKey(queryEndpoint);
4088
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
4136
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4137
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4089
4138
  });
4090
4139
  invalidationTags.forEach((tag) => {
4091
- if (!invalidations.some((inv) => inv.includes(`'${tag}'`))) {
4092
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4093
- }
4140
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4094
4141
  });
4095
- if (invalidations.length === 0) {
4142
+ if (invalidations.size === 0) {
4096
4143
  const inferredKey = this.inferQueryKeyFromPath(endpoint);
4097
4144
  if (inferredKey) {
4098
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4145
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4099
4146
  }
4100
4147
  }
4101
- 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
+ ])`;
4102
4158
  }
4103
4159
  /**
4104
4160
  * Build cancel queries code
4105
4161
  * Uses query key patterns to cancel all related queries regardless of parameters
4106
4162
  */
4107
4163
  buildCancelQueriesCode(endpoint, relatedQueries) {
4108
- const cancels = [];
4164
+ const cancels = /* @__PURE__ */ new Set();
4109
4165
  relatedQueries.forEach((queryEndpoint) => {
4110
- const queryKeyPrefix = `'${toActionName(queryEndpoint.operationId || queryEndpoint.id)}'`;
4111
- cancels.push(` await queryClient.cancelQueries({ queryKey: [${queryKeyPrefix}] })`);
4166
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4167
+ cancels.add(`queryClient.cancelQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4112
4168
  });
4113
- return cancels.length > 0 ? cancels.join("\n") : " // No queries to cancel";
4114
- }
4115
- /**
4116
- * Build snapshot code for rollback
4117
- * Note: We can't snapshot specific queries with parameters in onMutate scope
4118
- * Instead, we'll invalidate all related queries on error
4119
- */
4120
- buildSnapshotCode(endpoint, relatedQueries) {
4121
- if (relatedQueries.length === 0) {
4122
- return " // No queries to snapshot";
4123
- }
4124
- return " // Snapshot handled via query invalidation";
4125
- }
4126
- /**
4127
- * Build rollback code
4128
- */
4129
- buildRollbackCode(endpoint, relatedQueries) {
4130
- if (relatedQueries.length === 0) {
4131
- return " // No queries to rollback";
4169
+ if (cancels.size === 0) {
4170
+ return " // No queries to cancel";
4132
4171
  }
4133
- const rollbacks = [];
4134
- relatedQueries.forEach((queryEndpoint) => {
4135
- const queryKey = this.generateQueryKey(queryEndpoint);
4136
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4137
- rollbacks.push(` if (context?.${varName}) {
4138
- queryClient.setQueryData(${queryKey}, context.${varName})
4139
- }`);
4140
- });
4141
- return rollbacks.join("\n");
4142
- }
4143
- /**
4144
- * Get snapshot return names for context
4145
- */
4146
- getSnapshotReturnNames(relatedQueries) {
4147
- if (relatedQueries.length === 0) {
4148
- return "previousData: null";
4172
+ if (cancels.size === 1) {
4173
+ const [statement] = Array.from(cancels);
4174
+ return ` await ${statement}`;
4149
4175
  }
4150
- return relatedQueries.map((queryEndpoint) => {
4151
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4152
- return `${varName}`;
4153
- }).join(", ");
4176
+ return ` await Promise.all([
4177
+ ${Array.from(cancels).join(",\n ")}
4178
+ ])`;
4154
4179
  }
4155
4180
  /**
4156
4181
  * Infer query key from mutation path
@@ -9400,7 +9425,8 @@ var bridgeConfigSchema = zod.z.object({
9400
9425
  logging: zod.z.boolean().default(true),
9401
9426
  validation: zod.z.boolean().default(true),
9402
9427
  mocking: zod.z.boolean().default(false),
9403
- watch: zod.z.boolean().default(false)
9428
+ watch: zod.z.boolean().default(false),
9429
+ hotReload: zod.z.boolean().default(false)
9404
9430
  }).optional(),
9405
9431
  plugins: zod.z.array(
9406
9432
  zod.z.object({
@@ -9413,6 +9439,7 @@ var bridgeConfigSchema = zod.z.object({
9413
9439
  typescript: zod.z.boolean().default(true),
9414
9440
  strict: zod.z.boolean().default(true),
9415
9441
  comments: zod.z.boolean().default(true),
9442
+ documentation: zod.z.boolean().default(true),
9416
9443
  examples: zod.z.boolean().default(false),
9417
9444
  tests: zod.z.boolean().default(false)
9418
9445
  }).optional()
@@ -9518,12 +9545,14 @@ ${errorMessages}`,
9518
9545
  logging: true,
9519
9546
  validation: true,
9520
9547
  mocking: false,
9521
- watch: false
9548
+ watch: false,
9549
+ hotReload: false
9522
9550
  },
9523
9551
  generation: {
9524
9552
  typescript: true,
9525
9553
  strict: true,
9526
9554
  comments: true,
9555
+ documentation: true,
9527
9556
  examples: false,
9528
9557
  tests: false
9529
9558
  }
@@ -9590,5 +9619,5 @@ exports.VersionChecker = VersionChecker;
9590
9619
  exports.__name = __name;
9591
9620
  exports.checkAndNotifyUpdates = checkAndNotifyUpdates;
9592
9621
  exports.createBridgeVersionChecker = createBridgeVersionChecker;
9593
- //# sourceMappingURL=chunk-GKRRC2YD.cjs.map
9594
- //# sourceMappingURL=chunk-GKRRC2YD.cjs.map
9622
+ //# sourceMappingURL=chunk-OOY4PCDK.cjs.map
9623
+ //# sourceMappingURL=chunk-OOY4PCDK.cjs.map