mulink 1.0.4 → 1.0.6

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),
@@ -3802,7 +3856,6 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3802
3856
  refetchOnReconnect: true, // Refetch when network reconnects
3803
3857
  refetchOnMount: 'always', // Always refetch on mount for fresh data
3804
3858
  refetchInterval: options?.refetchInterval, // Optional polling interval
3805
- initialDataUpdatedAt: initialData ? Date.now() : undefined,
3806
3859
  // React Query v5: placeholderData replaces keepPreviousData
3807
3860
  placeholderData: (previousData: ${returnType} | undefined) => previousData,
3808
3861
  retry: (failureCount: number, error: Error) => {
@@ -3826,14 +3879,14 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3826
3879
 
3827
3880
  return useInfiniteQuery({
3828
3881
  queryKey: [...${queryKey}, 'infinite', searchParams],
3829
- queryFn: useCallback(async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3882
+ queryFn: async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3830
3883
  try {
3831
3884
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams, page: pageParam, limit: searchParams.limit }")})
3832
3885
  return result
3833
3886
  } catch (error) {
3834
3887
  handleActionError(error)
3835
3888
  }
3836
- }, [searchParams]),
3889
+ },
3837
3890
  getNextPageParam: (lastPage: ${returnType}, allPages: ${returnType}[]) => {
3838
3891
  if (lastPage?.hasMore || (Array.isArray(lastPage) && lastPage.length === searchParams.limit)) {
3839
3892
  return allPages.length + 1
@@ -3846,7 +3899,6 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3846
3899
  refetchOnWindowFocus: true,
3847
3900
  refetchOnReconnect: true,
3848
3901
  refetchOnMount: 'always',
3849
- initialDataUpdatedAt: initialData ? Date.now() : undefined,
3850
3902
  placeholderData: (previousData: ${returnType} | undefined) => previousData,
3851
3903
  retry: 3,
3852
3904
  initialData: initialData as ${returnType} | undefined,
@@ -3863,10 +3915,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3863
3915
 
3864
3916
  return useSuspenseQuery({
3865
3917
  queryKey: ${queryKey},
3866
- queryFn: useCallback(async () => {
3918
+ queryFn: async () => {
3867
3919
  const result = await ${actionName}(${actionCallParams})
3868
3920
  return result
3869
- }, []),
3921
+ },
3870
3922
  staleTime: ${staleTime},
3871
3923
  initialData: initialData as ${returnType} | undefined,
3872
3924
  ...restOptions
@@ -3883,14 +3935,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3883
3935
 
3884
3936
  return useQuery({
3885
3937
  queryKey: ${queryKey},
3886
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3938
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3887
3939
  try {
3888
3940
  const result = await ${actionName}(${actionCallParams})
3889
3941
  return result
3890
3942
  } catch (error) {
3891
3943
  handleActionError(error)
3892
3944
  }
3893
- }, []),
3945
+ },
3894
3946
  staleTime: ${staleTime},
3895
3947
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3896
3948
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3898,7 +3950,6 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3898
3950
  refetchOnReconnect: true, // Refetch when network reconnects
3899
3951
  refetchOnMount: 'always', // Always refetch on mount for fresh data
3900
3952
  refetchInterval: options?.refetchInterval, // Optional polling interval
3901
- initialDataUpdatedAt: initialData ? Date.now() : undefined,
3902
3953
  // React Query v5: placeholderData replaces keepPreviousData
3903
3954
  placeholderData: (previousData: ${returnType} | undefined) => previousData,
3904
3955
  retry: (failureCount: number, error: Error) => {
@@ -3921,10 +3972,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3921
3972
 
3922
3973
  return useSuspenseQuery({
3923
3974
  queryKey: ${queryKey},
3924
- queryFn: useCallback(async () => {
3975
+ queryFn: async () => {
3925
3976
  const result = await ${actionName}(${actionCallParams})
3926
3977
  return result
3927
- }, []),
3978
+ },
3928
3979
  staleTime: ${staleTime},
3929
3980
  initialData: initialData as ${returnType} | undefined,
3930
3981
  ...restOptions
@@ -3957,8 +4008,6 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3957
4008
  const relatedQueries = this.findRelatedQueries(endpoint, context);
3958
4009
  const invalidationCode = this.buildInvalidationCode(endpoint, relatedQueries, invalidationTags);
3959
4010
  const cancelQueriesCode = this.buildCancelQueriesCode(endpoint, relatedQueries);
3960
- const snapshotCode = this.buildSnapshotCode(endpoint, relatedQueries);
3961
- const rollbackCode = this.buildRollbackCode(endpoint, relatedQueries);
3962
4011
  return `/**
3963
4012
  * Optimized mutation hook for ${endpoint.method} ${endpoint.path}
3964
4013
  * Features: Optimistic updates, smart invalidation, error handling
@@ -3968,7 +4017,7 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3968
4017
  export function ${hookName}(options?: {
3969
4018
  onSuccess?: (data: ${outputType}, variables: ${inputType}) => void
3970
4019
  onError?: (error: Error, variables: ${inputType}) => void
3971
- optimisticUpdate?: (variables: ${inputType}) => any
4020
+ optimisticUpdate?: (variables: ${inputType}) => unknown
3972
4021
  showToast?: boolean
3973
4022
  }) {
3974
4023
  const queryClient = useQueryClient()
@@ -3979,7 +4028,7 @@ export function ${hookName}(options?: {
3979
4028
  mutationFn: async (variables: ${inputType}): Promise<${outputType}> => {
3980
4029
  try {
3981
4030
  const result = await ${actionName}(${variablesParam === "undefined" ? "" : "variables"})
3982
- return result.data || ({} as ${outputType})
4031
+ return (result ?? ({} as ${outputType}))
3983
4032
  } catch (error) {
3984
4033
  handleActionError(error)
3985
4034
  }
@@ -3987,16 +4036,14 @@ export function ${hookName}(options?: {
3987
4036
 
3988
4037
  onMutate: async (variables: ${inputType}) => {
3989
4038
  ${cancelQueriesCode}
3990
-
3991
- ${snapshotCode}
3992
-
4039
+
3993
4040
  // Optimistic update (if provided)
3994
4041
  if (options?.optimisticUpdate) {
3995
4042
  const optimisticValue = options.optimisticUpdate(variables)
3996
4043
  setOptimisticData(optimisticValue)
3997
4044
  }
3998
4045
 
3999
- return { /* Snapshot handled via query invalidation */ }
4046
+ return {}
4000
4047
  },
4001
4048
 
4002
4049
  onSuccess: (data, variables) => {
@@ -4005,17 +4052,11 @@ ${snapshotCode}
4005
4052
  toast.success('${this.getSuccessMessage(endpoint)}')
4006
4053
  }
4007
4054
 
4008
- // Invalidate and refetch related queries
4009
- ${invalidationCode}
4010
-
4011
4055
  // Custom success handler
4012
4056
  options?.onSuccess?.(data, variables)
4013
4057
  },
4014
4058
 
4015
- onError: (error: Error, variables: ${inputType}, context: any) => {
4016
- // Rollback optimistic update
4017
- ${rollbackCode}
4018
-
4059
+ onError: (error: Error, variables: ${inputType}) => {
4019
4060
  // Show error toast
4020
4061
  if (options?.showToast !== false) {
4021
4062
  toast.error(error.message || '${this.getErrorMessage(endpoint)}')
@@ -4025,7 +4066,7 @@ ${rollbackCode}
4025
4066
  options?.onError?.(error as Error, variables)
4026
4067
  },
4027
4068
 
4028
- onSettled: () => {
4069
+ onSettled: async () => {
4029
4070
  // Always refetch after error or success
4030
4071
  ${invalidationCode}
4031
4072
  }
@@ -4051,100 +4092,81 @@ ${invalidationCode}
4051
4092
  return [];
4052
4093
  }
4053
4094
  const relatedQueries = [];
4054
- const mutationPath = endpoint.path;
4095
+ const seen = /* @__PURE__ */ new Set();
4055
4096
  const mutationTag = endpoint.tags[0];
4056
- const pathSegments = mutationPath.split("/").filter(Boolean);
4097
+ const mutationSegments = this.getResourceSegments(endpoint.path);
4098
+ const mutationResource = mutationSegments[0];
4057
4099
  context.schema.endpoints.forEach((queryEndpoint) => {
4058
4100
  if (queryEndpoint.method !== "GET") return;
4059
- const queryPath = queryEndpoint.path;
4101
+ const queryId = queryEndpoint.operationId || queryEndpoint.id;
4102
+ if (seen.has(queryId)) return;
4060
4103
  const queryTag = queryEndpoint.tags[0];
4061
- if (queryTag === mutationTag) {
4104
+ const querySegments = this.getResourceSegments(queryEndpoint.path);
4105
+ const queryResource = querySegments[0];
4106
+ if (mutationTag && queryTag === mutationTag) {
4062
4107
  relatedQueries.push(queryEndpoint);
4108
+ seen.add(queryId);
4063
4109
  return;
4064
4110
  }
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
- }
4111
+ if (mutationResource && queryResource && mutationResource === queryResource) {
4112
+ relatedQueries.push(queryEndpoint);
4113
+ seen.add(queryId);
4071
4114
  }
4072
4115
  });
4073
4116
  return relatedQueries;
4074
4117
  }
4118
+ getResourceSegments(path3) {
4119
+ return path3.split("/").filter(Boolean).filter((segment) => segment.toLowerCase() !== "api" && !/^v\d+/i.test(segment));
4120
+ }
4075
4121
  /**
4076
4122
  * Build invalidation code for related queries
4077
4123
  */
4078
4124
  buildInvalidationCode(endpoint, relatedQueries, invalidationTags) {
4079
- const invalidations = [];
4125
+ const invalidations = /* @__PURE__ */ new Set();
4080
4126
  relatedQueries.forEach((queryEndpoint) => {
4081
- const queryKey = this.generateQueryKey(queryEndpoint);
4082
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
4127
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4128
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4083
4129
  });
4084
4130
  invalidationTags.forEach((tag) => {
4085
- if (!invalidations.some((inv) => inv.includes(`'${tag}'`))) {
4086
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4087
- }
4131
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4088
4132
  });
4089
- if (invalidations.length === 0) {
4133
+ if (invalidations.size === 0) {
4090
4134
  const inferredKey = this.inferQueryKeyFromPath(endpoint);
4091
4135
  if (inferredKey) {
4092
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4136
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4093
4137
  }
4094
4138
  }
4095
- return invalidations.length > 0 ? invalidations.join("\n") : " // No specific cache invalidation needed";
4139
+ if (invalidations.size === 0) {
4140
+ return " // No specific cache invalidation needed";
4141
+ }
4142
+ if (invalidations.size === 1) {
4143
+ const [statement] = Array.from(invalidations);
4144
+ return ` await ${statement}`;
4145
+ }
4146
+ return ` await Promise.all([
4147
+ ${Array.from(invalidations).join(",\n ")}
4148
+ ])`;
4096
4149
  }
4097
4150
  /**
4098
4151
  * Build cancel queries code
4099
4152
  * Uses query key patterns to cancel all related queries regardless of parameters
4100
4153
  */
4101
4154
  buildCancelQueriesCode(endpoint, relatedQueries) {
4102
- const cancels = [];
4155
+ const cancels = /* @__PURE__ */ new Set();
4103
4156
  relatedQueries.forEach((queryEndpoint) => {
4104
- const queryKeyPrefix = `'${toActionName(queryEndpoint.operationId || queryEndpoint.id)}'`;
4105
- cancels.push(` await queryClient.cancelQueries({ queryKey: [${queryKeyPrefix}] })`);
4157
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4158
+ cancels.add(`queryClient.cancelQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4106
4159
  });
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";
4160
+ if (cancels.size === 0) {
4161
+ return " // No queries to cancel";
4117
4162
  }
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 === 1) {
4164
+ const [statement] = Array.from(cancels);
4165
+ return ` await ${statement}`;
4126
4166
  }
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";
4143
- }
4144
- return relatedQueries.map((queryEndpoint) => {
4145
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4146
- return `${varName}`;
4147
- }).join(", ");
4167
+ return ` await Promise.all([
4168
+ ${Array.from(cancels).join(",\n ")}
4169
+ ])`;
4148
4170
  }
4149
4171
  /**
4150
4172
  * Infer query key from mutation path
@@ -4238,11 +4260,11 @@ ${invalidationCode}
4238
4260
  }
4239
4261
  getTypeFromZodSchema(schema) {
4240
4262
  if (!schema || typeof schema !== "object") {
4241
- return "any";
4263
+ return "unknown";
4242
4264
  }
4243
4265
  const def = schema._def;
4244
4266
  if (!def) {
4245
- return "any";
4267
+ return "unknown";
4246
4268
  }
4247
4269
  switch (def.typeName) {
4248
4270
  case "ZodString":
@@ -4256,7 +4278,7 @@ ${invalidationCode}
4256
4278
  case "ZodOptional":
4257
4279
  return this.getTypeFromZodSchema(def.innerType);
4258
4280
  default:
4259
- return "any";
4281
+ return "unknown";
4260
4282
  }
4261
4283
  }
4262
4284
  groupEndpointsByTag(endpoints) {
@@ -4318,7 +4340,7 @@ export function useBridgeQuery<TData = unknown, TError = Error>(
4318
4340
  export function useBridgeInfiniteQuery<TData = unknown, TError = Error>(
4319
4341
  queryKey: QueryKey,
4320
4342
  queryFn: QueryFunction<TData, QueryKey>,
4321
- options?: Partial<UseInfiniteQueryOptions<TData, TError, any, QueryKey>>
4343
+ options?: Partial<UseInfiniteQueryOptions<TData, TError, TData, QueryKey>>
4322
4344
  ) {
4323
4345
  return useInfiniteQuery<TData, TError>({
4324
4346
  queryKey,
@@ -9394,7 +9416,8 @@ var bridgeConfigSchema = z.object({
9394
9416
  logging: z.boolean().default(true),
9395
9417
  validation: z.boolean().default(true),
9396
9418
  mocking: z.boolean().default(false),
9397
- watch: z.boolean().default(false)
9419
+ watch: z.boolean().default(false),
9420
+ hotReload: z.boolean().default(false)
9398
9421
  }).optional(),
9399
9422
  plugins: z.array(
9400
9423
  z.object({
@@ -9407,6 +9430,7 @@ var bridgeConfigSchema = z.object({
9407
9430
  typescript: z.boolean().default(true),
9408
9431
  strict: z.boolean().default(true),
9409
9432
  comments: z.boolean().default(true),
9433
+ documentation: z.boolean().default(true),
9410
9434
  examples: z.boolean().default(false),
9411
9435
  tests: z.boolean().default(false)
9412
9436
  }).optional()
@@ -9512,12 +9536,14 @@ ${errorMessages}`,
9512
9536
  logging: true,
9513
9537
  validation: true,
9514
9538
  mocking: false,
9515
- watch: false
9539
+ watch: false,
9540
+ hotReload: false
9516
9541
  },
9517
9542
  generation: {
9518
9543
  typescript: true,
9519
9544
  strict: true,
9520
9545
  comments: true,
9546
+ documentation: true,
9521
9547
  examples: false,
9522
9548
  tests: false
9523
9549
  }
@@ -9570,5 +9596,5 @@ ${errorMessages}`,
9570
9596
  };
9571
9597
 
9572
9598
  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
9599
+ //# sourceMappingURL=chunk-GHDF3TJA.js.map
9600
+ //# sourceMappingURL=chunk-GHDF3TJA.js.map