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.
@@ -1544,7 +1544,6 @@ export class BaseApiClient {
1544
1544
  retryDelay, retryCondition, validateResponse, skipAuth,
1545
1545
  responseSchema, requestId, [...this.middleware, ...middleware],
1546
1546
  cacheTags, revalidate, connection, updateTag
1547
- cacheTags, revalidate, connection, updateTag
1548
1547
  )
1549
1548
 
1550
1549
  requestCache.set(dedupeKey, requestPromise)
@@ -1562,7 +1561,6 @@ export class BaseApiClient {
1562
1561
  retryDelay, retryCondition, validateResponse, skipAuth,
1563
1562
  responseSchema, requestId, [...this.middleware, ...middleware],
1564
1563
  cacheTags, revalidate, connection, updateTag
1565
- cacheTags, revalidate, connection, updateTag
1566
1564
  )
1567
1565
  }
1568
1566
 
@@ -2718,7 +2716,9 @@ var SchemaGenerator = class {
2718
2716
  ].join("\n");
2719
2717
  let content = this.removeUnusedCode(contentWithImports);
2720
2718
  if (!content.includes('import { z } from "zod"') && !content.includes("import { z } from 'zod'")) {
2721
- content = 'import { z } from "zod"\n\n' + content;
2719
+ content = `import { z } from "zod"
2720
+
2721
+ ${content}`;
2722
2722
  }
2723
2723
  return {
2724
2724
  path: "schemas/index.ts",
@@ -3129,6 +3129,166 @@ var ActionGenerator = class {
3129
3129
  static {
3130
3130
  __name(this, "ActionGenerator");
3131
3131
  }
3132
+ get loggingEnabled() {
3133
+ return this.configuration.dev?.logging !== false;
3134
+ }
3135
+ get validationEnabled() {
3136
+ return this.configuration.dev?.validation !== false;
3137
+ }
3138
+ get commentsEnabled() {
3139
+ return this.configuration.generation?.comments !== false;
3140
+ }
3141
+ get documentationEnabled() {
3142
+ return this.configuration.generation?.documentation !== false;
3143
+ }
3144
+ commentLine(text, indent = 2) {
3145
+ if (!this.commentsEnabled) {
3146
+ return "";
3147
+ }
3148
+ return `${" ".repeat(indent)}// ${text}
3149
+ `;
3150
+ }
3151
+ buildDocBlock(endpoint, featuresDescription) {
3152
+ if (!this.documentationEnabled) {
3153
+ return "";
3154
+ }
3155
+ const summary = endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`;
3156
+ return `/**
3157
+ * ${summary}
3158
+ * @generated from ${endpoint.method} ${endpoint.path}
3159
+ * Features: ${featuresDescription}
3160
+ */
3161
+ `;
3162
+ }
3163
+ buildUtilityFunctions() {
3164
+ const parts = [];
3165
+ if (this.commentsEnabled) {
3166
+ parts.push("// Utility functions for enhanced server actions");
3167
+ }
3168
+ parts.push(`async function getClientInfo() {
3169
+ const headersList = await headers()
3170
+ const userAgent = headersList.get('user-agent') || 'unknown'
3171
+ const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown'
3172
+
3173
+ return { userAgent, ip }
3174
+ }`);
3175
+ if (this.validationEnabled) {
3176
+ parts.push(`async function validateAndSanitizeInput<T>(schema: z.ZodSchema<T>, input: unknown): Promise<T> {
3177
+ try {
3178
+ return await schema.parseAsync(input)
3179
+ } catch (error) {
3180
+ if (error instanceof z.ZodError) {
3181
+ const errorMessages = error.issues.map(issue => {
3182
+ const path = issue.path.length > 0 ? \`\${issue.path.join('.')}: \` : ''
3183
+ return \`\${path}\${issue.message}\`
3184
+ }).join(', ')
3185
+ throw new ActionError(\`Input validation failed: \${errorMessages}\`, 'VALIDATION_ERROR')
3186
+ }
3187
+ throw new ActionError('Invalid input format', 'VALIDATION_ERROR')
3188
+ }
3189
+ }`);
3190
+ }
3191
+ const actionErrorBlock = `${this.commentsEnabled ? "// Enhanced error handling with context\n" : ""}class ActionExecutionError extends ActionError {
3192
+ constructor(
3193
+ message: string,
3194
+ public readonly context: {
3195
+ endpoint: string
3196
+ method: string
3197
+ timestamp: number
3198
+ },
3199
+ public readonly originalError?: unknown
3200
+ ) {
3201
+ super(message, 'EXECUTION_ERROR')
3202
+ }
3203
+ }`;
3204
+ parts.push(actionErrorBlock);
3205
+ if (this.loggingEnabled) {
3206
+ const loggingHeader = this.commentsEnabled ? "// Logging utility for server actions\n" : "";
3207
+ parts.push(`${loggingHeader}async function logActionExecution(
3208
+ action: string,
3209
+ success: boolean,
3210
+ duration: number,
3211
+ context?: Record<string, any>
3212
+ ) {
3213
+ if (process.env.NODE_ENV === 'development') {
3214
+ console.log(\`[ACTION] \${action} - \${success ? 'SUCCESS' : 'FAILED'} (\${duration}ms)\`, context)
3215
+ }
3216
+
3217
+ ${this.commentsEnabled ? " // In production, send to your logging service\n // await analytics.track('server_action_executed', { action, success, duration, ...context })" : ""}
3218
+ }`);
3219
+ }
3220
+ return parts.join("\n\n");
3221
+ }
3222
+ buildCombinedValidationSnippet(operationName, schemaName) {
3223
+ if (this.validationEnabled) {
3224
+ return `
3225
+ ${this.commentLine("Validate and sanitize input payload", 6)} const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3226
+ const validatedBody = body
3227
+ const validatedParams = params as z.infer<typeof ${operationName}ParamsSchema>`;
3228
+ }
3229
+ return `
3230
+ ${this.commentLine("Runtime validation disabled - trusting provided payload", 6)} const { body, params } = parsedInput as {
3231
+ body: z.infer<typeof ${operationName}RequestSchema>
3232
+ params: z.infer<typeof ${operationName}ParamsSchema>
3233
+ }
3234
+ const validatedBody = body
3235
+ const validatedParams = params`;
3236
+ }
3237
+ buildBodyValidationSnippet(schemaName) {
3238
+ if (schemaName === "z.void()") {
3239
+ return "";
3240
+ }
3241
+ if (this.validationEnabled) {
3242
+ return `
3243
+ ${this.commentLine("Validate and sanitize request body", 6)} const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3244
+ }
3245
+ return `
3246
+ ${this.commentLine("Runtime validation disabled - trusting request body", 6)} const validatedBody = parsedInput as z.infer<typeof ${schemaName}>`;
3247
+ }
3248
+ buildParamsValidationSnippet(operationName, schemaName) {
3249
+ if (schemaName === "z.void()") {
3250
+ return "";
3251
+ }
3252
+ if (this.validationEnabled) {
3253
+ return `
3254
+ ${this.commentLine("Validate and sanitize parameters", 6)} const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as z.infer<typeof ${operationName}ParamsSchema>`;
3255
+ }
3256
+ return `
3257
+ ${this.commentLine("Runtime validation disabled - trusting parameters", 6)} const validatedParams = parsedInput as z.infer<typeof ${operationName}ParamsSchema>`;
3258
+ }
3259
+ buildSuccessLoggingBlock(actionName, endpoint, indent, isStreaming = false) {
3260
+ if (!this.loggingEnabled) {
3261
+ return "";
3262
+ }
3263
+ if (isStreaming) {
3264
+ return `
3265
+ ${this.commentLine("Background tasks (Next.js 15 feature)", indent)}${" ".repeat(indent)}after(async () => {
3266
+ ${this.commentLine("Perform background tasks after response is sent", indent + 2)}${" ".repeat(indent + 2)}await logActionExecution('${actionName}', true, Date.now() - startTime, {
3267
+ ${" ".repeat(indent + 4)}method: '${endpoint.method}',
3268
+ ${" ".repeat(indent + 4)}path: '${endpoint.path}'
3269
+ ${" ".repeat(indent + 2)}})
3270
+ ${" ".repeat(indent)}})`;
3271
+ }
3272
+ return `
3273
+ ${this.commentLine("Log successful execution", indent)}${" ".repeat(indent)}const duration = Date.now() - startTime
3274
+ ${" ".repeat(indent)}await logActionExecution('${actionName}', true, duration, {
3275
+ ${" ".repeat(indent + 2)}method: '${endpoint.method}',
3276
+ ${" ".repeat(indent + 2)}path: '${endpoint.path}'
3277
+ ${" ".repeat(indent)}})`;
3278
+ }
3279
+ buildErrorLoggingBlock(actionName, endpoint, indent) {
3280
+ if (!this.loggingEnabled) {
3281
+ return "";
3282
+ }
3283
+ return `
3284
+ ${" ".repeat(indent)}const duration = Date.now() - startTime
3285
+
3286
+ ${this.commentLine("Enhanced error logging", indent)}${" ".repeat(indent)}await logActionExecution('${actionName}', false, duration, {
3287
+ ${" ".repeat(indent + 2)}method: '${endpoint.method}',
3288
+ ${" ".repeat(indent + 2)}path: '${endpoint.path}',
3289
+ ${" ".repeat(indent + 2)}error: error instanceof Error ? error.message : 'Unknown error'
3290
+ ${" ".repeat(indent)}})`;
3291
+ }
3132
3292
  buildImportPath(relativePath) {
3133
3293
  const outputDirectory = this.configuration.outputDir || "generated";
3134
3294
  const cleanPath = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
@@ -3193,8 +3353,7 @@ var ActionGenerator = class {
3193
3353
  ] : []
3194
3354
  ].filter(Boolean).join("\n");
3195
3355
  const rateLimitSetup = hasRateLimit ? `
3196
- // Rate limiting setup
3197
- const redis = new Redis({
3356
+ ${this.commentsEnabled ? "// Rate limiting setup\n" : ""}const redis = new Redis({
3198
3357
  url: process.env.UPSTASH_REDIS_REST_URL!,
3199
3358
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
3200
3359
  })
@@ -3204,60 +3363,7 @@ const ratelimit = new Ratelimit({
3204
3363
  limiter: Ratelimit.slidingWindow(10, '1 m'),
3205
3364
  analytics: true,
3206
3365
  })` : "";
3207
- const utilityFunctions = `
3208
- // Utility functions for enhanced server actions
3209
- async function getClientInfo() {
3210
- const headersList = await headers()
3211
- const userAgent = headersList.get('user-agent') || 'unknown'
3212
- const ip = headersList.get('x-forwarded-for') || headersList.get('x-real-ip') || 'unknown'
3213
-
3214
- return { userAgent, ip }
3215
- }
3216
-
3217
- async function validateAndSanitizeInput<T>(schema: z.ZodSchema<T>, input: unknown): Promise<T> {
3218
- try {
3219
- return await schema.parseAsync(input)
3220
- } catch (error) {
3221
- if (error instanceof z.ZodError) {
3222
- const errorMessages = error.issues.map(issue => {
3223
- const path = issue.path.length > 0 ? \`\${issue.path.join('.')}: \` : ''
3224
- return \`\${path}\${issue.message}\`
3225
- }).join(', ')
3226
- throw new ActionError(\`Input validation failed: \${errorMessages}\`, 'VALIDATION_ERROR')
3227
- }
3228
- throw new ActionError('Invalid input format', 'VALIDATION_ERROR')
3229
- }
3230
- }
3231
-
3232
- // Enhanced error handling with context
3233
- class ActionExecutionError extends ActionError {
3234
- constructor(
3235
- message: string,
3236
- public readonly context: {
3237
- endpoint: string
3238
- method: string
3239
- timestamp: number
3240
- },
3241
- public readonly originalError?: unknown
3242
- ) {
3243
- super(message, 'EXECUTION_ERROR')
3244
- }
3245
- }
3246
-
3247
- // Logging utility for server actions
3248
- async function logActionExecution(
3249
- action: string,
3250
- success: boolean,
3251
- duration: number,
3252
- context?: Record<string, any>
3253
- ) {
3254
- if (process.env.NODE_ENV === 'development') {
3255
- console.log(\`[ACTION] \${action} - \${success ? 'SUCCESS' : 'FAILED'} (\${duration}ms)\`, context)
3256
- }
3257
-
3258
- // In production, send to your logging service
3259
- // await analytics.track('server_action_executed', { action, success, duration, ...context })
3260
- }`;
3366
+ const utilityFunctions = this.buildUtilityFunctions();
3261
3367
  const content = `${imports}
3262
3368
  ${rateLimitSetup}
3263
3369
  ${utilityFunctions}
@@ -3274,8 +3380,8 @@ ${actions}`;
3274
3380
  }
3275
3381
  });
3276
3382
  }
3277
- const indexContent = `// Auto-generated actions index
3278
- ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier(tag)}'`).join("\n")}`;
3383
+ const indexHeader = this.commentsEnabled ? "// Auto-generated actions index\n" : "";
3384
+ const indexContent = `${indexHeader}${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier(tag)}'`).join("\n")}`;
3279
3385
  generatedFiles.push({
3280
3386
  path: "actions/index.ts",
3281
3387
  content: indexContent,
@@ -3324,54 +3430,48 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3324
3430
  body: ${operationName}RequestSchema,
3325
3431
  params: ${operationName}ParamsSchema
3326
3432
  })`;
3327
- parameterProcessing = `
3328
- // Validate and sanitize input
3329
- const { body, params } = await validateAndSanitizeInput(${schemaName}, parsedInput)
3330
- const validatedBody = body
3331
- const validatedParams = params as z.infer<typeof ${operationName}ParamsSchema>`;
3433
+ parameterProcessing = this.buildCombinedValidationSnippet(operationName, schemaName);
3332
3434
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, true, true);
3333
3435
  } else if (hasRequestBody) {
3334
3436
  schemaName = `${operationName}RequestSchema`;
3335
- parameterProcessing = `
3336
- // Validate and sanitize request body
3337
- const validatedBody = await validateAndSanitizeInput(${schemaName}, parsedInput)`;
3437
+ parameterProcessing = this.buildBodyValidationSnippet(schemaName);
3338
3438
  requestOptionsParams = `body: validatedBody`;
3339
3439
  } else if (hasAnyParams) {
3340
3440
  schemaName = `${operationName}ParamsSchema`;
3341
- parameterProcessing = `
3342
- // Validate and sanitize parameters
3343
- const validatedParams = await validateAndSanitizeInput(${schemaName}, parsedInput) as z.infer<typeof ${operationName}ParamsSchema>`;
3441
+ parameterProcessing = this.buildParamsValidationSnippet(operationName, schemaName);
3344
3442
  requestOptionsParams = this.buildRequestOptions(pathParameters, queryParameters, false, true);
3345
3443
  } else {
3346
3444
  schemaName = "z.void()";
3347
3445
  parameterProcessing = "";
3348
3446
  requestOptionsParams = "";
3349
3447
  }
3448
+ const parsedInputType = hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void";
3449
+ const ctxType = requiresAuth || hasRateLimit ? "{ user?: any; ratelimit?: { remaining: number } }" : "any";
3450
+ const ctxDeclaration = requiresAuth || hasRateLimit ? `ctx: ${ctxType}` : "ctx?: any";
3451
+ const actionArgsType = `{ parsedInput: ${parsedInputType}; ${ctxDeclaration} }`;
3350
3452
  const clientName = requiresAuth || hasRateLimit ? "authActionClient" : "actionClientWithMeta";
3351
3453
  const rateLimitCode = hasRateLimit ? `
3352
- // Rate limiting
3353
- const { userAgent, ip } = await getClientInfo()
3354
- const identifier = \`\${ip}-\${userAgent}\`
3355
- const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3356
-
3357
- if (!success) {
3358
- throw new ActionError(
3359
- \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3360
- 'RATE_LIMIT_EXCEEDED'
3361
- )
3362
- }` : "";
3454
+ ${this.commentLine("Rate limiting", 6)} const { userAgent, ip } = await getClientInfo()
3455
+ const identifier = \`\${ip}-\${userAgent}\`
3456
+ const { success, limit, reset, remaining } = await ratelimit.limit(identifier)
3457
+
3458
+ if (!success) {
3459
+ throw new ActionError(
3460
+ \`Rate limit exceeded. Try again in \${Math.round((reset - Date.now()) / 1000)} seconds.\`,
3461
+ 'RATE_LIMIT_EXCEEDED'
3462
+ )
3463
+ }` : "";
3363
3464
  const revalidationCode = revalidationTags.length > 0 ? revalidationTags.map(
3364
- (tag) => ` updateTag('${tag}')
3365
- console.log('Updated tag: ${tag}')`
3465
+ (tag) => ` updateTag('${tag}')${this.loggingEnabled ? `
3466
+ console.log('Updated tag: ${tag}')` : ""}`
3366
3467
  ).join("\n") : "";
3367
3468
  const kebabActionName = actionName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
3368
3469
  if (isQuery) {
3369
- return `/**
3370
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3371
- * @generated from ${endpoint.method} ${endpoint.path}
3372
- * Features: React cache, input validation, error handling
3373
- */
3374
- export const ${actionName} = cache(
3470
+ const docBlock = this.buildDocBlock(
3471
+ endpoint,
3472
+ "React cache, input validation, error handling"
3473
+ );
3474
+ return `${docBlock}export const ${actionName} = cache(
3375
3475
  ${clientName}
3376
3476
  .metadata({
3377
3477
  name: "${kebabActionName}",
@@ -3379,7 +3479,7 @@ export const ${actionName} = cache(
3379
3479
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3380
3480
  })
3381
3481
  .schema(${schemaName})
3382
- .action(async ({ parsedInput, ctx }: { parsedInput: ${hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void"}, ctx${requiresAuth || hasRateLimit ? ": { user?: any, ratelimit?: { remaining: number } }" : "?: any"}) => {
3482
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3383
3483
  const startTime = Date.now()
3384
3484
 
3385
3485
  try {${rateLimitCode}${parameterProcessing}
@@ -3394,23 +3494,11 @@ export const ${actionName} = cache(
3394
3494
  }
3395
3495
  })
3396
3496
 
3397
- // Log successful execution
3398
- const duration = Date.now() - startTime
3399
- await logActionExecution('${actionName}', true, duration, {
3400
- method: '${endpoint.method}',
3401
- path: '${endpoint.path}'
3402
- })
3497
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 8)}
3403
3498
 
3404
3499
  return response.data
3405
3500
  } catch (error) {
3406
- const duration = Date.now() - startTime
3407
-
3408
- // Enhanced error logging
3409
- await logActionExecution('${actionName}', false, duration, {
3410
- method: '${endpoint.method}',
3411
- path: '${endpoint.path}',
3412
- error: error instanceof Error ? error.message : 'Unknown error'
3413
- })
3501
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 8)}
3414
3502
 
3415
3503
  // Throw enhanced error with context
3416
3504
  throw new ActionExecutionError(
@@ -3427,50 +3515,37 @@ export const ${actionName} = cache(
3427
3515
  )`;
3428
3516
  }
3429
3517
  const redirectCode = `
3430
- // Handle potential redirects based on response
3431
- if (response.status === 201 && response.headers.get('location')) {
3518
+ ${this.commentLine("Handle potential redirects based on response", 8)} if (response.status === 201 && response.headers.get('location')) {
3432
3519
  const location = response.headers.get('location')!
3433
3520
  redirect(location)
3434
3521
  }`;
3435
3522
  const streamingCode = `
3436
- // Handle streaming responses
3437
- if (response.headers.get('content-type')?.includes('text/stream')) {
3438
- // Process streaming response
3439
- return response.data
3523
+ ${this.commentLine("Handle streaming responses", 8)} if (response.headers.get('content-type')?.includes('text/stream')) {
3524
+ ${this.commentLine("Process streaming response", 10)} return response.data
3440
3525
  }`;
3441
3526
  const fileUploadCode = hasFileUpload ? uploadStrategy === "external" && uploadProvider === "vercel-blob" ? `
3442
- // Handle file uploads with Vercel Blob
3443
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3527
+ ${this.commentLine("Handle file uploads with Vercel Blob", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3444
3528
  const file = (validatedBody as any).file as File
3445
3529
  const blob = await put(file.name, file, { access: 'public' })
3446
3530
  Object.assign(validatedBody, { fileUrl: blob.url })
3447
3531
  }` : uploadStrategy === "external" && uploadProvider === "uploadthing" ? `
3448
- // Handle file uploads with UploadThing
3449
- // Note: UploadThing requires a separate route handler setup
3450
- // See: https://docs.uploadthing.com/getting-started/appdir
3451
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3452
- // UploadThing file handling should be done via their route handler
3453
- // This is a placeholder - implement according to UploadThing docs
3454
- }` : `
3455
- // Handle file uploads with standard FormData
3456
- if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3457
- // Standard file upload handling
3458
- const file = (validatedBody as any).file as File
3459
- // Process file with compression and validation if enabled
3460
- }` : "";
3461
- return `/**
3462
- * ${endpoint.summary || endpoint.description || `${endpoint.method} ${endpoint.path}`}
3463
- * @generated from ${endpoint.method} ${endpoint.path}
3464
- * Features: Input validation, revalidation, error handling
3465
- */
3466
- export const ${actionName} = ${clientName}
3532
+ ${this.commentLine("Handle file uploads with UploadThing", 8)}${this.commentLine("UploadThing requires a separate route handler setup", 8)}${this.commentLine("See: https://docs.uploadthing.com/getting-started/appdir", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3533
+ ${this.commentLine("UploadThing file handling should be done via their route handler", 10)}${this.commentLine("This is a placeholder - implement according to UploadThing docs", 10)} }` : `
3534
+ ${this.commentLine("Handle file uploads with standard FormData", 8)} if (validatedBody && typeof validatedBody === 'object' && 'file' in validatedBody) {
3535
+ ${this.commentLine("Standard file upload handling", 10)} const file = (validatedBody as any).file as File
3536
+ ${this.commentLine("Process file with compression and validation if enabled", 10)} }` : "";
3537
+ const mutationDocBlock = this.buildDocBlock(
3538
+ endpoint,
3539
+ "Input validation, revalidation, error handling"
3540
+ );
3541
+ return `${mutationDocBlock}export const ${actionName} = ${clientName}
3467
3542
  .metadata({
3468
3543
  name: "${kebabActionName}",
3469
3544
  requiresAuth: ${requiresAuth}${hasRateLimit ? `,
3470
3545
  rateLimit: { requests: ${endpoint.metadata.rateLimit?.requests || 10}, window: "${endpoint.metadata.rateLimit?.window || "1m"}" }` : ""}
3471
3546
  })
3472
3547
  .schema(${schemaName})
3473
- .action(async ({ parsedInput, ctx }: { parsedInput: ${hasRequestBody || hasAnyParams ? `z.infer<typeof ${schemaName}>` : "void"}, ctx${requiresAuth || hasRateLimit ? ": { user?: any, ratelimit?: { remaining: number } }" : "?: any"}) => {
3548
+ .action(async ({ parsedInput, ctx }: ${actionArgsType}) => {
3474
3549
  const startTime = Date.now()
3475
3550
 
3476
3551
  try {${rateLimitCode}${parameterProcessing}${fileUploadCode}
@@ -3485,33 +3560,13 @@ export const ${actionName} = ${clientName}
3485
3560
  }
3486
3561
  })${streamingCode}${redirectCode}
3487
3562
 
3488
- // Revalidate cache after successful mutation
3489
- ${revalidationCode}
3490
-
3491
- // Background tasks (Next.js 15 feature)
3492
- ${isStreaming ? `after(async () => {
3493
- // Perform background tasks after response is sent
3494
- await logActionExecution('${actionName}', true, Date.now() - startTime, {
3495
- method: '${endpoint.method}',
3496
- path: '${endpoint.path}'
3497
- })
3498
- })` : `// Log successful execution
3499
- const duration = Date.now() - startTime
3500
- await logActionExecution('${actionName}', true, duration, {
3501
- method: '${endpoint.method}',
3502
- path: '${endpoint.path}'
3503
- })`}
3563
+ ${revalidationCode ? `${this.commentLine("Revalidate cache after successful mutation", 6)}${revalidationCode}
3564
+ ` : ""}
3565
+ ${this.buildSuccessLoggingBlock(actionName, endpoint, 6, isStreaming)}
3504
3566
 
3505
3567
  return response.data
3506
3568
  } catch (error) {
3507
- const duration = Date.now() - startTime
3508
-
3509
- // Enhanced error logging
3510
- await logActionExecution('${actionName}', false, duration, {
3511
- method: '${endpoint.method}',
3512
- path: '${endpoint.path}',
3513
- error: error instanceof Error ? error.message : 'Unknown error'
3514
- })
3569
+ ${this.buildErrorLoggingBlock(actionName, endpoint, 6)}
3515
3570
 
3516
3571
  // Throw enhanced error with context
3517
3572
  throw new ActionExecutionError(
@@ -3670,7 +3725,6 @@ var HookGenerator = class {
3670
3725
  if (hasInfiniteQueries) reactQueryImports.push("useInfiniteQuery");
3671
3726
  const reactImports = [];
3672
3727
  if (hasMutations) reactImports.push("useOptimistic", "useTransition");
3673
- if (hasQueries) reactImports.push("useCallback");
3674
3728
  const imports = [
3675
3729
  "'use client'",
3676
3730
  "",
@@ -3782,19 +3836,19 @@ ${Object.keys(endpointsByTag).map((tag) => `export * from './${toValidIdentifier
3782
3836
  * @returns useQuery result with data of type ${returnType}
3783
3837
  */
3784
3838
  export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join(", ")}, ` : ""}options?: ${optionsType}) {
3785
- const [searchParams, setSearchParams] = useQueryStates(searchParamsParser)
3839
+ const [searchParams] = useQueryStates(searchParamsParser)
3786
3840
  const { initialData, ...restOptions } = options ?? {}
3787
3841
 
3788
3842
  return useQuery({
3789
3843
  queryKey: [...${queryKey}, searchParams],
3790
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3844
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3791
3845
  try {
3792
3846
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams }")})
3793
3847
  return result
3794
3848
  } catch (error) {
3795
3849
  handleActionError(error)
3796
3850
  }
3797
- }, [searchParams]),
3851
+ },
3798
3852
  staleTime: ${staleTime},
3799
3853
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3800
3854
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3826,14 +3880,14 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3826
3880
 
3827
3881
  return useInfiniteQuery({
3828
3882
  queryKey: [...${queryKey}, 'infinite', searchParams],
3829
- queryFn: useCallback(async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3883
+ queryFn: async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3830
3884
  try {
3831
3885
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams, page: pageParam, limit: searchParams.limit }")})
3832
3886
  return result
3833
3887
  } catch (error) {
3834
3888
  handleActionError(error)
3835
3889
  }
3836
- }, [searchParams]),
3890
+ },
3837
3891
  getNextPageParam: (lastPage: ${returnType}, allPages: ${returnType}[]) => {
3838
3892
  if (lastPage?.hasMore || (Array.isArray(lastPage) && lastPage.length === searchParams.limit)) {
3839
3893
  return allPages.length + 1
@@ -3863,10 +3917,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3863
3917
 
3864
3918
  return useSuspenseQuery({
3865
3919
  queryKey: ${queryKey},
3866
- queryFn: useCallback(async () => {
3920
+ queryFn: async () => {
3867
3921
  const result = await ${actionName}(${actionCallParams})
3868
3922
  return result
3869
- }, []),
3923
+ },
3870
3924
  staleTime: ${staleTime},
3871
3925
  initialData: initialData as ${returnType} | undefined,
3872
3926
  ...restOptions
@@ -3883,14 +3937,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3883
3937
 
3884
3938
  return useQuery({
3885
3939
  queryKey: ${queryKey},
3886
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3940
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3887
3941
  try {
3888
3942
  const result = await ${actionName}(${actionCallParams})
3889
3943
  return result
3890
3944
  } catch (error) {
3891
3945
  handleActionError(error)
3892
3946
  }
3893
- }, []),
3947
+ },
3894
3948
  staleTime: ${staleTime},
3895
3949
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3896
3950
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3921,10 +3975,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3921
3975
 
3922
3976
  return useSuspenseQuery({
3923
3977
  queryKey: ${queryKey},
3924
- queryFn: useCallback(async () => {
3978
+ queryFn: async () => {
3925
3979
  const result = await ${actionName}(${actionCallParams})
3926
3980
  return result
3927
- }, []),
3981
+ },
3928
3982
  staleTime: ${staleTime},
3929
3983
  initialData: initialData as ${returnType} | undefined,
3930
3984
  ...restOptions
@@ -3957,8 +4011,6 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3957
4011
  const relatedQueries = this.findRelatedQueries(endpoint, context);
3958
4012
  const invalidationCode = this.buildInvalidationCode(endpoint, relatedQueries, invalidationTags);
3959
4013
  const cancelQueriesCode = this.buildCancelQueriesCode(endpoint, relatedQueries);
3960
- const snapshotCode = this.buildSnapshotCode(endpoint, relatedQueries);
3961
- const rollbackCode = this.buildRollbackCode(endpoint, relatedQueries);
3962
4014
  return `/**
3963
4015
  * Optimized mutation hook for ${endpoint.method} ${endpoint.path}
3964
4016
  * Features: Optimistic updates, smart invalidation, error handling
@@ -3979,7 +4031,7 @@ export function ${hookName}(options?: {
3979
4031
  mutationFn: async (variables: ${inputType}): Promise<${outputType}> => {
3980
4032
  try {
3981
4033
  const result = await ${actionName}(${variablesParam === "undefined" ? "" : "variables"})
3982
- return result.data || ({} as ${outputType})
4034
+ return (result ?? ({} as ${outputType}))
3983
4035
  } catch (error) {
3984
4036
  handleActionError(error)
3985
4037
  }
@@ -3987,16 +4039,14 @@ export function ${hookName}(options?: {
3987
4039
 
3988
4040
  onMutate: async (variables: ${inputType}) => {
3989
4041
  ${cancelQueriesCode}
3990
-
3991
- ${snapshotCode}
3992
-
4042
+
3993
4043
  // Optimistic update (if provided)
3994
4044
  if (options?.optimisticUpdate) {
3995
4045
  const optimisticValue = options.optimisticUpdate(variables)
3996
4046
  setOptimisticData(optimisticValue)
3997
4047
  }
3998
4048
 
3999
- return { /* Snapshot handled via query invalidation */ }
4049
+ return {}
4000
4050
  },
4001
4051
 
4002
4052
  onSuccess: (data, variables) => {
@@ -4005,17 +4055,11 @@ ${snapshotCode}
4005
4055
  toast.success('${this.getSuccessMessage(endpoint)}')
4006
4056
  }
4007
4057
 
4008
- // Invalidate and refetch related queries
4009
- ${invalidationCode}
4010
-
4011
4058
  // Custom success handler
4012
4059
  options?.onSuccess?.(data, variables)
4013
4060
  },
4014
4061
 
4015
- onError: (error: Error, variables: ${inputType}, context: any) => {
4016
- // Rollback optimistic update
4017
- ${rollbackCode}
4018
-
4062
+ onError: (error: Error, variables: ${inputType}) => {
4019
4063
  // Show error toast
4020
4064
  if (options?.showToast !== false) {
4021
4065
  toast.error(error.message || '${this.getErrorMessage(endpoint)}')
@@ -4025,7 +4069,7 @@ ${rollbackCode}
4025
4069
  options?.onError?.(error as Error, variables)
4026
4070
  },
4027
4071
 
4028
- onSettled: () => {
4072
+ onSettled: async () => {
4029
4073
  // Always refetch after error or success
4030
4074
  ${invalidationCode}
4031
4075
  }
@@ -4051,100 +4095,81 @@ ${invalidationCode}
4051
4095
  return [];
4052
4096
  }
4053
4097
  const relatedQueries = [];
4054
- const mutationPath = endpoint.path;
4098
+ const seen = /* @__PURE__ */ new Set();
4055
4099
  const mutationTag = endpoint.tags[0];
4056
- const pathSegments = mutationPath.split("/").filter(Boolean);
4100
+ const mutationSegments = this.getResourceSegments(endpoint.path);
4101
+ const mutationResource = mutationSegments[0];
4057
4102
  context.schema.endpoints.forEach((queryEndpoint) => {
4058
4103
  if (queryEndpoint.method !== "GET") return;
4059
- const queryPath = queryEndpoint.path;
4104
+ const queryId = queryEndpoint.operationId || queryEndpoint.id;
4105
+ if (seen.has(queryId)) return;
4060
4106
  const queryTag = queryEndpoint.tags[0];
4061
- if (queryTag === mutationTag) {
4107
+ const querySegments = this.getResourceSegments(queryEndpoint.path);
4108
+ const queryResource = querySegments[0];
4109
+ if (mutationTag && queryTag === mutationTag) {
4062
4110
  relatedQueries.push(queryEndpoint);
4111
+ seen.add(queryId);
4063
4112
  return;
4064
4113
  }
4065
- const querySegments = queryPath.split("/").filter(Boolean);
4066
- if (pathSegments.length >= 2 && querySegments.length >= 2) {
4067
- const baseMatch = pathSegments.slice(0, 2).join("/") === querySegments.slice(0, 2).join("/");
4068
- if (baseMatch) {
4069
- relatedQueries.push(queryEndpoint);
4070
- }
4114
+ if (mutationResource && queryResource && mutationResource === queryResource) {
4115
+ relatedQueries.push(queryEndpoint);
4116
+ seen.add(queryId);
4071
4117
  }
4072
4118
  });
4073
4119
  return relatedQueries;
4074
4120
  }
4121
+ getResourceSegments(path3) {
4122
+ return path3.split("/").filter(Boolean).filter((segment) => segment.toLowerCase() !== "api" && !/^v\d+/i.test(segment));
4123
+ }
4075
4124
  /**
4076
4125
  * Build invalidation code for related queries
4077
4126
  */
4078
4127
  buildInvalidationCode(endpoint, relatedQueries, invalidationTags) {
4079
- const invalidations = [];
4128
+ const invalidations = /* @__PURE__ */ new Set();
4080
4129
  relatedQueries.forEach((queryEndpoint) => {
4081
- const queryKey = this.generateQueryKey(queryEndpoint);
4082
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
4130
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4131
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4083
4132
  });
4084
4133
  invalidationTags.forEach((tag) => {
4085
- if (!invalidations.some((inv) => inv.includes(`'${tag}'`))) {
4086
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4087
- }
4134
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4088
4135
  });
4089
- if (invalidations.length === 0) {
4136
+ if (invalidations.size === 0) {
4090
4137
  const inferredKey = this.inferQueryKeyFromPath(endpoint);
4091
4138
  if (inferredKey) {
4092
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4139
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4093
4140
  }
4094
4141
  }
4095
- return invalidations.length > 0 ? invalidations.join("\n") : " // No specific cache invalidation needed";
4142
+ if (invalidations.size === 0) {
4143
+ return " // No specific cache invalidation needed";
4144
+ }
4145
+ if (invalidations.size === 1) {
4146
+ const [statement] = Array.from(invalidations);
4147
+ return ` await ${statement}`;
4148
+ }
4149
+ return ` await Promise.all([
4150
+ ${Array.from(invalidations).join(",\n ")}
4151
+ ])`;
4096
4152
  }
4097
4153
  /**
4098
4154
  * Build cancel queries code
4099
4155
  * Uses query key patterns to cancel all related queries regardless of parameters
4100
4156
  */
4101
4157
  buildCancelQueriesCode(endpoint, relatedQueries) {
4102
- const cancels = [];
4158
+ const cancels = /* @__PURE__ */ new Set();
4103
4159
  relatedQueries.forEach((queryEndpoint) => {
4104
- const queryKeyPrefix = `'${toActionName(queryEndpoint.operationId || queryEndpoint.id)}'`;
4105
- cancels.push(` await queryClient.cancelQueries({ queryKey: [${queryKeyPrefix}] })`);
4160
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4161
+ cancels.add(`queryClient.cancelQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4106
4162
  });
4107
- return cancels.length > 0 ? cancels.join("\n") : " // No queries to cancel";
4108
- }
4109
- /**
4110
- * Build snapshot code for rollback
4111
- * Note: We can't snapshot specific queries with parameters in onMutate scope
4112
- * Instead, we'll invalidate all related queries on error
4113
- */
4114
- buildSnapshotCode(endpoint, relatedQueries) {
4115
- if (relatedQueries.length === 0) {
4116
- return " // No queries to snapshot";
4117
- }
4118
- return " // Snapshot handled via query invalidation";
4119
- }
4120
- /**
4121
- * Build rollback code
4122
- */
4123
- buildRollbackCode(endpoint, relatedQueries) {
4124
- if (relatedQueries.length === 0) {
4125
- return " // No queries to rollback";
4163
+ if (cancels.size === 0) {
4164
+ return " // No queries to cancel";
4126
4165
  }
4127
- const rollbacks = [];
4128
- relatedQueries.forEach((queryEndpoint) => {
4129
- const queryKey = this.generateQueryKey(queryEndpoint);
4130
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4131
- rollbacks.push(` if (context?.${varName}) {
4132
- queryClient.setQueryData(${queryKey}, context.${varName})
4133
- }`);
4134
- });
4135
- return rollbacks.join("\n");
4136
- }
4137
- /**
4138
- * Get snapshot return names for context
4139
- */
4140
- getSnapshotReturnNames(relatedQueries) {
4141
- if (relatedQueries.length === 0) {
4142
- return "previousData: null";
4166
+ if (cancels.size === 1) {
4167
+ const [statement] = Array.from(cancels);
4168
+ return ` await ${statement}`;
4143
4169
  }
4144
- return relatedQueries.map((queryEndpoint) => {
4145
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4146
- return `${varName}`;
4147
- }).join(", ");
4170
+ return ` await Promise.all([
4171
+ ${Array.from(cancels).join(",\n ")}
4172
+ ])`;
4148
4173
  }
4149
4174
  /**
4150
4175
  * Infer query key from mutation path
@@ -9394,7 +9419,8 @@ var bridgeConfigSchema = z.object({
9394
9419
  logging: z.boolean().default(true),
9395
9420
  validation: z.boolean().default(true),
9396
9421
  mocking: z.boolean().default(false),
9397
- watch: z.boolean().default(false)
9422
+ watch: z.boolean().default(false),
9423
+ hotReload: z.boolean().default(false)
9398
9424
  }).optional(),
9399
9425
  plugins: z.array(
9400
9426
  z.object({
@@ -9407,6 +9433,7 @@ var bridgeConfigSchema = z.object({
9407
9433
  typescript: z.boolean().default(true),
9408
9434
  strict: z.boolean().default(true),
9409
9435
  comments: z.boolean().default(true),
9436
+ documentation: z.boolean().default(true),
9410
9437
  examples: z.boolean().default(false),
9411
9438
  tests: z.boolean().default(false)
9412
9439
  }).optional()
@@ -9512,12 +9539,14 @@ ${errorMessages}`,
9512
9539
  logging: true,
9513
9540
  validation: true,
9514
9541
  mocking: false,
9515
- watch: false
9542
+ watch: false,
9543
+ hotReload: false
9516
9544
  },
9517
9545
  generation: {
9518
9546
  typescript: true,
9519
9547
  strict: true,
9520
9548
  comments: true,
9549
+ documentation: true,
9521
9550
  examples: false,
9522
9551
  tests: false
9523
9552
  }
@@ -9570,5 +9599,5 @@ ${errorMessages}`,
9570
9599
  };
9571
9600
 
9572
9601
  export { BridgeCore, BridgeError, BridgeLogger, ConfigurationLoader, FileSystemManager, GenerationError, LogLevel, NextJsCodeGenerator, OpenApiSchemaParser, SchemaParseError, ValidationError, VersionChecker, __name, checkAndNotifyUpdates, createBridgeVersionChecker };
9573
- //# sourceMappingURL=chunk-KCNMWAI2.js.map
9574
- //# sourceMappingURL=chunk-KCNMWAI2.js.map
9602
+ //# sourceMappingURL=chunk-7Y66DW7M.js.map
9603
+ //# sourceMappingURL=chunk-7Y66DW7M.js.map