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.
@@ -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),
@@ -3808,7 +3862,6 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3808
3862
  refetchOnReconnect: true, // Refetch when network reconnects
3809
3863
  refetchOnMount: 'always', // Always refetch on mount for fresh data
3810
3864
  refetchInterval: options?.refetchInterval, // Optional polling interval
3811
- initialDataUpdatedAt: initialData ? Date.now() : undefined,
3812
3865
  // React Query v5: placeholderData replaces keepPreviousData
3813
3866
  placeholderData: (previousData: ${returnType} | undefined) => previousData,
3814
3867
  retry: (failureCount: number, error: Error) => {
@@ -3832,14 +3885,14 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3832
3885
 
3833
3886
  return useInfiniteQuery({
3834
3887
  queryKey: [...${queryKey}, 'infinite', searchParams],
3835
- queryFn: useCallback(async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3888
+ queryFn: async ({ pageParam = 1, signal }: { pageParam?: number; signal?: AbortSignal }) => {
3836
3889
  try {
3837
3890
  const result = await ${actionName}(${actionCallParams.replace(queryParamObject, "{ ...searchParams, page: pageParam, limit: searchParams.limit }")})
3838
3891
  return result
3839
3892
  } catch (error) {
3840
3893
  handleActionError(error)
3841
3894
  }
3842
- }, [searchParams]),
3895
+ },
3843
3896
  getNextPageParam: (lastPage: ${returnType}, allPages: ${returnType}[]) => {
3844
3897
  if (lastPage?.hasMore || (Array.isArray(lastPage) && lastPage.length === searchParams.limit)) {
3845
3898
  return allPages.length + 1
@@ -3852,7 +3905,6 @@ export function ${hookName.replace("use", "useInfinite")}(${parameterTypes.lengt
3852
3905
  refetchOnWindowFocus: true,
3853
3906
  refetchOnReconnect: true,
3854
3907
  refetchOnMount: 'always',
3855
- initialDataUpdatedAt: initialData ? Date.now() : undefined,
3856
3908
  placeholderData: (previousData: ${returnType} | undefined) => previousData,
3857
3909
  retry: 3,
3858
3910
  initialData: initialData as ${returnType} | undefined,
@@ -3869,10 +3921,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3869
3921
 
3870
3922
  return useSuspenseQuery({
3871
3923
  queryKey: ${queryKey},
3872
- queryFn: useCallback(async () => {
3924
+ queryFn: async () => {
3873
3925
  const result = await ${actionName}(${actionCallParams})
3874
3926
  return result
3875
- }, []),
3927
+ },
3876
3928
  staleTime: ${staleTime},
3877
3929
  initialData: initialData as ${returnType} | undefined,
3878
3930
  ...restOptions
@@ -3889,14 +3941,14 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3889
3941
 
3890
3942
  return useQuery({
3891
3943
  queryKey: ${queryKey},
3892
- queryFn: useCallback(async ({ signal }: { signal?: AbortSignal }) => {
3944
+ queryFn: async ({ signal }: { signal?: AbortSignal }) => {
3893
3945
  try {
3894
3946
  const result = await ${actionName}(${actionCallParams})
3895
3947
  return result
3896
3948
  } catch (error) {
3897
3949
  handleActionError(error)
3898
3950
  }
3899
- }, []),
3951
+ },
3900
3952
  staleTime: ${staleTime},
3901
3953
  gcTime: ${staleTime * 2}, // React Query v5: gcTime replaces cacheTime
3902
3954
  enabled: ${enabledCondition} && (options?.enabled ?? true),
@@ -3904,7 +3956,6 @@ export function ${hookName}(${parameterTypes.length > 0 ? `${parameterTypes.join
3904
3956
  refetchOnReconnect: true, // Refetch when network reconnects
3905
3957
  refetchOnMount: 'always', // Always refetch on mount for fresh data
3906
3958
  refetchInterval: options?.refetchInterval, // Optional polling interval
3907
- initialDataUpdatedAt: initialData ? Date.now() : undefined,
3908
3959
  // React Query v5: placeholderData replaces keepPreviousData
3909
3960
  placeholderData: (previousData: ${returnType} | undefined) => previousData,
3910
3961
  retry: (failureCount: number, error: Error) => {
@@ -3927,10 +3978,10 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3927
3978
 
3928
3979
  return useSuspenseQuery({
3929
3980
  queryKey: ${queryKey},
3930
- queryFn: useCallback(async () => {
3981
+ queryFn: async () => {
3931
3982
  const result = await ${actionName}(${actionCallParams})
3932
3983
  return result
3933
- }, []),
3984
+ },
3934
3985
  staleTime: ${staleTime},
3935
3986
  initialData: initialData as ${returnType} | undefined,
3936
3987
  ...restOptions
@@ -3963,8 +4014,6 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3963
4014
  const relatedQueries = this.findRelatedQueries(endpoint, context);
3964
4015
  const invalidationCode = this.buildInvalidationCode(endpoint, relatedQueries, invalidationTags);
3965
4016
  const cancelQueriesCode = this.buildCancelQueriesCode(endpoint, relatedQueries);
3966
- const snapshotCode = this.buildSnapshotCode(endpoint, relatedQueries);
3967
- const rollbackCode = this.buildRollbackCode(endpoint, relatedQueries);
3968
4017
  return `/**
3969
4018
  * Optimized mutation hook for ${endpoint.method} ${endpoint.path}
3970
4019
  * Features: Optimistic updates, smart invalidation, error handling
@@ -3974,7 +4023,7 @@ export function ${hookName.replace("use", "useSuspense")}(${parameterTypes.lengt
3974
4023
  export function ${hookName}(options?: {
3975
4024
  onSuccess?: (data: ${outputType}, variables: ${inputType}) => void
3976
4025
  onError?: (error: Error, variables: ${inputType}) => void
3977
- optimisticUpdate?: (variables: ${inputType}) => any
4026
+ optimisticUpdate?: (variables: ${inputType}) => unknown
3978
4027
  showToast?: boolean
3979
4028
  }) {
3980
4029
  const queryClient = useQueryClient()
@@ -3985,7 +4034,7 @@ export function ${hookName}(options?: {
3985
4034
  mutationFn: async (variables: ${inputType}): Promise<${outputType}> => {
3986
4035
  try {
3987
4036
  const result = await ${actionName}(${variablesParam === "undefined" ? "" : "variables"})
3988
- return result.data || ({} as ${outputType})
4037
+ return (result ?? ({} as ${outputType}))
3989
4038
  } catch (error) {
3990
4039
  handleActionError(error)
3991
4040
  }
@@ -3993,16 +4042,14 @@ export function ${hookName}(options?: {
3993
4042
 
3994
4043
  onMutate: async (variables: ${inputType}) => {
3995
4044
  ${cancelQueriesCode}
3996
-
3997
- ${snapshotCode}
3998
-
4045
+
3999
4046
  // Optimistic update (if provided)
4000
4047
  if (options?.optimisticUpdate) {
4001
4048
  const optimisticValue = options.optimisticUpdate(variables)
4002
4049
  setOptimisticData(optimisticValue)
4003
4050
  }
4004
4051
 
4005
- return { /* Snapshot handled via query invalidation */ }
4052
+ return {}
4006
4053
  },
4007
4054
 
4008
4055
  onSuccess: (data, variables) => {
@@ -4011,17 +4058,11 @@ ${snapshotCode}
4011
4058
  toast.success('${this.getSuccessMessage(endpoint)}')
4012
4059
  }
4013
4060
 
4014
- // Invalidate and refetch related queries
4015
- ${invalidationCode}
4016
-
4017
4061
  // Custom success handler
4018
4062
  options?.onSuccess?.(data, variables)
4019
4063
  },
4020
4064
 
4021
- onError: (error: Error, variables: ${inputType}, context: any) => {
4022
- // Rollback optimistic update
4023
- ${rollbackCode}
4024
-
4065
+ onError: (error: Error, variables: ${inputType}) => {
4025
4066
  // Show error toast
4026
4067
  if (options?.showToast !== false) {
4027
4068
  toast.error(error.message || '${this.getErrorMessage(endpoint)}')
@@ -4031,7 +4072,7 @@ ${rollbackCode}
4031
4072
  options?.onError?.(error as Error, variables)
4032
4073
  },
4033
4074
 
4034
- onSettled: () => {
4075
+ onSettled: async () => {
4035
4076
  // Always refetch after error or success
4036
4077
  ${invalidationCode}
4037
4078
  }
@@ -4057,100 +4098,81 @@ ${invalidationCode}
4057
4098
  return [];
4058
4099
  }
4059
4100
  const relatedQueries = [];
4060
- const mutationPath = endpoint.path;
4101
+ const seen = /* @__PURE__ */ new Set();
4061
4102
  const mutationTag = endpoint.tags[0];
4062
- const pathSegments = mutationPath.split("/").filter(Boolean);
4103
+ const mutationSegments = this.getResourceSegments(endpoint.path);
4104
+ const mutationResource = mutationSegments[0];
4063
4105
  context.schema.endpoints.forEach((queryEndpoint) => {
4064
4106
  if (queryEndpoint.method !== "GET") return;
4065
- const queryPath = queryEndpoint.path;
4107
+ const queryId = queryEndpoint.operationId || queryEndpoint.id;
4108
+ if (seen.has(queryId)) return;
4066
4109
  const queryTag = queryEndpoint.tags[0];
4067
- if (queryTag === mutationTag) {
4110
+ const querySegments = this.getResourceSegments(queryEndpoint.path);
4111
+ const queryResource = querySegments[0];
4112
+ if (mutationTag && queryTag === mutationTag) {
4068
4113
  relatedQueries.push(queryEndpoint);
4114
+ seen.add(queryId);
4069
4115
  return;
4070
4116
  }
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
- }
4117
+ if (mutationResource && queryResource && mutationResource === queryResource) {
4118
+ relatedQueries.push(queryEndpoint);
4119
+ seen.add(queryId);
4077
4120
  }
4078
4121
  });
4079
4122
  return relatedQueries;
4080
4123
  }
4124
+ getResourceSegments(path3) {
4125
+ return path3.split("/").filter(Boolean).filter((segment) => segment.toLowerCase() !== "api" && !/^v\d+/i.test(segment));
4126
+ }
4081
4127
  /**
4082
4128
  * Build invalidation code for related queries
4083
4129
  */
4084
4130
  buildInvalidationCode(endpoint, relatedQueries, invalidationTags) {
4085
- const invalidations = [];
4131
+ const invalidations = /* @__PURE__ */ new Set();
4086
4132
  relatedQueries.forEach((queryEndpoint) => {
4087
- const queryKey = this.generateQueryKey(queryEndpoint);
4088
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
4133
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4134
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4089
4135
  });
4090
4136
  invalidationTags.forEach((tag) => {
4091
- if (!invalidations.some((inv) => inv.includes(`'${tag}'`))) {
4092
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4093
- }
4137
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ['${tag}'] })`);
4094
4138
  });
4095
- if (invalidations.length === 0) {
4139
+ if (invalidations.size === 0) {
4096
4140
  const inferredKey = this.inferQueryKeyFromPath(endpoint);
4097
4141
  if (inferredKey) {
4098
- invalidations.push(` queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4142
+ invalidations.add(`queryClient.invalidateQueries({ queryKey: ${inferredKey} })`);
4099
4143
  }
4100
4144
  }
4101
- return invalidations.length > 0 ? invalidations.join("\n") : " // No specific cache invalidation needed";
4145
+ if (invalidations.size === 0) {
4146
+ return " // No specific cache invalidation needed";
4147
+ }
4148
+ if (invalidations.size === 1) {
4149
+ const [statement] = Array.from(invalidations);
4150
+ return ` await ${statement}`;
4151
+ }
4152
+ return ` await Promise.all([
4153
+ ${Array.from(invalidations).join(",\n ")}
4154
+ ])`;
4102
4155
  }
4103
4156
  /**
4104
4157
  * Build cancel queries code
4105
4158
  * Uses query key patterns to cancel all related queries regardless of parameters
4106
4159
  */
4107
4160
  buildCancelQueriesCode(endpoint, relatedQueries) {
4108
- const cancels = [];
4161
+ const cancels = /* @__PURE__ */ new Set();
4109
4162
  relatedQueries.forEach((queryEndpoint) => {
4110
- const queryKeyPrefix = `'${toActionName(queryEndpoint.operationId || queryEndpoint.id)}'`;
4111
- cancels.push(` await queryClient.cancelQueries({ queryKey: [${queryKeyPrefix}] })`);
4163
+ const queryKeyPrefix = toActionName(queryEndpoint.operationId || queryEndpoint.id);
4164
+ cancels.add(`queryClient.cancelQueries({ queryKey: ['${queryKeyPrefix}'] })`);
4112
4165
  });
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";
4166
+ if (cancels.size === 0) {
4167
+ return " // No queries to cancel";
4123
4168
  }
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 === 1) {
4170
+ const [statement] = Array.from(cancels);
4171
+ return ` await ${statement}`;
4132
4172
  }
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";
4149
- }
4150
- return relatedQueries.map((queryEndpoint) => {
4151
- const varName = `previous${this.toPascalCase(toActionName(queryEndpoint.operationId || queryEndpoint.id))}`;
4152
- return `${varName}`;
4153
- }).join(", ");
4173
+ return ` await Promise.all([
4174
+ ${Array.from(cancels).join(",\n ")}
4175
+ ])`;
4154
4176
  }
4155
4177
  /**
4156
4178
  * Infer query key from mutation path
@@ -4244,11 +4266,11 @@ ${invalidationCode}
4244
4266
  }
4245
4267
  getTypeFromZodSchema(schema) {
4246
4268
  if (!schema || typeof schema !== "object") {
4247
- return "any";
4269
+ return "unknown";
4248
4270
  }
4249
4271
  const def = schema._def;
4250
4272
  if (!def) {
4251
- return "any";
4273
+ return "unknown";
4252
4274
  }
4253
4275
  switch (def.typeName) {
4254
4276
  case "ZodString":
@@ -4262,7 +4284,7 @@ ${invalidationCode}
4262
4284
  case "ZodOptional":
4263
4285
  return this.getTypeFromZodSchema(def.innerType);
4264
4286
  default:
4265
- return "any";
4287
+ return "unknown";
4266
4288
  }
4267
4289
  }
4268
4290
  groupEndpointsByTag(endpoints) {
@@ -4324,7 +4346,7 @@ export function useBridgeQuery<TData = unknown, TError = Error>(
4324
4346
  export function useBridgeInfiniteQuery<TData = unknown, TError = Error>(
4325
4347
  queryKey: QueryKey,
4326
4348
  queryFn: QueryFunction<TData, QueryKey>,
4327
- options?: Partial<UseInfiniteQueryOptions<TData, TError, any, QueryKey>>
4349
+ options?: Partial<UseInfiniteQueryOptions<TData, TError, TData, QueryKey>>
4328
4350
  ) {
4329
4351
  return useInfiniteQuery<TData, TError>({
4330
4352
  queryKey,
@@ -9400,7 +9422,8 @@ var bridgeConfigSchema = zod.z.object({
9400
9422
  logging: zod.z.boolean().default(true),
9401
9423
  validation: zod.z.boolean().default(true),
9402
9424
  mocking: zod.z.boolean().default(false),
9403
- watch: zod.z.boolean().default(false)
9425
+ watch: zod.z.boolean().default(false),
9426
+ hotReload: zod.z.boolean().default(false)
9404
9427
  }).optional(),
9405
9428
  plugins: zod.z.array(
9406
9429
  zod.z.object({
@@ -9413,6 +9436,7 @@ var bridgeConfigSchema = zod.z.object({
9413
9436
  typescript: zod.z.boolean().default(true),
9414
9437
  strict: zod.z.boolean().default(true),
9415
9438
  comments: zod.z.boolean().default(true),
9439
+ documentation: zod.z.boolean().default(true),
9416
9440
  examples: zod.z.boolean().default(false),
9417
9441
  tests: zod.z.boolean().default(false)
9418
9442
  }).optional()
@@ -9518,12 +9542,14 @@ ${errorMessages}`,
9518
9542
  logging: true,
9519
9543
  validation: true,
9520
9544
  mocking: false,
9521
- watch: false
9545
+ watch: false,
9546
+ hotReload: false
9522
9547
  },
9523
9548
  generation: {
9524
9549
  typescript: true,
9525
9550
  strict: true,
9526
9551
  comments: true,
9552
+ documentation: true,
9527
9553
  examples: false,
9528
9554
  tests: false
9529
9555
  }
@@ -9590,5 +9616,5 @@ exports.VersionChecker = VersionChecker;
9590
9616
  exports.__name = __name;
9591
9617
  exports.checkAndNotifyUpdates = checkAndNotifyUpdates;
9592
9618
  exports.createBridgeVersionChecker = createBridgeVersionChecker;
9593
- //# sourceMappingURL=chunk-GKRRC2YD.cjs.map
9594
- //# sourceMappingURL=chunk-GKRRC2YD.cjs.map
9619
+ //# sourceMappingURL=chunk-PZXEJ43S.cjs.map
9620
+ //# sourceMappingURL=chunk-PZXEJ43S.cjs.map